Skip to content

Commit 1a634c2

Browse files
authored
Fix timetz offset (#114)
* Fix timetz offset * Fix time range
1 parent 1316be6 commit 1a634c2

File tree

3 files changed

+126
-13
lines changed

3 files changed

+126
-13
lines changed

kotlin/local-data-api/src/com/koxudaxi/localDataApi/LocalDataApi.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.koxudaxi.localDataApi
22

33
import java.sql.*
44
import java.time.OffsetDateTime
5+
import java.time.OffsetTime
56
import java.time.ZoneOffset
67
import java.time.format.DateTimeFormatter
78
import java.util.*
@@ -14,17 +15,35 @@ val BOOLEAN = listOf(Types.BOOLEAN, Types.BIT)
1415
val BLOB = listOf(Types.BLOB, Types.BINARY, Types.LONGVARBINARY, Types.VARBINARY)
1516
val DATETIME = listOf(Types.TIMESTAMP)
1617
val DATETIME_TZ = listOf(Types.TIMESTAMP_WITH_TIMEZONE)
18+
val TIME = listOf(Types.TIME)
19+
val TIME_TZ = listOf(Types.TIME_WITH_TIMEZONE)
20+
21+
fun stripNanoSecs(time: String): String {
22+
val splitTime = time.split(".")
23+
return splitTime[0] + ".${splitTime[1]}".dropLastWhile { char -> char == '0' || char == '.' }
24+
}
25+
1726

1827
fun convertOffsetDatetimeToUTC(input: String): String {
1928
val splitFormatUtc = OffsetDateTime.parse(input,
2029
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S]x")
2130
)
2231
.atZoneSameInstant(ZoneOffset.UTC)
2332
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
24-
.split(".")
25-
return splitFormatUtc[0] + ".${splitFormatUtc[1]}".dropLastWhile { char -> char == '0' || char == '.' }
33+
34+
return stripNanoSecs(splitFormatUtc)
2635
}
2736

37+
fun convertOffsetTimeToUTC(input: String): String {
38+
val splitFormatUtc = OffsetTime.parse(input,
39+
DateTimeFormatter.ofPattern("HH:mm:ss[.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S]x")
40+
)
41+
.withOffsetSameInstant(ZoneOffset.UTC)
42+
.format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"))
43+
return stripNanoSecs(splitFormatUtc)
44+
}
45+
46+
2847
fun createField(resultSet: ResultSet, index: Int): Field {
2948
if (resultSet.getObject(index) == null) {
3049
return Field(isNull = true)
@@ -37,7 +56,12 @@ fun createField(resultSet: ResultSet, index: Int): Field {
3756
value in BLOB -> Field(blobValue = Base64.getEncoder().encodeToString(resultSet.getBytes(index)))
3857
value in DATETIME_TZ || (value in DATETIME && resultSet.metaData.getColumnTypeName(index) == "timestamptz")
3958
-> Field(stringValue = convertOffsetDatetimeToUTC(resultSet.getString(index)))
40-
value in DATETIME -> Field(stringValue = Regex("^[^.]+\\.\\d{3}|^[^.]+").find(resultSet.getString(index))!!.value)
59+
value in DATETIME -> Field(stringValue = Regex("^[^.]+\\.\\d{1,6}|^[^.]+").find(resultSet.getString(
60+
index))!!.value)
61+
value in TIME_TZ || (value in TIME && resultSet.metaData.getColumnTypeName(index) == "timetz")
62+
-> Field(stringValue = convertOffsetTimeToUTC(resultSet.getString(index)))
63+
value in TIME -> Field(stringValue = Regex("^[^.]+\\.\\d{1,3}|^[^.]+").find(resultSet.getString(
64+
index))!!.value)
4165
else -> Field(stringValue = resultSet.getString(index))
4266
}
4367
}

kotlin/local-data-api/test/com/koxudaxi/localDataApi/ApplicationTest.kt

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,33 @@ class ApplicationTest {
196196
}
197197
}
198198

