Skip to content

Commit 933e76c

Browse files
committed
Fix timestamp before epoch with micros/nanos (1.4)
This is a backport of the PR #377 to `v1.4-andium` stable branch. This PR fixes the problem with converting `TIMESTAMP` values into `LocalDateTime` where, when micros/nanos part was present, the calculation may have been shifted by 1 second. Fixes: #336
1 parent cbfbe2c commit 933e76c

File tree

3 files changed

+34
-24
lines changed

3 files changed

+34
-24
lines changed

src/main/java/org/duckdb/DuckDBTimestamp.java

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ private static Instant createInstant(long value, ChronoUnit unit) throws SQLExce
4040
case MILLIS:
4141
return Instant.ofEpochMilli(value);
4242
case MICROS: {
43-
long epochSecond = value / 1_000_000;
44-
int nanoAdjustment = nanosPartMicros(value);
43+
long epochSecond = value / 1_000_000L;
44+
long nanoAdjustment = (value % 1_000_000L) * 1000L;
4545
return Instant.ofEpochSecond(epochSecond, nanoAdjustment);
4646
}
4747
case NANOS: {
48-
long epochSecond = value / 1_000_000_000;
49-
long nanoAdjustment = nanosPartNanos(value);
48+
long epochSecond = value / 1_000_000_000L;
49+
long nanoAdjustment = value % 1_000_000_000L;
5050
return Instant.ofEpochSecond(epochSecond, nanoAdjustment);
5151
}
5252
default:
@@ -166,20 +166,4 @@ private static int nanosPartMicros(long micros) {
166166
return (int) ((1000_000L + (micros % 1000_000L)) * 1000);
167167
}
168168
}
169-
170-
private static long nanos2seconds(long nanos) {
171-
if ((nanos % 1_000_000_000L) >= 0) {
172-
return nanos / 1_000_000_000L;
173-
} else {
174-
return (nanos / 1_000_000_000L) - 1;
175-
}
176-
}
177-
178-
private static int nanosPartNanos(long nanos) {
179-
if ((nanos % 1_000_000_000L) >= 0) {
180-
return (int) ((nanos % 1_000_000_000L));
181-
} else {
182-
return (int) ((1_000_000_000L + (nanos % 1_000_000_000L)));
183-
}
184-
}
185169
}

src/test/java/org/duckdb/TestDuckDBJDBC.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2283,7 +2283,7 @@ private static OffsetDateTime localDateTimeToOffset(LocalDateTime ldt) {
22832283
localDateTimeToOffset(LocalDateTime.ofInstant(
22842284
Instant.parse("+294247-01-10T04:00:54.775807Z"), ZoneId.systemDefault())),
22852285
localDateTimeToOffset(LocalDateTime.ofInstant(
2286-
Instant.parse("-290308-12-21T19:59:06.224193Z"), ZoneId.systemDefault())),
2286+
Instant.parse("-290308-12-21T19:59:05.224193Z"), ZoneId.systemDefault())),
22872287
null,
22882288
localDateTimeToOffset(LocalDateTime.ofInstant(Instant.parse("2022-05-12T23:23:45Z"),
22892289
ZoneId.systemDefault()))));

src/test/java/org/duckdb/TestTimestamp.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static void test_timestamp_tz() throws Exception {
7070
OffsetDateTime odt1 = OffsetDateTime.of(2020, 10, 7, 13, 15, 7, 12345, ZoneOffset.ofHours(7));
7171
OffsetDateTime odt1Rounded = OffsetDateTime.of(2020, 10, 7, 13, 15, 7, 12000, ZoneOffset.ofHours(7));
7272
OffsetDateTime odt2 = OffsetDateTime.of(1878, 10, 2, 1, 15, 7, 12345, ZoneOffset.ofHours(-5));
73-
OffsetDateTime odt2Rounded = OffsetDateTime.of(1878, 10, 2, 1, 15, 8, 13000, ZoneOffset.ofHours(-5));
73+
OffsetDateTime odt2Rounded = OffsetDateTime.of(1878, 10, 2, 1, 15, 7, 13000, ZoneOffset.ofHours(-5));
7474
OffsetDateTime odt3 = OffsetDateTime.of(2022, 1, 1, 12, 11, 10, 0, ZoneOffset.ofHours(2));
7575
OffsetDateTime odt4 = OffsetDateTime.of(2022, 1, 1, 12, 11, 10, 0, ZoneOffset.ofHours(0));
7676
OffsetDateTime odt5 = OffsetDateTime.of(1900, 11, 27, 23, 59, 59, 0, ZoneOffset.ofHours(1));
@@ -102,7 +102,7 @@ public static void test_timestamp_tz() throws Exception {
102102
// Metadata tests
103103
assertEquals(Types.TIMESTAMP_WITH_TIMEZONE,
104104
DuckDBResultSetMetaData.type_to_int(DuckDBColumnType.TIMESTAMP_WITH_TIME_ZONE));
105-
assertTrue(OffsetDateTime.class.getName().equals(meta.getColumnClassName(2)));
105+
assertEquals(OffsetDateTime.class.getName(), meta.getColumnClassName(2));
106106
}
107107
} finally {
108108
TimeZone.setDefault(defaultTimeZone);
@@ -296,7 +296,7 @@ public static void duckdb_timestamp_test() throws Exception {
296296
ps.setTimestamp(1, Timestamp.valueOf("1905-11-02 07:59:58.12345"));
297297
try (ResultSet rs6 = ps.executeQuery()) {
298298
assertTrue(rs6.next());
299-
assertEquals(rs6.getTimestamp(1), Timestamp.valueOf("1905-11-02 07:59:59.12345"));
299+
assertEquals(rs6.getTimestamp(1), Timestamp.valueOf("1905-11-02 07:59:58.12345"));
300300
}
301301
}
302302
}
@@ -555,4 +555,30 @@ public static void test_bug532_timestamp() throws Exception {
555555
}
556556
}
557557
}
558+
559+
public static void test_timestamp_before_epoch() throws Exception {
560+
TimeZone defaultTimeZone = TimeZone.getDefault();
561+
TimeZone activeTimeZone = TimeZone.getTimeZone("Europe/Sofia");
562+
TimeZone.setDefault(activeTimeZone);
563+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) {
564+
565+
try (ResultSet rs = stmt.executeQuery("SELECT TIMESTAMP '1969-01-01 00:00:00.123456'")) {
566+
rs.next();
567+
assertEquals(rs.getObject(1, LocalDateTime.class), LocalDateTime.of(1969, 1, 1, 0, 0, 0, 123456000));
568+
}
569+
570+
try (ResultSet rs = stmt.executeQuery("SELECT TIMESTAMP_NS '1969-01-01 00:00:00.123456789'")) {
571+
rs.next();
572+
assertEquals(rs.getObject(1, LocalDateTime.class), LocalDateTime.of(1969, 1, 1, 0, 0, 0, 123456789));
573+
}
574+
575+
try (ResultSet rs = stmt.executeQuery("SELECT TIMESTAMP WITH TIME ZONE '1969-01-01 00:00:00.123456Z'")) {
576+
rs.next();
577+
assertEquals(rs.getObject(1, LocalDateTime.class), LocalDateTime.of(1969, 1, 1, 2, 0, 0, 123456000));
578+
}
579+
580+
} finally {
581+
TimeZone.setDefault(defaultTimeZone);
582+
}
583+
}
558584
}

0 commit comments

Comments
 (0)