diff --git a/icu/icu4c/source/i18n/smpdtfmt.cpp b/icu/icu4c/source/i18n/smpdtfmt.cpp index 72ba0c0bfc7..f5e34349caf 100644 --- a/icu/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu/icu4c/source/i18n/smpdtfmt.cpp @@ -3876,7 +3876,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC src = &text; } parseInt(*src, number, pos, allowNegative,currentNumberFormat); - if (!isLenient() && pos.getIndex() < start + count) { + if (obeyCount && !isLenient() && pos.getIndex() < start + count) { return -start; } if (pos.getIndex() != parseStart) { diff --git a/icu/icu4c/source/test/cintltst/cdattst.c b/icu/icu4c/source/test/cintltst/cdattst.c index dfa1e3eebcc..7ce7f38b8bf 100644 --- a/icu/icu4c/source/test/cintltst/cdattst.c +++ b/icu/icu4c/source/test/cintltst/cdattst.c @@ -47,6 +47,7 @@ static void TestForceGannenNumbering(void); static void TestMapDateToCalFields(void); static void TestNarrowQuarters(void); static void TestExtraneousCharacters(void); +static void TestParseTooStrict(void); void addDateForTest(TestNode** root); @@ -70,6 +71,7 @@ void addDateForTest(TestNode** root) TESTCASE(TestMapDateToCalFields); TESTCASE(TestNarrowQuarters); TESTCASE(TestExtraneousCharacters); + TESTCASE(TestParseTooStrict); } /* Testing the DateFormat API */ static void TestDateFormat() @@ -2040,4 +2042,39 @@ static void TestExtraneousCharacters(void) { ucal_close(cal); } +static void TestParseTooStrict(void) { + UErrorCode status = U_ZERO_ERROR; + const char* locale = "en_US"; + UDateFormat* df = udat_open(UDAT_PATTERN, UDAT_PATTERN, locale, u"UTC", -1, u"MM/dd/yyyy", -1, &status); + if (U_FAILURE(status)) { + log_data_err("udat_open locale %s pattern MM/dd/yyyy: %s\n", locale, u_errorName(status)); + return; + } + UCalendar* cal = ucal_open(u"UTC", -1, locale, UCAL_GREGORIAN, &status); + if (U_FAILURE(status)) { + log_data_err("ucal_open locale %s: %s\n", locale, u_errorName(status)); + udat_close(df); + return; + } + ucal_clear(cal); + int32_t ppos = 0; + udat_setLenient(df, false); + udat_parseCalendar(df, cal, u"1/1/2023", -1, &ppos, &status); + if (U_FAILURE(status)) { + log_err("udat_parseCalendar locale %s, 1/1/2023: %s\n", locale, u_errorName(status)); + } else if (ppos != 8) { + log_err("udat_parseCalendar locale %s, 1/1/2023: ppos expect 8, get %d\n", locale, ppos); + } else { + UDate parsedDate = ucal_getMillis(cal, &status); + if (U_FAILURE(status)) { + log_err("ucal_getMillis: %s\n", u_errorName(status)); + } else if (parsedDate < 1672531200000.0 || parsedDate >= 1672617600000.0) { // check for day stating at UTC 2023-01-01 00:00 + log_err("udat_parseCalendar locale %s, 1/1/2023: parsed UDate %.0f out of range\n", locale, parsedDate); + } + } + + ucal_close(cal); + udat_close(df); +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu/icu4c/source/test/intltest/dtfmttst.cpp b/icu/icu4c/source/test/intltest/dtfmttst.cpp index af20ef01782..29f52fab61d 100644 --- a/icu/icu4c/source/test/intltest/dtfmttst.cpp +++ b/icu/icu4c/source/test/intltest/dtfmttst.cpp @@ -20,6 +20,7 @@ #include "unicode/simpletz.h" #include "unicode/strenum.h" #include "unicode/dtfmtsym.h" +#include "unicode/ustring.h" #include "cmemory.h" #include "cstring.h" #include "caltest.h" // for fieldName @@ -131,6 +132,7 @@ void DateFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &nam TESTCASE_AUTO(TestAdoptCalendarLeak); TESTCASE_AUTO(Test20741_ABFields); TESTCASE_AUTO(Test22023_UTCWithMinusZero); + TESTCASE_AUTO(TestNumericFieldStrictParse); TESTCASE_AUTO_END; } @@ -5745,6 +5747,79 @@ void DateFormatTest::Test22023_UTCWithMinusZero() { ASSERT_OK(status); } +void DateFormatTest::TestNumericFieldStrictParse() { + static const struct { + const char* localeID; + const char16_t* const pattern; + const char16_t* const text; + int32_t pos; // final parsed position + UCalendarDateFields field1; + int32_t value1; + UCalendarDateFields field2; + int32_t value2; + } TESTDATA[] = { + // Ticket #22337 + {"en_US", u"MM/dd/yyyy", u"1/1/2023", 8, UCAL_MONTH, UCAL_JANUARY, UCAL_DAY_OF_MONTH, 1}, + // Ticket #22259 + {"en_US", u"dd-MM-uuuu", u"1-01-2023", 9, UCAL_MONTH, UCAL_JANUARY, UCAL_DAY_OF_MONTH, 1}, + {"en_US", u"dd-MM-uuuu", u"01-01-223", 9, UCAL_DAY_OF_MONTH, 1, UCAL_EXTENDED_YEAR, 223}, + }; + for (size_t i = 0; i < UPRV_LENGTHOF(TESTDATA); i++) { + UErrorCode status = U_ZERO_ERROR; + char pbuf[64]; + char tbuf[64]; + + Locale locale = Locale::createFromName(TESTDATA[i].localeID); + LocalPointer sdfmt(new SimpleDateFormat(UnicodeString(TESTDATA[i].pattern), locale, status)); + if (U_FAILURE(status)) { + u_austrncpy(pbuf, TESTDATA[i].pattern, sizeof(pbuf)); + dataerrln("Fail in new SimpleDateFormat locale %s pattern %s: %s", + TESTDATA[i].localeID, pbuf, u_errorName(status)); + continue; + } + LocalPointer cal(Calendar::createInstance(*TimeZone::getGMT(), locale, status)); + if (U_FAILURE(status)) { + dataerrln("Fail in Calendar::createInstance locale %s: %s", + TESTDATA[i].localeID, u_errorName(status)); + continue; + } + cal->clear(); + //cal->set(2023, 0, 1); + ParsePosition ppos(0); + sdfmt->setLenient(false); + sdfmt->parse(UnicodeString(TESTDATA[i].text), *cal, ppos); + + u_austrncpy(pbuf, TESTDATA[i].pattern, sizeof(pbuf)); + u_austrncpy(tbuf, TESTDATA[i].text, sizeof(tbuf)); + if (ppos.getIndex() != TESTDATA[i].pos) { + errln("SimpleDateFormat::parse locale %s pattern %s: expected pos %d, got %d, errIndex %d", + TESTDATA[i].localeID, pbuf, TESTDATA[i].pos, ppos.getIndex(), ppos.getErrorIndex()); + continue; + } + if (TESTDATA[i].field1 < UCAL_FIELD_COUNT) { + int32_t value = cal->get(TESTDATA[i].field1, status); + if (U_FAILURE(status)) { + errln("Calendar::get locale %s pattern %s field %d: %s", + TESTDATA[i].localeID, pbuf, TESTDATA[i].field1, u_errorName(status)); + } else if (value != TESTDATA[i].value1) { + errln("Calendar::get locale %s pattern %s field %d: expected value %d, got %d", + TESTDATA[i].localeID, pbuf, TESTDATA[i].field1, TESTDATA[i].value1, value); + } + } + status = U_ZERO_ERROR; + if (TESTDATA[i].field2 < UCAL_FIELD_COUNT) { + int32_t value = cal->get(TESTDATA[i].field2, status); + if (U_FAILURE(status)) { + errln("Calendar::get locale %s pattern %s field %d: %s", + TESTDATA[i].localeID, pbuf, TESTDATA[i].field2, u_errorName(status)); + } else if (value != TESTDATA[i].value2) { + errln("Calendar::get locale %s pattern %s field %d: expected value %d, got %d", + TESTDATA[i].localeID, pbuf, TESTDATA[i].field2, TESTDATA[i].value2, value); + } + } + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu/icu4c/source/test/intltest/dtfmttst.h b/icu/icu4c/source/test/intltest/dtfmttst.h index f5e07697745..a578a62064a 100644 --- a/icu/icu4c/source/test/intltest/dtfmttst.h +++ b/icu/icu4c/source/test/intltest/dtfmttst.h @@ -267,6 +267,7 @@ class DateFormatTest: public CalendarTimeZoneTest { void TestAdoptCalendarLeak(); void Test20741_ABFields(); void Test22023_UTCWithMinusZero(); + void TestNumericFieldStrictParse(); private: UBool showParse(DateFormat &format, const UnicodeString &formattedString);