From ea55baf863a12dd8fcc3e2148fc72becebfef4b8 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Thu, 25 Aug 2022 13:21:19 +0200 Subject: [PATCH] [jOOQ/jOOQ#13913] Convert truncates microseconds from OffsetDateTime when converting to Instant --- jOOQ/src/main/java/org/jooq/impl/Convert.java | 43 +++++++++++++--- .../src/main/java/org/jooq/tools/Convert.java | 49 ++++++++++++++----- 2 files changed, 72 insertions(+), 20 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/Convert.java b/jOOQ/src/main/java/org/jooq/impl/Convert.java index 4a6e7e9ed4..12859ce1d7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Convert.java +++ b/jOOQ/src/main/java/org/jooq/impl/Convert.java @@ -944,8 +944,11 @@ final class Convert { else if (java.util.Date.class.isAssignableFrom(fromClass)) { // [#12225] Avoid losing precision if possible - if (Timestamp.class == fromClass && LocalDateTime.class == toClass) - return (U) ((Timestamp) from).toLocalDateTime(); + if (Timestamp.class == fromClass) + if (LocalDateTime.class == toClass) + return (U) ((Timestamp) from).toLocalDateTime(); + else + return toDate(((Timestamp) from).getTime(), ((Timestamp) from).getNanos(), toClass); else if (Date.class == fromClass && LocalDate.class == toClass) return (U) ((Date) from).toLocalDate(); else if (Time.class == fromClass && LocalTime.class == toClass) @@ -958,10 +961,14 @@ final class Convert { // [#12225] Avoid losing precision if possible if (LocalDateTime.class == fromClass && Timestamp.class == toClass) return (U) Timestamp.valueOf((LocalDateTime) from); + else if (LocalDateTime.class == fromClass && Temporal.class.isAssignableFrom(toClass)) + return toDate(((LocalDateTime) from).toInstant(OffsetTime.now().getOffset()).toEpochMilli(), ((LocalDateTime) from).getNano(), toClass); else if (LocalDate.class == fromClass && Date.class == toClass) return (U) Date.valueOf((LocalDate) from); else if (LocalTime.class == fromClass && Time.class == toClass) return (U) Time.valueOf((LocalTime) from); + else if (OffsetDateTime.class == fromClass && (Timestamp.class == toClass || Temporal.class.isAssignableFrom(toClass))) + return toDate(((OffsetDateTime) from).toInstant().toEpochMilli(), ((OffsetDateTime) from).getNano(), toClass); else return toDate(convert(from, Long.class), toClass); } @@ -1405,16 +1412,24 @@ final class Convert { } /** - * Convert a long timestamp to any date type + * Convert a long timestamp (millis) to any date type. + */ + private static X toDate(long time, Class toClass) { + return toDate(time, 0, toClass); + } + + /** + * Convert a long timestamp (millis) with nanos adjustment to any date + * type. */ @SuppressWarnings("unchecked") - private static X toDate(long time, Class toClass) { + private static X toDate(long time, int nanos, Class toClass) { if (toClass == Date.class) return (X) new Date(time); else if (toClass == Time.class) return (X) new Time(time); else if (toClass == Timestamp.class) - return (X) new Timestamp(time); + return (X) toTimestamp(time, nanos); else if (toClass == java.util.Date.class) return (X) new java.util.Date(time); else if (toClass == Calendar.class) { @@ -1429,15 +1444,27 @@ final class Convert { else if (toClass == OffsetTime.class) return (X) new Time(time).toLocalTime().atOffset(OffsetTime.now().getOffset()); else if (toClass == LocalDateTime.class) - return (X) new Timestamp(time).toLocalDateTime(); + return (X) toTimestamp(time, nanos).toLocalDateTime(); else if (toClass == OffsetDateTime.class) - return (X) new Timestamp(time).toLocalDateTime().atOffset(OffsetDateTime.now().getOffset()); + return (X) toTimestamp(time, nanos).toLocalDateTime().atOffset(OffsetDateTime.now().getOffset()); else if (toClass == Instant.class) - return (X) Instant.ofEpochMilli(time); + if (nanos == 0L) + return (X) Instant.ofEpochMilli(time); + else + return (X) Instant.ofEpochSecond(time / 1000L, nanos); throw fail(time, toClass); } + private static Timestamp toTimestamp(long time, int nanos) { + if (nanos == 0L) + return new Timestamp(time); + + Timestamp ts = new Timestamp(time / 1000L * 1000L); + ts.setNanos(nanos); + return ts; + } + private static final long millis(Temporal temporal) { // java.sql.* temporal types: diff --git a/jOOQ/src/main/java/org/jooq/tools/Convert.java b/jOOQ/src/main/java/org/jooq/tools/Convert.java index 455a8fcd90..4391429631 100644 --- a/jOOQ/src/main/java/org/jooq/tools/Convert.java +++ b/jOOQ/src/main/java/org/jooq/tools/Convert.java @@ -880,13 +880,15 @@ public final class Convert { return null; } } - // Date types can be converted among each other else if (java.util.Date.class.isAssignableFrom(fromClass)) { // [#12225] Avoid losing precision if possible - if (Timestamp.class == fromClass && LocalDateTime.class == toClass) - return (U) ((Timestamp) from).toLocalDateTime(); + if (Timestamp.class == fromClass) + if (LocalDateTime.class == toClass) + return (U) ((Timestamp) from).toLocalDateTime(); + else + return toDate(((Timestamp) from).getTime(), ((Timestamp) from).getNanos(), toClass); else if (Date.class == fromClass && LocalDate.class == toClass) return (U) ((Date) from).toLocalDate(); else if (Time.class == fromClass && LocalTime.class == toClass) @@ -899,16 +901,17 @@ public final class Convert { // [#12225] Avoid losing precision if possible if (LocalDateTime.class == fromClass && Timestamp.class == toClass) return (U) Timestamp.valueOf((LocalDateTime) from); + else if (LocalDateTime.class == fromClass && Temporal.class.isAssignableFrom(toClass)) + return toDate(((LocalDateTime) from).toInstant(OffsetTime.now().getOffset()).toEpochMilli(), ((LocalDateTime) from).getNano(), toClass); else if (LocalDate.class == fromClass && Date.class == toClass) return (U) Date.valueOf((LocalDate) from); else if (LocalTime.class == fromClass && Time.class == toClass) return (U) Time.valueOf((LocalTime) from); + else if (OffsetDateTime.class == fromClass && (Timestamp.class == toClass || Temporal.class.isAssignableFrom(toClass))) + return toDate(((OffsetDateTime) from).toInstant().toEpochMilli(), ((OffsetDateTime) from).getNano(), toClass); else return toDate(convert(from, Long.class), toClass); } - else if (Temporal.class.isAssignableFrom(fromClass)) { - return toDate(convert(from, Long.class), toClass); - } // Long may also be converted into a date type else if (wrapperFrom == Long.class && java.util.Date.class.isAssignableFrom(toClass)) { @@ -1251,17 +1254,26 @@ public final class Convert { return (Class) toClass; } + /** - * Convert a long timestamp to any date type + * Convert a long timestamp (millis) to any date type. + */ + private static X toDate(long time, Class toClass) { + return toDate(time, 0, toClass); + } + + /** + * Convert a long timestamp (millis) with nanos adjustment to any date + * type. */ @SuppressWarnings("unchecked") - private static X toDate(long time, Class toClass) { + private static X toDate(long time, int nanos, Class toClass) { if (toClass == Date.class) return (X) new Date(time); else if (toClass == Time.class) return (X) new Time(time); else if (toClass == Timestamp.class) - return (X) new Timestamp(time); + return (X) toTimestamp(time, nanos); else if (toClass == java.util.Date.class) return (X) new java.util.Date(time); else if (toClass == Calendar.class) { @@ -1276,15 +1288,28 @@ public final class Convert { else if (toClass == OffsetTime.class) return (X) new Time(time).toLocalTime().atOffset(OffsetTime.now().getOffset()); else if (toClass == LocalDateTime.class) - return (X) new Timestamp(time).toLocalDateTime(); + return (X) toTimestamp(time, nanos).toLocalDateTime(); else if (toClass == OffsetDateTime.class) - return (X) new Timestamp(time).toLocalDateTime().atOffset(OffsetDateTime.now().getOffset()); + return (X) toTimestamp(time, nanos).toLocalDateTime().atOffset(OffsetDateTime.now().getOffset()); else if (toClass == Instant.class) - return (X) Instant.ofEpochMilli(time); + if (nanos == 0L) + return (X) Instant.ofEpochMilli(time); + else + return (X) Instant.ofEpochSecond(time / 1000L, nanos); throw fail(time, toClass); } + private static Timestamp toTimestamp(long time, int nanos) { + if (nanos == 0L) + return new Timestamp(time); + + Timestamp ts = new Timestamp(time / 1000L * 1000L); + ts.setNanos(nanos); + return ts; + } + + private static final long millis(Temporal temporal) { // java.sql.* temporal types: