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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git


The following commit(s) were added to refs/heads/master by this push:
     new 67fe51ea Floor pre-epoch Timestamp seconds in 
DateTimeConverter.convertToType (#433)
67fe51ea is described below

commit 67fe51ea2bdedc91c15d950ce041eb55ad949ffe
Author: Naveed Khan <[email protected]>
AuthorDate: Mon Aug 10 11:24:55 2026 +0000

    Floor pre-epoch Timestamp seconds in DateTimeConverter.convertToType (#433)
    
    * floor pre-epoch Timestamp seconds in DateTimeConverter
    
    getTime() / 1000 truncates toward zero, so a pre-epoch java.sql.Timestamp 
with a sub-second part gained a whole second; use Math.floorDiv so the 
whole-second term agrees with the non-negative getNanos() term.
    
    * test the wrapping whole-second term near Long.MIN_VALUE
    
    For getTime() in [Long.MIN_VALUE, Long.MIN_VALUE + 807] the floorDiv
    term wraps, but adding the non-negative getNanos() / 1000000 wraps it
    back; the two terms reconstruct getTime() exactly, so the conversion
    stays correct without an overflow guard.
---
 .../beanutils2/converters/DateTimeConverter.java   |  2 +-
 .../beanutils2/converters/DateConverterTest.java   | 30 ++++++++++++++++++++++
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git 
a/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java 
b/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java
index 7515b346..3e7952df 100644
--- 
a/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java
+++ 
b/src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java
@@ -200,7 +200,7 @@ public abstract class DateTimeConverter<D> extends 
AbstractConverter<D> {
             // didn't include the milliseconds. The following code
             // ensures it works consistently across JDK versions
             final java.sql.Timestamp timestamp = (java.sql.Timestamp) value;
-            long timeInMillis = timestamp.getTime() / 1000 * 1000;
+            long timeInMillis = Math.floorDiv(timestamp.getTime(), 1000) * 
1000;
             timeInMillis += timestamp.getNanos() / 1000000;
             return toDate(targetType, timeInMillis);
         }
diff --git 
a/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTest.java 
b/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTest.java
index a3cd6e40..7bb5380a 100644
--- 
a/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTest.java
+++ 
b/src/test/java/org/apache/commons/beanutils2/converters/DateConverterTest.java
@@ -17,9 +17,14 @@
 
 package org.apache.commons.beanutils2.converters;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.sql.Timestamp;
 import java.util.Calendar;
 import java.util.Date;
 
+import org.junit.jupiter.api.Test;
+
 /**
  * Test Case for the DateConverter class.
  */
@@ -66,4 +71,29 @@ class DateConverterTest extends 
AbstractDateConverterTest<Date> {
     protected Date toType(final Calendar value) {
         return value.getTime();
     }
+
+    /**
+     * A pre-epoch {@link Timestamp} carries a non-negative sub-second part in 
{@code getNanos()}, so decomposing
+     * {@code getTime()} into whole seconds must floor: integer division 
truncates toward zero for negative values and
+     * gains a whole second.
+     */
+    @Test
+    void testConvertPreEpochSqlTimestamp() {
+        // 1969-12-31T23:59:59.500Z: getTime() == -500, getNanos() == 
500_000_000
+        final Timestamp timestamp = new Timestamp(-500L);
+        assertEquals(-500L, makeConverter().convert(getExpectedType(), 
timestamp).getTime());
+    }
+
+    /**
+     * For {@code getTime()} in {@code [Long.MIN_VALUE, Long.MIN_VALUE + 807]} 
the whole-second term
+     * {@code Math.floorDiv(getTime(), 1000) * 1000} wraps around {@link 
Long#MIN_VALUE}, but adding the non-negative
+     * {@code getNanos() / 1_000_000} wraps it back: the two terms reconstruct 
{@code getTime()} exactly in
+     * two's-complement arithmetic, so no overflow guard is needed.
+     */
+    @Test
+    void testConvertExtremePreEpochSqlTimestamp() {
+        assertEquals(Long.MIN_VALUE, 
makeConverter().convert(getExpectedType(), new 
Timestamp(Long.MIN_VALUE)).getTime());
+        // last value whose whole-second term still wraps
+        assertEquals(Long.MIN_VALUE + 807, 
makeConverter().convert(getExpectedType(), new Timestamp(Long.MIN_VALUE + 
807)).getTime());
+    }
 }

Reply via email to