From c7677154e6b36403ab48ab1caab2b050902f83af Mon Sep 17 00:00:00 2001 From: Scott Russell Date: Tue, 14 Jan 2014 17:26:35 +0000 Subject: [PATCH] ICU-10619 Improve usability of DateFormat's leniency control & promote BooleanAttribute control to draft for v53 X-SVN-Rev: 34888 --- icu4c/source/i18n/datefmt.cpp | 28 ++++++++++ icu4c/source/i18n/smpdtfmt.cpp | 32 +++++++---- icu4c/source/i18n/unicode/datefmt.h | 54 +++++++++++++----- icu4c/source/i18n/unicode/smpdtfmt.h | 2 + icu4c/source/i18n/unicode/udat.h | 15 ++--- icu4c/source/test/intltest/dtfmrgts.cpp | 73 ++++++++++++++++++++++++- icu4c/source/test/intltest/dtfmrgts.h | 3 +- 7 files changed, 175 insertions(+), 32 deletions(-) diff --git a/icu4c/source/i18n/datefmt.cpp b/icu4c/source/i18n/datefmt.cpp index 122f5ee0040..dab4354b0e8 100644 --- a/icu4c/source/i18n/datefmt.cpp +++ b/icu4c/source/i18n/datefmt.cpp @@ -502,6 +502,9 @@ DateFormat::setLenient(UBool lenient) { if (fCalendar != NULL) { fCalendar->setLenient(lenient); + UErrorCode status = U_ZERO_ERROR; + setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status); + setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); } } @@ -509,6 +512,29 @@ DateFormat::setLenient(UBool lenient) UBool DateFormat::isLenient() const +{ + if (fCalendar != NULL) { + UErrorCode status = U_ZERO_ERROR; + return fCalendar->isLenient() + && getBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, status) + && getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status); + } + // fCalendar is rarely null + return FALSE; +} + +void +DateFormat::setCalendarLenient(UBool lenient) +{ + if (fCalendar != NULL) { + fCalendar->setLenient(lenient); + } +} + +//---------------------------------------------------------------------- + +UBool +DateFormat::isCalendarLenient() const { if (fCalendar != NULL) { return fCalendar->isLenient(); @@ -517,6 +543,7 @@ DateFormat::isLenient() const return FALSE; } + //---------------------------------------------------------------------- @@ -549,6 +576,7 @@ UDisplayContext DateFormat::getContext(UDisplayContextType type, UErrorCode& sta //---------------------------------------------------------------------- + DateFormat& DateFormat::setBooleanAttribute(UDateFormatBooleanAttribute attr, UBool newValue, diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 2f0078d5ef4..1e860462bec 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -247,7 +247,7 @@ SimpleDateFormat::SimpleDateFormat(UErrorCode& status) fNumberFormatters(NULL), fOverrideList(NULL) { - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); construct(kShort, (EStyle) (kShort + kDateOffset), fLocale, status); initializeDefaultCentury(); } @@ -265,7 +265,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, { fDateOverride.setToBogus(); fTimeOverride.setToBogus(); - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); initializeDefaultCentury(); @@ -285,7 +285,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, { fDateOverride.setTo(override); fTimeOverride.setToBogus(); - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); initializeDefaultCentury(); @@ -308,7 +308,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fDateOverride.setToBogus(); fTimeOverride.setToBogus(); - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); @@ -330,7 +330,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fDateOverride.setTo(override); fTimeOverride.setToBogus(); - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); initialize(fLocale, status); @@ -355,7 +355,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fDateOverride.setToBogus(); fTimeOverride.setToBogus(); - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); initializeCalendar(NULL,fLocale,status); initialize(fLocale, status); @@ -377,7 +377,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fDateOverride.setToBogus(); fTimeOverride.setToBogus(); - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); initializeCalendar(NULL, fLocale, status); initialize(fLocale, status); @@ -397,7 +397,7 @@ SimpleDateFormat::SimpleDateFormat(EStyle timeStyle, fNumberFormatters(NULL), fOverrideList(NULL) { - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); construct(timeStyle, dateStyle, fLocale, status); if(U_SUCCESS(status)) { initializeDefaultCentury(); @@ -421,6 +421,7 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, fOverrideList(NULL) { if (U_FAILURE(status)) return; + initializeBooleanAttributes(); initializeSymbols(fLocale, initializeCalendar(NULL, fLocale, status),status); if (U_FAILURE(status)) { @@ -437,7 +438,6 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, fDateOverride.setToBogus(); fTimeOverride.setToBogus(); - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); initialize(fLocale, status); if(U_SUCCESS(status)) { @@ -456,7 +456,7 @@ SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat& other) fOverrideList(NULL) { UErrorCode status = U_ZERO_ERROR; - setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status).setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + initializeBooleanAttributes(); *this = other; } @@ -791,6 +791,18 @@ void SimpleDateFormat::initializeDefaultCentury() } } +/* + * Initialize the boolean attributes. Separate so we can call it from all constructors. + */ +void SimpleDateFormat::initializeBooleanAttributes() +{ + UErrorCode status = U_ZERO_ERROR; + + setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status); + setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); + setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); +} + /* Define one-century window into which to disambiguate dates using * two-digit years. Make public in JDK 1.2. */ diff --git a/icu4c/source/i18n/unicode/datefmt.h b/icu4c/source/i18n/unicode/datefmt.h index 4af35b7d73c..3de0adcd892 100644 --- a/icu4c/source/i18n/unicode/datefmt.h +++ b/icu4c/source/i18n/unicode/datefmt.h @@ -581,26 +581,54 @@ public: static const Locale* U_EXPORT2 getAvailableLocales(int32_t& count); /** - * Returns true if the encapsulated Calendar object is set for lenient parsing. + * Returns whether both date/time parsing in the encapsulated Calendar object and DateFormat whitespace & + * numeric processing is lenient. * @stable ICU 2.0 */ virtual UBool isLenient(void) const; /** - * Specify whether or not date/time parsing is to be lenient. With lenient - * parsing, the parser may use heuristics to interpret inputs that do not - * precisely match this object's format. With strict parsing, inputs must - * match this object's format. - * - * Note: This method is specific to the encapsulated Calendar object. DateFormat - * leniency aspects are controlled by setBooleanAttribute. + * Specifies whether date/time parsing is to be lenient. With + * lenient parsing, the parser may use heuristics to interpret inputs that + * do not precisely match this object's format. Without lenient parsing, + * inputs must match this object's format more closely. + * + * Note: ICU 53 introduced finer grained control of leniency (and added + * new control points) making the preferred method a combination of + * setCalendarLenient() & setBooleanAttribute() calls. + * This method supports prior functionality but may not support all + * future leniency control & behavior of DateFormat. For control of pre 53 leniency, + * Calendar and DateFormat whitespace & numeric tolerance, this method is safe to + * use. However, mixing leniency control via this method and modification of the + * newer attributes via setBooleanAttribute() may produce undesirable + * results. * * @param lenient True specifies date/time interpretation to be lenient. * @see Calendar::setLenient - * @stable ICU 2.0 + * @stable ICU 2.0 */ virtual void setLenient(UBool lenient); + + /** + * Returns whether date/time parsing in the encapsulated Calendar object processing is lenient. + * @draft ICU 53 + */ + virtual UBool isCalendarLenient(void) const; + + + /** + * Specifies whether encapsulated Calendar date/time parsing is to be lenient. With + * lenient parsing, the parser may use heuristics to interpret inputs that + * do not precisely match this object's format. Without lenient parsing, + * inputs must match this object's format more closely. + * @param lenient when true, parsing is lenient + * @see com.ibm.icu.util.Calendar#setLenient + * @draft ICU 53 + */ + virtual void setCalendarLenient(UBool lenient); + + /** * Gets the calendar associated with this date/time formatter. * @return the calendar associated with this date/time formatter. @@ -700,14 +728,14 @@ public: virtual UDisplayContext getContext(UDisplayContextType type, UErrorCode& status) const; /** - * Set an boolean attribute on this DateFormat. + * Sets an boolean attribute on this DateFormat. * May return U_UNSUPPORTED_ERROR if this instance does not support * the specified attribute. * @param attr the attribute to set * @param newvalue new value * @param status the error type * @return *this - for chaining (example: format.setAttribute(...).setAttribute(...) ) - * @internal ICU technology preview + * @draft ICU 53 */ virtual DateFormat& U_EXPORT2 setBooleanAttribute(UDateFormatBooleanAttribute attr, @@ -715,13 +743,13 @@ public: UErrorCode &status); /** - * Get an boolean from this DateFormat + * Returns a boolean from this DateFormat * May return U_UNSUPPORTED_ERROR if this instance does not support * the specified attribute. * @param attr the attribute to set * @param status the error type * @return the attribute value. Undefined if there is an error. - * @internal ICU technology preview + * @draft ICU 53 */ virtual UBool U_EXPORT2 getBooleanAttribute(UDateFormatBooleanAttribute attr, UErrorCode &status) const; diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index bd6bdbd0745..4875615404d 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -1151,6 +1151,8 @@ private: void initializeDefaultCentury(void); + void initializeBooleanAttributes(void); + SimpleDateFormat(); // default constructor not implemented /** diff --git a/icu4c/source/i18n/unicode/udat.h b/icu4c/source/i18n/unicode/udat.h index 4437743f6b3..ccdbc73eb93 100644 --- a/icu4c/source/i18n/unicode/udat.h +++ b/icu4c/source/i18n/unicode/udat.h @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 1996-2013, International Business Machines + * Copyright (C) 1996-2014, International Business Machines * Corporation and others. All Rights Reserved. ******************************************************************************* */ @@ -837,18 +837,19 @@ udat_close(UDateFormat* format); /** * DateFormat boolean attributes - * @internal ICU technology preview + * + * @draft ICU 53 */ typedef enum UDateFormatBooleanAttribute { /** * indicates whether whitespace is allowed. Includes trailing dot tolerance. - * @internal ICU technology preview + * @draft ICU 53 */ UDAT_PARSE_ALLOW_WHITESPACE, /** * indicates tolerance of numeric data when String data may be assumed. eg: UDAT_YEAR_NAME_FIELD, * UDAT_STANDALONE_MONTH_FIELD, UDAT_DAY_OF_WEEK_FIELD - * @internal ICU technology preview + * @draft ICU 53 */ UDAT_PARSE_ALLOW_NUMERIC, /** @@ -858,7 +859,7 @@ typedef enum UDateFormatBooleanAttribute { UDAT_PARSE_PARTIAL_MATCH, /** * count boolean date format constants - * @internal ICU technology preview + * @draft ICU 53 */ UDAT_BOOLEAN_ATTRIBUTE_COUNT } UDateFormatBooleanAttribute; @@ -872,7 +873,7 @@ typedef enum UDateFormatBooleanAttribute { * @param attr The attribute to query; e.g. UDAT_PARSE_ALLOW_WHITESPACE. * @param status A pointer to an UErrorCode to receive any errors * @return The value of attr. - * @internal technology preview + * @draft ICU 53 */ U_INTERNAL UBool U_EXPORT2 udat_getBooleanAttribute(const UDateFormat* fmt, UDateFormatBooleanAttribute attr, UErrorCode* status); @@ -885,7 +886,7 @@ udat_getBooleanAttribute(const UDateFormat* fmt, UDateFormatBooleanAttribute att * @param attr The attribute to set; one of UDAT_PARSE_ALLOW_WHITESPACE or UDAT_PARSE_ALLOW_NUMERIC * @param newValue The new value of attr. * @param status A pointer to an UErrorCode to receive any errors - * @internal ICU technology preview + * @draft ICU 53 */ U_INTERNAL void U_EXPORT2 udat_setBooleanAttribute(UDateFormat *fmt, UDateFormatBooleanAttribute attr, UBool, UErrorCode* status); diff --git a/icu4c/source/test/intltest/dtfmrgts.cpp b/icu4c/source/test/intltest/dtfmrgts.cpp index d49c5b2f60d..2534a5397f7 100644 --- a/icu4c/source/test/intltest/dtfmrgts.cpp +++ b/icu4c/source/test/intltest/dtfmrgts.cpp @@ -1,6 +1,6 @@ /******************************************************************** * COPYRIGHT: - * Copyright (c) 1997-2013, International Business Machines Corporation and + * Copyright (c) 1997-2014, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -58,6 +58,7 @@ DateFormatRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* CASE(27,Test9237) CASE(28,TestParsing) CASE(29,TestT10334) + CASE(30,TestT10619) default: name = ""; break; } } @@ -85,6 +86,7 @@ void DateFormatRegressionTest::Test4029195(void) pat = sdf->toPattern(pat); logln("pattern: " + pat); UnicodeString fmtd; + FieldPosition pos(FieldPosition::DONT_CARE); fmtd = sdf->format(today, fmtd, pos); logln("today: " + fmtd); @@ -1580,6 +1582,75 @@ void DateFormatRegressionTest::TestT10334(void) { } + +typedef struct { + const char * locale; + UBool leniency; + UnicodeString parseString; + UnicodeString pattern; + UnicodeString expectedResult; // null indicates expected error +} TestDateFormatLeniencyItem; + + +void DateFormatRegressionTest::TestT10619(void) { + const UDate july022008 = 1215000001979.0; + const TestDateFormatLeniencyItem items[] = { + /* + new TestDateFormatLeniencyItem(true, "2008-Jan 02", "yyyy-LLL. dd", "2008-Jan. 02"), + new TestDateFormatLeniencyItem(false, "2008-Jan 03", "yyyy-LLL. dd", null), + new TestDateFormatLeniencyItem(true, "2008-Jan--04", "yyyy-MMM' -- 'dd", "2008-Jan -- 04"), + new TestDateFormatLeniencyItem(false, "2008-Jan--05", "yyyy-MMM' -- 'dd", null), + new TestDateFormatLeniencyItem(true, "2008-12-31", "yyyy-mm-dd", "2008-12-31"), + new TestDateFormatLeniencyItem(false, "6 Jan 05 2008", "eee MMM dd yyyy", null), + new TestDateFormatLeniencyItem(true, "6 Jan 05 2008", "eee MMM dd yyyy", "Sat Jan 05 2008"), + */ + //locale leniency parse String pattern expected result + { "en", true, UnicodeString("2008-07 02"), UnicodeString("yyyy-LLLL dd"), UnicodeString("2008-July 02") }, + { "en", false, UnicodeString("2008-07 02"), UnicodeString("yyyy-LLLL dd"), UnicodeString("") }, + { "en", true, UnicodeString("2008-Jan 02"), UnicodeString("yyyy-LLL. dd"), UnicodeString("2008-Jan 02") }, + { "en", false, UnicodeString("2008-Jan 02"), UnicodeString("yyyy-LLL. dd"), UnicodeString("") }, + { "en", true, UnicodeString("2008-Jan--02"), UnicodeString("yyyy-MMM' -- 'dd"), UnicodeString("2008-Jan 02") }, + { "en", false, UnicodeString("2008-Jan--02"), UnicodeString("yyyy-MMM' -- 'dd"), UnicodeString("") }, + { "en", true, UnicodeString("6 Jan 05 2008"), UnicodeString("eee MMM dd yyyy"), UnicodeString("Sat Jan 05 2008") }, + { "en", false, UnicodeString("6 Jan 05 2008"), UnicodeString("eee MMM dd yyyy"), UnicodeString("") }, + // terminator + { NULL, true, UnicodeString(""), UnicodeString(""), UnicodeString("") } + }; + UErrorCode status = U_ZERO_ERROR; + Calendar* cal = Calendar::createInstance(status); + if (U_FAILURE(status)) { + dataerrln(UnicodeString("FAIL: Unable to create Calendar for default timezone and locale.")); + } else { + cal->setTime(july022008, status); + const TestDateFormatLeniencyItem * itemPtr; + for (itemPtr = items; itemPtr->locale != NULL; itemPtr++ ) { + + Locale locale = Locale::createFromName(itemPtr->locale); + status = U_ZERO_ERROR; + ParsePosition pos(0); + SimpleDateFormat * sdmft = new SimpleDateFormat(itemPtr->pattern, locale, status); + if (U_FAILURE(status)) { + dataerrln("Unable to create SimpleDateFormat - %s", u_errorName(status)); + continue; + } + sdmft->setLenient(itemPtr->leniency); + sdmft->setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, itemPtr->leniency, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, itemPtr->leniency, status); + /*UDate d = */sdmft->parse(itemPtr->parseString, pos); + + delete sdmft; + if(pos.getErrorIndex() > -1) + if(itemPtr->expectedResult.length() != 0) { + errln("error: unexpected error - " + itemPtr->parseString + " - error index " + pos.getErrorIndex() + " - leniency " + itemPtr->leniency); + continue; + } else { + continue; + } + } + } + delete cal; + +} + #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu4c/source/test/intltest/dtfmrgts.h b/icu4c/source/test/intltest/dtfmrgts.h index 1d5ed01b7c1..63955103881 100644 --- a/icu4c/source/test/intltest/dtfmrgts.h +++ b/icu4c/source/test/intltest/dtfmrgts.h @@ -1,6 +1,6 @@ /******************************************************************** * COPYRIGHT: - * Copyright (c) 1997-2013, International Business Machines Corporation and + * Copyright (c) 1997-2014, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -54,6 +54,7 @@ public: void Test9237(void); void TestParsing(void); void TestT10334(void); + void TestT10619(void); }; #endif /* #if !UCONFIG_NO_FORMATTING */