199+
@Test
200+
fun testExecuteSelectTIME() {
201+
withTestApplication({ module(testing = true) }) {
202+
203+
handleRequest(HttpMethod.Post, "/Execute") {
204+
addHeader(HttpHeaders.ContentType, "*/*")
205+
setBody(Json.encodeToString(ExecuteStatementRequest(dummyResourceArn, dummySecretArn,
206+
"SELECT CAST('22:41:04.968123' AS TIME) AS value")))
207+
}.apply {
208+
assertEquals(
209+
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"22:41:05\"}]],\"columnMetadata\":null}",
210+
response.content)
211+
assertEquals(HttpStatusCode.OK, response.status())
212+
}
213+
handleRequest(HttpMethod.Post, "/Execute") {
214+
addHeader(HttpHeaders.ContentType, "*/*")
215+
setBody(Json.encodeToString(ExecuteStatementRequest(dummyResourceArn, dummySecretArn,
216+
"SELECT CAST('22:41:04' AS TIME) AS value")))
217+
}.apply {
218+
assertEquals(
219+
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"22:41:04\"}]],\"columnMetadata\":null}",
220+
response.content)
221+
assertEquals(HttpStatusCode.OK, response.status())
222+
}
223+
}
224+
}
225+
199226
@Test
200227
fun testExecuteSelectTIMESTAMP() {
201228
withTestApplication({ module(testing = true) }) {
@@ -205,11 +232,12 @@ class ApplicationTest {
205232
"SELECT CAST('2021-03-10 22:41:04.968123' AS TIMESTAMP) AS value")))
206233
}.apply {
207234
assertEquals(
208-
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"2021-03-10 22:41:04.968\"}]],\"columnMetadata\":null}",
235+
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"2021-03-10 22:41:04.968123\"}]],\"columnMetadata\":null}",
209236
response.content)
210237
assertEquals(HttpStatusCode.OK, response.status())
211238
}
212239

240+
213241
handleRequest(HttpMethod.Post, "/Execute") {
214242
addHeader(HttpHeaders.ContentType, "*/*")
215243
setBody(Json.encodeToString(ExecuteStatementRequest(dummyResourceArn, dummySecretArn,
@@ -223,6 +251,35 @@ class ApplicationTest {
223251
}
224252
}
225253

254+
@Test
255+
fun testExecuteSelectTIME_WITH_TZ() {
256+
ResourceManager.INSTANCE.setResource("h2:./test;MODE=PostgreSQL", dummyResourceArn, null, null, emptyMap())
257+
258+
withTestApplication({ module(testing = true) }) {
259+
handleRequest(HttpMethod.Post, "/Execute") {
260+
addHeader(HttpHeaders.ContentType, "*/*")
261+
setBody(Json.encodeToString(ExecuteStatementRequest(dummyResourceArn, dummySecretArn,
262+
"SELECT CAST('22:41:04.968123+02' AS TIME(6) WITH TIME ZONE) as value")))
263+
}.apply {
264+
assertEquals(
265+
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"20:41:04.968\"}]],\"columnMetadata\":null}",
266+
response.content)
267+
assertEquals(HttpStatusCode.OK, response.status())
268+
}
269+
270+
handleRequest(HttpMethod.Post, "/Execute") {
271+
addHeader(HttpHeaders.ContentType, "*/*")
272+
setBody(Json.encodeToString(ExecuteStatementRequest(dummyResourceArn, dummySecretArn,
273+
"SELECT CAST('22:41:04+02' AS TIME(6) WITH TIME ZONE) AS value")))
274+
}.apply {
275+
assertEquals(
276+
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"20:41:04\"}]],\"columnMetadata\":null}",
277+
response.content)
278+
assertEquals(HttpStatusCode.OK, response.status())
279+
}
280+
}
281+
}
282+
226283
@Test
227284
fun testExecuteSelectTIMESTAMP_WITH_TZ() {
228285
ResourceManager.INSTANCE.setResource("h2:./test;MODE=PostgreSQL", dummyResourceArn, null, null, emptyMap())
@@ -231,21 +288,21 @@ class ApplicationTest {
231288
handleRequest(HttpMethod.Post, "/Execute") {
232289
addHeader(HttpHeaders.ContentType, "*/*")
233290
setBody(Json.encodeToString(ExecuteStatementRequest(dummyResourceArn, dummySecretArn,
234-
"SELECT CAST('2021-03-10 22:41:04.968123' AS TIMESTAMP(6) WITH TIME ZONE) as value")))
291+
"SELECT CAST('2021-03-10 22:41:04.968123+02' AS TIMESTAMP(6) WITH TIME ZONE) as value")))
235292
}.apply {
236293
assertEquals(
237-
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"2021-03-10 22:41:04.968123\"}]],\"columnMetadata\":null}",
294+
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"2021-03-10 20:41:04.968123\"}]],\"columnMetadata\":null}",
238295
response.content)
239296
assertEquals(HttpStatusCode.OK, response.status())
240297
}
241298

242299
handleRequest(HttpMethod.Post, "/Execute") {
243300
addHeader(HttpHeaders.ContentType, "*/*")
244301
setBody(Json.encodeToString(ExecuteStatementRequest(dummyResourceArn, dummySecretArn,
245-
"SELECT CAST('2021-03-10 22:41:04' AS TIMESTAMP(6) WITH TIME ZONE) AS value")))
302+
"SELECT CAST('2021-03-10 22:41:04+02' AS TIMESTAMP(6) WITH TIME ZONE) AS value")))
246303
}.apply {
247304
assertEquals(
248-
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"2021-03-10 22:41:04\"}]],\"columnMetadata\":null}",
305+
"{\"numberOfRecordsUpdated\":0,\"generatedFields\":null,\"records\":[[{\"blobValue\":null,\"booleanValue\":null,\"doubleValue\":null,\"isNull\":null,\"longValue\":null,\"stringValue\":\"2021-03-10 20:41:04\"}]],\"columnMetadata\":null}",
249306
response.content)
250307
assertEquals(HttpStatusCode.OK, response.status())
251308
}

