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-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new f4bdd4e1d Fix FastDateParser normalization of out-of-range ISO 
day-of-week values (#1839)
f4bdd4e1d is described below

commit f4bdd4e1d2c99cdab12e5177989fc11e1add33d5
Author: Gary Gregory <[email protected]>
AuthorDate: Sat Oct 24 13:25:51 2026 -0400

    Fix FastDateParser normalization of out-of-range ISO day-of-week values 
(#1839)
    
    Match SimpleDateFormat's lenient handling of week-year and calendar-year
    patterns. Reject zero for strict week-date parsing and add parameterized
    regression coverage.
---
 .../apache/commons/lang3/time/FastDateParser.java  | 52 ++++++++++++++++-
 .../commons/lang3/time/FastDateParserTest.java     | 67 ++++++++++++++++++++++
 2 files changed, 116 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java 
b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
index adcfab611..887b6f8ab 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
@@ -692,9 +692,27 @@ private static final class WeekDateRecorder extends 
GregorianCalendar {
          * {@link java.text.SimpleDateFormat}.
          */
         void applyWeekDate() {
-            if (weekYearSet) {
-                target.setWeekDate(weekYearValue, weekOfYearSet ? 
weekOfYearValue : 1, dayOfWeekSet ? dayOfWeekValue : 
target.getFirstDayOfWeek());
+            if (!weekYearSet) {
+                return;
             }
+            int weekOfYear = weekOfYearSet ? weekOfYearValue : 1;
+            int dayOfWeek = dayOfWeekSet ? dayOfWeekValue : 
target.getFirstDayOfWeek();
+            if (!isValidDayOfWeek(dayOfWeek) && target.isLenient()) {
+                // Calendar.setWeekDate rejects a day of week outside 
SUNDAY..SATURDAY even on a lenient calendar, so fold the overflow of an
+                // out-of-range 'u' value into the week number first, exactly 
as java.text.CalendarBuilder does for SimpleDateFormat.
+                if (dayOfWeek >= 8) {
+                    dayOfWeek--;
+                    weekOfYear += dayOfWeek / 7;
+                    dayOfWeek = dayOfWeek % 7 + 1;
+                } else {
+                    while (dayOfWeek <= 0) {
+                        dayOfWeek += 7;
+                        weekOfYear--;
+                    }
+                }
+                dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
+            }
+            target.setWeekDate(weekYearValue, weekOfYear, dayOfWeek);
         }
 
         @Override
@@ -785,7 +803,7 @@ int modify(final FastDateParser parser, final int iValue) {
     private static final Strategy DAY_OF_WEEK_STRATEGY = new 
NumberStrategy(Calendar.DAY_OF_WEEK) {
         @Override
         int modify(final FastDateParser parser, final int iValue) {
-            return iValue == 7 ? Calendar.SUNDAY : iValue + 1;
+            return toCalendarDayOfWeek(iValue);
         }
     };
 
@@ -862,6 +880,16 @@ private static ConcurrentMap<Locale, Strategy> 
getCache(final int field) {
         }
     }
 
+    /**
+     * Tests whether a value is a valid {@link Calendar#DAY_OF_WEEK} value, in 
the range {@link Calendar#SUNDAY} to {@link Calendar#SATURDAY}.
+     *
+     * @param dayOfWeek the value to test.
+     * @return true if the value is in range.
+     */
+    private static boolean isValidDayOfWeek(final int dayOfWeek) {
+        return dayOfWeek >= Calendar.SUNDAY && dayOfWeek <= Calendar.SATURDAY;
+    }
+
     private static StringBuilder simpleQuote(final StringBuilder sb, final 
String value) {
         for (int i = 0; i < value.length(); ++i) {
             final char c = value.charAt(i);
@@ -891,6 +919,24 @@ private static StringBuilder simpleQuote(final 
StringBuilder sb, final String va
         return sb;
     }
 
+    /**
+     * Converts an ISO 8601 day number of week ('u', Monday = 1 to Sunday = 7) 
to a {@link Calendar#DAY_OF_WEEK} value.
+     *
+     * <p>
+     * An out-of-range value is returned unchanged so a lenient calendar can 
adjust it, as {@code java.text.CalendarBuilder} does for
+     * {@link java.text.SimpleDateFormat}.
+     * </p>
+     *
+     * @param isoDayOfWeek the parsed ISO day number of week.
+     * @return the {@link Calendar#DAY_OF_WEEK} value, or the input when it is 
out of range.
+     */
+    private static int toCalendarDayOfWeek(final int isoDayOfWeek) {
+        if (!isValidDayOfWeek(isoDayOfWeek)) {
+            return isoDayOfWeek;
+        }
+        return isoDayOfWeek == 7 ? Calendar.SUNDAY : isoDayOfWeek + 1;
+    }
+
     /** Input pattern. */
     private final String pattern;
 
diff --git 
a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java 
b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
index 0fddcbafc..a14352c2e 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
@@ -167,6 +167,29 @@ static Stream<Arguments> dateParserParameters() {
         );
     }
 
+    static Stream<Arguments> weekYearParsingOutOfRangeDayOfWeekParameters() {
+        final String[][] cases = {
+            { "YYYY-'W'ww-u", "2025-W01-8" },
+            { "YYYY-'W'ww-u", "2025-W01-0" },
+            { "YYYY-'W'ww-u", "2025-W01-15" },
+            { "YYYY-'W'ww-u", "2025-W01-" + Integer.MAX_VALUE },
+            { "YYYY u", "2014 8" },
+            { "YYYY-'W'ww-u", "2025-W01-7" }, // in range, unchanged
+            { "yyyy-'W'ww-u", "2025-W01-8" }, // plain calendar year is 
adjusted the same way
+            { "yyyy-MM-dd u", "2014-01-06 9" },
+            { "u", "8" },
+            { "u", "0" },
+            { "u", Integer.toString(Integer.MAX_VALUE) }
+        };
+        return Stream.of(Locale.US, Locale.GERMANY)
+            .flatMap(locale -> Stream.of(cases).map(testCase -> 
Arguments.of(testCase[0], testCase[1], locale)));
+    }
+
+    static Stream<Arguments> weekYearParsingStrictDayOfWeekParameters() {
+        return Stream.of(Locale.US, Locale.GERMANY)
+            .flatMap(locale -> Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 123, 
Integer.MAX_VALUE).map(dayOfWeek -> Arguments.of(dayOfWeek, locale)));
+    }
+
     private static Calendar initializeCalendar(final TimeZone timeZone) {
         final Calendar cal = Calendar.getInstance(timeZone);
         cal.set(Calendar.YEAR, 2001);
@@ -930,6 +953,50 @@ void testWeekYearParsing() throws ParseException {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource("weekYearParsingOutOfRangeDayOfWeekParameters")
+    void testWeekYearParsingOutOfRangeDayOfWeek(final String pattern, final 
String input, final Locale locale) throws ParseException {
+        // An out-of-range 'u' value must be adjusted leniently like 
SimpleDateFormat (java.text.CalendarBuilder folds the overflow into the
+        // week number), not passed to Calendar.setWeekDate, which throws 
IllegalArgumentException for a day of week outside 1..7.
+        final SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale);
+        final DateParser parser = getInstance(pattern, locale);
+        final Date expected = sdf.parse(input);
+        assertEquals(expected, parser.parse(input));
+        final ParsePosition pos = new ParsePosition(0);
+        assertEquals(expected, parser.parse(input, pos));
+        assertEquals(input.length(), pos.getIndex());
+        final Calendar calendar = Calendar.getInstance(parser.getTimeZone(), 
locale);
+        calendar.clear();
+        calendar.setLenient(true);
+        final ParsePosition calendarPos = new ParsePosition(0);
+        assertTrue(parser.parse(input, calendarPos, calendar));
+        assertEquals(expected, calendar.getTime());
+        assertEquals(input.length(), calendarPos.getIndex());
+    }
+
+    @ParameterizedTest
+    @MethodSource("weekYearParsingStrictDayOfWeekParameters")
+    void testWeekYearParsingStrictDayOfWeek(final int dayOfWeek, final Locale 
locale) throws ParseException {
+        final String pattern = "YYYY-'W'ww-u";
+        final DateParser parser = getInstance(pattern, locale);
+        final SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale);
+        sdf.setLenient(false);
+        final String input = "2025-W01-" + dayOfWeek;
+        final Calendar calendar = Calendar.getInstance(parser.getTimeZone(), 
locale);
+        calendar.clear();
+        calendar.setLenient(false);
+        final ParsePosition pos = new ParsePosition(0);
+        if (dayOfWeek >= 1 && dayOfWeek <= 7) {
+            assertTrue(parser.parse(input, pos, calendar));
+            assertEquals(sdf.parse(input), calendar.getTime());
+            assertEquals(input.length(), pos.getIndex());
+        } else {
+            // Zero must remain invalid instead of being converted to 
Calendar.SUNDAY.
+            assertThrows(ParseException.class, () -> sdf.parse(input));
+            assertThrows(IllegalArgumentException.class, () -> 
parser.parse(input, pos, calendar));
+        }
+    }
+
     private void validateSdfFormatFdpParseEquality(final String formatStr, 
final Locale locale, final TimeZone timeZone,
         final FastDateParser fastDateParser, final Date inDate, final int 
year, final Date csDate) throws ParseException {
         final SimpleDateFormat sdf = new SimpleDateFormat(formatStr, locale);

Reply via email to