From 80534a479b19cc92c6db0cea85d9631d91567f2e Mon Sep 17 00:00:00 2001 From: Makarand Milind Hinge Date: Sun, 22 Feb 2026 01:10:53 +0530 Subject: [PATCH 1/3] LANG-771 Fix DateUtils.ceiling increment on exact boundary --- src/main/java/org/apache/commons/lang3/time/DateUtils.java | 4 +++- .../java/org/apache/commons/lang3/time/DateUtilsTest.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/lang3/time/DateUtils.java b/src/main/java/org/apache/commons/lang3/time/DateUtils.java index f8abf1429b7..28e785f8d0e 100644 --- a/src/main/java/org/apache/commons/lang3/time/DateUtils.java +++ b/src/main/java/org/apache/commons/lang3/time/DateUtils.java @@ -1115,6 +1115,8 @@ private static Calendar modify(final Calendar val, final int field, final Modify return val; } + final long originalMillis = val.getTimeInMillis(); + // Fix for LANG-59 START // see https://issues.apache.org/jira/browse/LANG-59 // @@ -1161,7 +1163,7 @@ private static Calendar modify(final Calendar val, final int field, final Modify for (final int element : aField) { if (element == field) { //This is our field... we stop looping - if (modType == ModifyType.CEILING || modType == ModifyType.ROUND && roundUp) { + if ((modType == ModifyType.CEILING && originalMillis != val.getTimeInMillis())|| (modType == ModifyType.ROUND && roundUp)) { if (field == SEMI_MONTH) { //This is a special case that's hard to generalize //If the date is 1, we round up to 16, otherwise diff --git a/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java index d4f7b0f5d81..bef1052248a 100644 --- a/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java @@ -230,6 +230,7 @@ private static Stream testToLocalDateTimeTimeZone() { private Date date6; private Date date7; private Date date8; + private Date date9; private Calendar calAmPm1; private Calendar calAmPm2; private Calendar calAmPm3; @@ -283,6 +284,7 @@ public void setUp() throws Exception { dateTimeParser.setTimeZone(TIME_ZONE_DEFAULT); TimeZone.setDefault(TIME_ZONE_DEFAULT); } + date9 = dateTimeParser.parse("March 30, 2003 01:10:00.000"); calAmPm1 = Calendar.getInstance(); calAmPm1.setTime(dateAmPm1); calAmPm2 = Calendar.getInstance(); @@ -530,6 +532,9 @@ void testCeiling() throws Exception { assertEquals(dateTimeParser.parse("November 18, 2001 1:24:00.000"), DateUtils.ceiling(date2, Calendar.MINUTE), "ceiling minute-2 failed"); + assertEquals(dateTimeParser.parse("March 30, 2003 01:10:00.000"), + DateUtils.ceiling(date9, Calendar.MINUTE), + "ceiling minute boundary failed"); assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"), DateUtils.ceiling(date1, Calendar.SECOND), "ceiling second-1 failed"); From 6266942d1cf44ab3cbd7df2f33cbd8f724237a6d Mon Sep 17 00:00:00 2001 From: Makarand Milind Hinge Date: Sun, 22 Feb 2026 05:45:17 +0530 Subject: [PATCH 2/3] Fix Checkstyle violations for LANG-771 --- src/main/java/org/apache/commons/lang3/time/DateUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/lang3/time/DateUtils.java b/src/main/java/org/apache/commons/lang3/time/DateUtils.java index 28e785f8d0e..09e0998c8a4 100644 --- a/src/main/java/org/apache/commons/lang3/time/DateUtils.java +++ b/src/main/java/org/apache/commons/lang3/time/DateUtils.java @@ -1163,7 +1163,7 @@ private static Calendar modify(final Calendar val, final int field, final Modify for (final int element : aField) { if (element == field) { //This is our field... we stop looping - if ((modType == ModifyType.CEILING && originalMillis != val.getTimeInMillis())|| (modType == ModifyType.ROUND && roundUp)) { + if (modType == ModifyType.CEILING && originalMillis != val.getTimeInMillis() || modType == ModifyType.ROUND && roundUp) { if (field == SEMI_MONTH) { //This is a special case that's hard to generalize //If the date is 1, we round up to 16, otherwise From 1aa1a1730736a306c7b9e77d0976eb65964b67ad Mon Sep 17 00:00:00 2001 From: Makarand Milind Hinge Date: Sun, 22 Feb 2026 18:30:22 +0530 Subject: [PATCH 3/3] LANG-771 Add edge case tests for DateUtils.ceiling() Add tests for: - Epoch (0) - Negative date (-1) - Long.MIN_VALUE and Long.MAX_VALUE handling - Minute boundary case --- .../apache/commons/lang3/time/DateUtilsTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java index bef1052248a..23a85ecbb79 100644 --- a/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java @@ -532,9 +532,24 @@ void testCeiling() throws Exception { assertEquals(dateTimeParser.parse("November 18, 2001 1:24:00.000"), DateUtils.ceiling(date2, Calendar.MINUTE), "ceiling minute-2 failed"); + // Edge cases (LANG-771) assertEquals(dateTimeParser.parse("March 30, 2003 01:10:00.000"), DateUtils.ceiling(date9, Calendar.MINUTE), "ceiling minute boundary failed"); + final Date epoch = new Date(0); + assertEquals(epoch, + DateUtils.ceiling(epoch, Calendar.MINUTE), + "ceiling minute epoch failed"); + final Date negative = new Date(-1); + assertEquals(new Date(0), + DateUtils.ceiling(negative, Calendar.MINUTE), + "ceiling minute negative failed"); + assertThrows(ArithmeticException.class, + () -> DateUtils.ceiling(new Date(Long.MIN_VALUE), Calendar.MINUTE), + "ceiling minute Long.MIN_VALUE failed"); + assertThrows(ArithmeticException.class, + () -> DateUtils.ceiling(new Date(Long.MAX_VALUE), Calendar.MINUTE), + "ceiling minute Long.MAX_VALUE failed"); assertEquals(dateTimeParser.parse("February 12, 2002 12:34:57.000"), DateUtils.ceiling(date1, Calendar.SECOND), "ceiling second-1 failed");