From 5c91214f4958ae1e407c98de0a2a0caed6187979 Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Mon, 9 Mar 2026 14:13:23 +0000 Subject: [PATCH] fix: minimise overflow on timestamp conversion When converting a Calcite timestamp to Substrait, we target microsecond precision. However, this is achieved by calculating entirely in nanoseconds and then narrowing to microseconds, introducing the possibility of overflow when dealing with timestamps beyond the year 2262. This change targets microseconds directly when converting Calcite timestamp literals. Signed-off-by: Mark S. Lewis --- .../isthmus/expression/LiteralConverter.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java b/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java index fbaf2b3fb..4296c1a45 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java @@ -12,6 +12,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; @@ -181,11 +182,11 @@ public Expression.Literal convert(RexLiteral literal, boolean nullable) { TimestampString timestamp = literal.getValueAs(TimestampString.class); LocalDateTime localDateTime = LocalDateTime.parse(timestamp.toString(), CALCITE_LOCAL_DATETIME_FORMATTER); - // Calcite supports up to microsecond precision (6), convert nanoseconds to microseconds - long epochNanos = - TimeUnit.SECONDS.toNanos(localDateTime.toEpochSecond(java.time.ZoneOffset.UTC)) - + localDateTime.toLocalTime().getNano(); - long epochMicros = TimeUnit.NANOSECONDS.toMicros(epochNanos); + // Calcite supports up to microsecond precision (6) + long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC); + long epochMicros = + TimeUnit.SECONDS.toMicros(epochSeconds) + + TimeUnit.NANOSECONDS.toMicros(localDateTime.getNano()); return ExpressionCreator.precisionTimestamp(nullable, epochMicros, 6); } case INTERVAL_YEAR: