Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avro time converter respects timezone #24

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package tech.allegro.schema.json2avro.converter.util;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

@@ -67,14 +62,31 @@ public static Long getMicroSeconds(String jsonTime) {
return Long.valueOf(jsonTime);
}
try {
LocalTime time = LocalTime.parse(jsonTime, TIME_FORMATTER);
nanoOfDay = time.toNanoOfDay();
} catch (DateTimeParseException e) {
// This will only succeed if the time has a timezone.
OffsetTime time = OffsetTime.parse(jsonTime, TIME_FORMATTER);
nanoOfDay = time.toLocalTime().toNanoOfDay();

// Apply the offset. The results of this operation might be negative.
// For example, if the time is 02:00:00+03:00, the time at UTC
// is 23:00 the previous day (ie, -01:00). Negative results are added
// to 24 hours to get the correct result. (-01:00 + 24:00 = 23:00.)
nanoOfDay -= time.getOffset().getTotalSeconds() * 1_000_000_000L;
if (nanoOfDay < 0) {
nanoOfDay += 24 * 60 * 60 * 1_000_000_000L;
}
} catch (DateTimeException e) {
try {
LocalTime time = LocalTime.parse(jsonTime, DATE_TIME_FORMATTER);
// Works on any correctly formatted time without a timezone
LocalTime time = LocalTime.parse(jsonTime, TIME_FORMATTER);
nanoOfDay = time.toNanoOfDay();
} catch (DateTimeParseException ex) {
// no logging since it may generate too much noise
try {
// Catchall
LocalTime time = LocalTime.parse(jsonTime, DATE_TIME_FORMATTER);
nanoOfDay = time.toNanoOfDay();
} catch (DateTimeParseException exc) {
// no logging since it may generate too much noise
}
}
}
return nanoOfDay == null ? null : nanoOfDay / 1000;
Original file line number Diff line number Diff line change
@@ -51,6 +51,9 @@ public void testDateTimeConversion() {
assertEquals(3660000000L, getMicroSeconds("01:01"));
assertEquals(44581541000L, getMicroSeconds("12:23:01.541"));
assertEquals(44581541214L, getMicroSeconds("12:23:01.541214"));
assertEquals(39600000000L, getMicroSeconds("12:00:00.000000+01:00"));
assertEquals(39600000000L, getMicroSeconds("10:00:00.000000-01:00"));
assertEquals(84600000000L, getMicroSeconds("03:30:00.000000+04:00"));
}

@Test
100 changes: 88 additions & 12 deletions converter/src/test/resources/field_conversion_failure_listener.json
Original file line number Diff line number Diff line change
@@ -59,6 +59,17 @@
"int"
],
"default": null
},
{
"name": "TIME_WITH_TIMEZONE",
"type": [
"null",
{
"type": "long",
"logicalType": "time-micros"
}
],
"default": null
}
]
}
@@ -81,7 +92,8 @@
},
"data": {
"name": "Bob",
"id": 1
"id": 1,
"time_with_timezone": "04:00:00+05:30"
}
},
{
@@ -90,7 +102,8 @@
},
"data": {
"name": "Alice",
"id": 2
"id": 2,
"time_with_timezone": "12:00:00"
}
}
],
@@ -107,7 +120,8 @@
},
"DATA": {
"NAME": "Bob",
"ID": 1
"ID": 1,
"TIME_WITH_TIMEZONE": 81000000000
}
},
{
@@ -116,7 +130,8 @@
},
"DATA": {
"NAME": "Alice",
"ID": 2
"ID": 2,
"TIME_WITH_TIMEZONE": 43200000000
}
}
]
@@ -134,7 +149,8 @@
"o",
"b"
],
"id": 1
"id": 1,
"time_with_timezone": "04:00:00.000+05:30"
}
},
{
@@ -143,7 +159,8 @@
},
"data": {
"name": "Alice",
"id": 2
"id": 2,
"time_with_timezone": "12:00:00.000"
}
}
],
@@ -160,7 +177,8 @@
},
"DATA": {
"NAME": null,
"ID": 1
"ID": 1,
"TIME_WITH_TIMEZONE": 81000000000
}
},
{
@@ -169,7 +187,8 @@
},
"DATA": {
"NAME": "Alice",
"ID": 2
"ID": 2,
"TIME_WITH_TIMEZONE": 43200000000
}
}
]
@@ -189,7 +208,8 @@
},
"data": {
"name": 808,
"id": 2
"id": 2,
"time_with_timezone": "04:00:00+05:30"
}
},
{
@@ -198,7 +218,8 @@
},
"data": {
"name": "Alice",
"id": 2
"id": 2,
"time_with_timezone": "12:00:00Z"
}
}
],
@@ -220,7 +241,8 @@
},
"DATA": {
"NAME": null,
"ID": 2
"ID": 2,
"TIME_WITH_TIMEZONE": 81000000000
}
},
{
@@ -229,10 +251,64 @@
},
"DATA": {
"NAME": "Alice",
"ID": 2
"ID": 2,
"TIME_WITH_TIMEZONE": 43200000000
}
}
]
},
{
"name": "record with malformed timezone",
"records": [
{
"meta": {
"changes": []
},
"data": {
"name": "Bob",
"id": 1,
"time_with_timezone": "04:00:00+05:30"
}
},
{
"meta": {
"changes": []
},
"data": {
"name": "Alice",
"id": 2,
"time_with_timezone": "12:00:00-3:00"
}
}
],
"expectedOutput": [
{
"META": {
"CHANGES": []
},
"DATA": {
"NAME": "Bob",
"ID": 1,
"TIME_WITH_TIMEZONE": 81000000000
}
},
{
"META": {
"CHANGES": [
{
"FIELD": "time_with_timezone",
"CHANGE": "NULLED",
"REASON": "Could not evaluate union, field TIME_WITH_TIMEZONE is expected to be one of these: NULL, LONG. If this is a complex type, check if offending field (path: DATA.TIME_WITH_TIMEZONE) adheres to schema: 12:00:00-3:00"
}
]
},
"DATA": {
"NAME": "Alice",
"ID": 2,
"TIME_WITH_TIMEZONE": null
}
}
]
}
]
}