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);
+ }
}