Skip to content

Commit

Permalink
ICU-22337 restore strict parsing length tolerance for non-abutting nu…
Browse files Browse the repository at this point in the history
…meric date fields
  • Loading branch information
pedberg-icu committed Mar 30, 2023
1 parent 9f774e2 commit c125cf6
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 2 deletions.
2 changes: 1 addition & 1 deletion icu4c/source/i18n/smpdtfmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3818,7 +3818,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, ch
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) {
Expand Down
37 changes: 37 additions & 0 deletions icu4c/source/test/cintltst/cdattst.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -70,6 +71,7 @@ void addDateForTest(TestNode** root)
TESTCASE(TestMapDateToCalFields);
TESTCASE(TestNarrowQuarters);
TESTCASE(TestExtraneousCharacters);
TESTCASE(TestParseTooStrict);
}
/* Testing the DateFormat API */
static void TestDateFormat()
Expand Down Expand Up @@ -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 */
75 changes: 75 additions & 0 deletions icu4c/source/test/intltest/dtfmttst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -5751,6 +5753,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<SimpleDateFormat> 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<Calendar> 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
1 change: 1 addition & 0 deletions icu4c/source/test/intltest/dtfmttst.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3857,7 +3857,7 @@ private int subParse(String text, int start, char ch, int count,
} else {
number = parseInt(text, pos, allowNegative,currentNumberFormat);
}
if (!isLenient() && pos.getIndex() < start + count) {
if (obeyCount && !isLenient() && pos.getIndex() < start + count) {
return -start;
}
if (number != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5627,4 +5627,67 @@ public void testExtraneousCharacters() {
df.parse("2021-", cal, pos);
assertTrue("Success parsing '2021-'", pos.getIndex() == 0);
}

@Test
public void TestNumericFieldStrictParse() {
// regression test for ICU-22337, ICU-22259
class NumericFieldStrictParseItem {
public String localeID;
public String pattern;
public String text;
public int pos;
public int field1;
public int value1;
public int field2; // -1 to skip
public int value2;
public NumericFieldStrictParseItem(String locID, String pat, String txt, int p, int f1, int v1, int f2, int v2) {
localeID = locID;
pattern = pat;
text = txt;
pos = p;
field1 = f1;
value1 = v1;
field2 = f2;
value2 = v2;
}
};

final NumericFieldStrictParseItem[] items = {
// locale pattern text pos field1 value1 field2 value2
// Ticket #22337
new NumericFieldStrictParseItem("en_US", "MM/dd/yyyy", "1/1/2023", 8, Calendar.MONTH, Calendar.JANUARY, Calendar.DATE, 1),
// Ticket #22259
new NumericFieldStrictParseItem("en_US", "dd-MM-uuuu", "1-01-2023", 9, Calendar.MONTH, Calendar.JANUARY, Calendar.DATE, 1),
new NumericFieldStrictParseItem("en_US", "dd-MM-uuuu", "01-01-223", 9, Calendar.DATE, 1, Calendar.EXTENDED_YEAR, 223),
};

for (NumericFieldStrictParseItem item : items) {
ULocale locale = new ULocale(item.localeID);
SimpleDateFormat sdfmt = new SimpleDateFormat(item.pattern, locale);
Calendar cal = Calendar.getInstance(TimeZone.GMT_ZONE, locale);
cal.clear();
ParsePosition ppos = new ParsePosition(0);
sdfmt.setLenient(false);
sdfmt.parse(item.text, cal, ppos);
if (ppos.getIndex() != item.pos) {
errln("error: SimpleDateFormat.parse locale " + item.localeID + " pattern " + item.pattern + ": expected pos " +
item.pos + ", got " + ppos.getIndex() + ", errIndex " + ppos.getErrorIndex());
continue;
}
if (item.field1 >= 0) {
int value = cal.get(item.field1);
if (value != item.value1) {
errln("error: Calendar.get locale " + item.localeID + " pattern " + item.pattern + " field "
+ item.field1 + ": expected value " + item.value1 + ", got " + value );
}
}
if (item.field2 >= 0) {
int value = cal.get(item.field2);
if (value != item.value2) {
errln("error: Calendar.get locale " + item.localeID + " pattern " + item.pattern + " field "
+ item.field2 + ": expected value " + item.value2 + ", got " + value );
}
}
}
}
}

0 comments on commit c125cf6

Please sign in to comment.