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