kotlin/local-data-api/test/com/koxudaxi/localDataApi/LocalDataApiTest.kt

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class LocalDataApiTest {
2222
assertEquals(listOf(Types.BLOB, Types.BINARY, Types.LONGVARBINARY, Types.VARBINARY), BLOB)
2323
assertEquals(listOf(Types.TIMESTAMP), DATETIME)
2424
assertEquals(listOf(Types.TIMESTAMP_WITH_TIMEZONE), DATETIME_TZ)
25+
assertEquals(listOf(Types.TIME), TIME)
26+
assertEquals(listOf(Types.TIME_WITH_TIMEZONE), TIME_TZ)
2527
}
2628

2729
@Test
@@ -47,14 +49,44 @@ class LocalDataApiTest {
4749
assertEquals("2021-03-10 20:41:04", convertOffsetDatetimeToUTC("2021-03-10 22:41:04.000000+02"))
4850
}
4951

52+
@Test
53+
fun testConvertOffsetTimeToUTC() {
54+
assertEquals("20:41:04.123", convertOffsetTimeToUTC("22:41:04.123456+02"))
55+
assertEquals("22:41:04.123", convertOffsetTimeToUTC("22:41:04.123456+00"))
56+
assertEquals("20:41:04.123", convertOffsetTimeToUTC("22:41:04.123450+02"))
57+
assertEquals("20:41:04.123", convertOffsetTimeToUTC("22:41:04.123400+02"))
58+
assertEquals("20:41:04.123", convertOffsetTimeToUTC("22:41:04.123000+02"))
59+
assertEquals("20:41:04.12", convertOffsetTimeToUTC("22:41:04.120000+02"))
60+
assertEquals("20:41:04.1", convertOffsetTimeToUTC("22:41:04.100000+02"))
61+
assertEquals("20:41:04", convertOffsetTimeToUTC("22:41:04.000000+02"))
62+
assertEquals("20:41:04.123", convertOffsetTimeToUTC("22:41:04.12345+02"))
63+
assertEquals("20:41:04.123", convertOffsetTimeToUTC("22:41:04.1234+02"))
64+
assertEquals("20:41:04.123", convertOffsetTimeToUTC("22:41:04.123+02"))
65+
assertEquals("20:41:04.12", convertOffsetTimeToUTC("22:41:04.12+02"))
66+
assertEquals("20:41:04.1", convertOffsetTimeToUTC("22:41:04.1+02"))
67+
assertEquals("20:41:04", convertOffsetTimeToUTC("22:41:04+02"))
68+
assertEquals("20:41:04", convertOffsetTimeToUTC("22:41:04.000050+02"))
69+
assertEquals("20:41:04", convertOffsetTimeToUTC("22:41:04.000400+02"))
70+
assertEquals("20:41:04.003", convertOffsetTimeToUTC("22:41:04.003000+02"))
71+
assertEquals("20:41:04.02", convertOffsetTimeToUTC("22:41:04.020000+02"))
72+
assertEquals("20:41:04", convertOffsetTimeToUTC("22:41:04.000000+02"))
73+
}
74+
5075
@Test
5176
fun testCreateField() {
5277
// For PostgreSQL
53-
val mock = mockk<ResultSet>(relaxed = true)
54-
every {mock.metaData.getColumnType(1) } returns Types.TIMESTAMP
55-
every {mock.metaData.getColumnTypeName(1) } returns "timestamptz"
56-
every {mock.getString(1) } returns "2021-03-10 22:41:04.123456+02"
57-
assertEquals(createField(mock, 1).stringValue, "2021-03-10 20:41:04.123456")
78+
val timestamptzMock = mockk<ResultSet>(relaxed = true)
79+
every {timestamptzMock.metaData.getColumnType(1) } returns Types.TIMESTAMP
80+
every {timestamptzMock.metaData.getColumnTypeName(1) } returns "timestamptz"
81+
every {timestamptzMock.getString(1) } returns "2021-03-10 22:41:04.123456+02"
82+
assertEquals(createField(timestamptzMock, 1).stringValue, "2021-03-10 20:41:04.123456")
83+
84+
val timetzMock = mockk<ResultSet>(relaxed = true)
85+
every {timetzMock.metaData.getColumnType(1) } returns Types.TIME
86+
every {timetzMock.metaData.getColumnTypeName(1) } returns "timetz"
87+
every {timetzMock.getString(1) } returns "22:41:04.123+02"
88+
assertEquals(createField(timetzMock, 1).stringValue, "20:41:04.123")
89+
5890
}
5991

6092
@Test

0 commit comments

Comments
 (0)