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