This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 32a0ed5109 [common] Fix pre-epoch timestamp casts truncating toward 
zero (#9577)
32a0ed5109 is described below

commit 32a0ed5109b55f2bddd3e326af8a1a7d944829d3
Author: jackylee <[email protected]>
AuthorDate: Fri Sep 4 15:23:53 2026 +0800

    [common] Fix pre-epoch timestamp casts truncating toward zero (#9577)
---
 .../org/apache/paimon/utils/DateTimeUtils.java     |  4 ++--
 .../apache/paimon/casting/CastExecutorTest.java    | 25 ++++++++++++++++++++++
 .../org/apache/paimon/utils/DateTimeUtilsTest.java | 24 +++++++++++++++++++++
 3 files changed, 51 insertions(+), 2 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java 
b/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java
index be9857883d..d202a5bc47 100644
--- a/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java
+++ b/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java
@@ -617,7 +617,7 @@ public class DateTimeUtils {
 
     /** Returns the value of the timestamp to seconds since '1970-01-01 
00:00:00' UTC. */
     public static long unixTimestamp(long ts) {
-        return ts / 1000;
+        return Math.floorDiv(ts, MILLIS_PER_SECOND);
     }
 
     // 
--------------------------------------------------------------------------------------------
@@ -680,7 +680,7 @@ public class DateTimeUtils {
 
     private static long zeroLastDigits(long l, int n) {
         long tenToTheN = (long) Math.pow(10, n);
-        return (l / tenToTheN) * tenToTheN;
+        return Math.floorDiv(l, tenToTheN) * tenToTheN;
     }
 
     private static String pad(int length, long v) {
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/casting/CastExecutorTest.java 
b/paimon-common/src/test/java/org/apache/paimon/casting/CastExecutorTest.java
index adea8d08d6..a1cf992a2f 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/casting/CastExecutorTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/casting/CastExecutorTest.java
@@ -868,6 +868,31 @@ public class CastExecutorTest {
                 36000000);
     }
 
+    @Test
+    public void testTimestampToNumericPreEpoch() {
+        CastExecutor<?, ?> cast =
+                CastExecutors.resolve(new TimestampType(3), new 
BigIntType(false));
+
+        // pre-epoch 1969-12-31 23:59:58.500 is -1500 millis, whose epoch 
second is -2
+        compareCastResult(cast, Timestamp.fromEpochMillis(-1500), -2L);
+        compareCastResult(cast, Timestamp.fromEpochMillis(-1000), -1L);
+
+        // post-epoch 1970-01-01 00:00:01.500 -> 1 (unchanged behavior)
+        compareCastResult(cast, Timestamp.fromEpochMillis(1500), 1L);
+    }
+
+    @Test
+    public void testTimestampToTimestampPreEpoch() {
+        CastExecutor<?, ?> cast = CastExecutors.resolve(new TimestampType(6), 
new TimestampType(0));
+
+        // narrowing 1969-12-31 23:59:59.999999 must drop the fraction, not 
cross the epoch
+        compareCastResult(
+                cast,
+                Timestamp.fromLocalDateTime(
+                        LocalDateTime.of(1969, 12, 31, 23, 59, 59, 
999_999_000)),
+                Timestamp.fromLocalDateTime(LocalDateTime.of(1969, 12, 31, 23, 
59, 59)));
+    }
+
     @Test
     public void testDateToTimestamp() {
         String date = "2023-06-06";
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java 
b/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java
index 071ea5aa6e..6a0f88a367 100644
--- a/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java
+++ b/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java
@@ -158,4 +158,28 @@ public class DateTimeUtilsTest {
         assertThat(DateTimeUtils.truncate(full, 9).toLocalDateTime().getNano())
                 .isEqualTo(123_456_789);
     }
+
+    @Test
+    public void testTruncatePreEpoch() {
+        // A pre-epoch value has a negative millisecond, so dropping the 
sub-precision digits has
+        // to floor: rounding toward zero would move the value forward in time 
instead.
+        Timestamp preEpoch =
+                Timestamp.fromLocalDateTime(
+                        LocalDateTime.of(1969, 12, 31, 23, 59, 59, 
999_999_000));
+        assertThat(preEpoch.getMillisecond()).isEqualTo(-1);
+
+        assertThat(DateTimeUtils.truncate(preEpoch, 0).toLocalDateTime())
+                .isEqualTo(LocalDateTime.of(1969, 12, 31, 23, 59, 59));
+        assertThat(DateTimeUtils.truncate(preEpoch, 2).toLocalDateTime())
+                .isEqualTo(LocalDateTime.of(1969, 12, 31, 23, 59, 59, 
990_000_000));
+    }
+
+    @Test
+    public void testUnixTimestampPreEpoch() {
+        // -1500 epoch millis is 1969-12-31 23:59:58.500, whose epoch second 
is -2.
+        assertThat(DateTimeUtils.unixTimestamp(-1500)).isEqualTo(-2);
+        assertThat(DateTimeUtils.unixTimestamp(-1000)).isEqualTo(-1);
+        assertThat(DateTimeUtils.unixTimestamp(-1)).isEqualTo(-1);
+        assertThat(DateTimeUtils.unixTimestamp(1500)).isEqualTo(1);
+    }
 }

Reply via email to