diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 8a72e4f0cd..0f7cf4ee4f 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 1997-2008, International Business Machines Corporation and * +* Copyright (C) 1997-2009, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * @@ -514,6 +514,19 @@ SimpleDateFormat::format(Calendar& cal, UnicodeString& appendTo, FieldPosition& pos.setBeginIndex(0); pos.setEndIndex(0); + Calendar *workCal = &cal; + TimeZone *backupTZ = NULL; + if (&cal != fCalendar && uprv_strcmp(cal.getType(), fCalendar->getType()) != 0) { + // Different calendar type + // We use the time and time zone from the input calendar, but + // do not use the input calendar for field calculation. + UDate t = cal.getTime(status); + fCalendar->setTime(t, status); + backupTZ = fCalendar->getTimeZone().clone(); + fCalendar->setTimeZone(cal.getTimeZone()); + workCal = fCalendar; + } + UBool inQuote = FALSE; UChar prevCh = 0; int32_t count = 0; @@ -525,7 +538,7 @@ SimpleDateFormat::format(Calendar& cal, UnicodeString& appendTo, FieldPosition& // Use subFormat() to format a repeated pattern character // when a different pattern or non-pattern character is seen if (ch != prevCh && count > 0) { - subFormat(appendTo, prevCh, count, pos, cal, status); + subFormat(appendTo, prevCh, count, pos, *workCal, status); count = 0; } if (ch == QUOTE) { @@ -553,7 +566,12 @@ SimpleDateFormat::format(Calendar& cal, UnicodeString& appendTo, FieldPosition& // Format the last item in the pattern, if any if (count > 0) { - subFormat(appendTo, prevCh, count, pos, cal, status); + subFormat(appendTo, prevCh, count, pos, *workCal, status); + } + + if (backupTZ != NULL) { + // Restore the original time zone + fCalendar->adoptTimeZone(backupTZ); } // and if something failed (e.g., an invalid format character), reset our FieldPosition @@ -1337,8 +1355,10 @@ UBool SimpleDateFormat::isNumeric(UChar formatChar, int32_t count) { void SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& parsePos) const { + UErrorCode status = U_ZERO_ERROR; int32_t pos = parsePos.getIndex(); int32_t start = pos; + UBool ambiguousYear[] = { FALSE }; int32_t count = 0; @@ -1357,6 +1377,21 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& const UnicodeString numericFormatChars(NUMERIC_FORMAT_CHARS); + TimeZone *backupTZ = NULL; + Calendar *workCal = &cal; + if (&cal != fCalendar && uprv_strcmp(cal.getType(), fCalendar->getType()) != 0) { + // Different calendar type + // We use the time/zone from the input calendar, but + // do not use the input calendar for field calculation. + fCalendar->setTime(cal.getTime(status),status); + if (U_FAILURE(status)) { + goto ExitParse; + } + backupTZ = fCalendar->getTimeZone().clone(); + fCalendar->setTimeZone(cal.getTimeZone()); + workCal = fCalendar; + } + for (int32_t i=0; igetTimeZone()); + cal.setTime(workCal->getTime(status), status); + } + + // Restore the original time zone if required + if (backupTZ != NULL) { + fCalendar->adoptTimeZone(backupTZ); + } + // If any Calendar calls failed, we pretend that we // couldn't parse the string, when in reality this isn't quite accurate-- // we did parse it; the Calendar calls just failed. if (U_FAILURE(status)) { parsePos.setErrorIndex(pos); - parsePos.setIndex(start); + parsePos.setIndex(start); } } diff --git a/icu4c/source/i18n/unicode/datefmt.h b/icu4c/source/i18n/unicode/datefmt.h index 5b91ac1f08..e4d5f99a0b 100644 --- a/icu4c/source/i18n/unicode/datefmt.h +++ b/icu4c/source/i18n/unicode/datefmt.h @@ -1,6 +1,6 @@ /* ******************************************************************************** - * Copyright (C) 1997-2008, International Business Machines + * Copyright (C) 1997-2009, International Business Machines * Corporation and others. All Rights Reserved. ******************************************************************************** * @@ -245,7 +245,11 @@ public: * occurence of the timezone pattern character 'z'. * * @param cal Calendar set to the date and time to be formatted - * into a date/time string. + * into a date/time string. When the calendar type is + * different from the internal calendar held by this + * DateFormat instance, the date and the time zone will + * be inherited from the input calendar, but other calendar + * field values will be calculated by the internal calendar. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param fieldPosition On input: an alignment field, if desired (see examples above) @@ -342,7 +346,12 @@ public: * * @param text The date/time string to be parsed * @param cal a Calendar set to the date and time to be formatted - * into a date/time string. + * into a date/time string. When the calendar type + * is different from the internal calendar held by this + * DateFormat instance, calendar field values will be + * parsed based on the internal calendar, then the result + * (time in milliseconds and time zone) will be set in + * this calendar. * @param pos On input, the position at which to start parsing; on * output, the position at which parsing terminated, or the * start position if the parse failed. diff --git a/icu4c/source/test/intltest/dtfmttst.cpp b/icu4c/source/test/intltest/dtfmttst.cpp index 67ed619eb7..3564264f78 100644 --- a/icu4c/source/test/intltest/dtfmttst.cpp +++ b/icu4c/source/test/intltest/dtfmttst.cpp @@ -1,6 +1,6 @@ /******************************************************************** * COPYRIGHT: - * Copyright (c) 1997-2008, International Business Machines Corporation and + * Copyright (c) 1997-2009, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -74,9 +74,10 @@ void DateFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &nam TESTCASE(34,TestRelativeClone); TESTCASE(35,TestHostClone); TESTCASE(36,TestTimeZoneDisplayName); + TESTCASE(37,TestRoundtripWithCalendar); /* - TESTCASE(37,TestRelativeError); - TESTCASE(38,TestRelativeOther); + TESTCASE(38,TestRelativeError); + TESTCASE(39,TestRelativeOther); */ default: name = ""; break; } @@ -2898,6 +2899,128 @@ void DateFormatTest::TestTimeZoneDisplayName() delete cal; } +void DateFormatTest::TestRoundtripWithCalendar(void) { + UErrorCode status = U_ZERO_ERROR; + + TimeZone *tz = TimeZone::createTimeZone("Europe/Paris"); + TimeZone *gmt = TimeZone::createTimeZone("Etc/GMT"); + + Calendar *calendars[] = { + Calendar::createInstance(*tz, Locale("und@calendar=gregorian"), status), + Calendar::createInstance(*tz, Locale("und@calendar=buddhist"), status), +// Calendar::createInstance(*tz, Locale("und@calendar=hebrew"), status), + Calendar::createInstance(*tz, Locale("und@calendar=islamic"), status), + Calendar::createInstance(*tz, Locale("und@calendar=japanese"), status), + NULL + }; + if (U_FAILURE(status)) { + errln("Failed to initialize calendars"); + for (int i = 0; calendars[i] != NULL; i++) { + delete calendars[i]; + } + return; + } + + //FIXME The formatters commented out below are currently failing because of + // the calendar calculation problem reported by #6691 + + // The order of test formatters must match the order of calendars above. + DateFormat *formatters[] = { + DateFormat::createDateTimeInstance(DateFormat::kFull, DateFormat::kFull, Locale("en_US")), //calendar=gregorian + DateFormat::createDateTimeInstance(DateFormat::kFull, DateFormat::kFull, Locale("th_TH")), //calendar=buddhist +// DateFormat::createDateTimeInstance(DateFormat::kFull, DateFormat::kFull, Locale("he_IL@calendar=hebrew")), + DateFormat::createDateTimeInstance(DateFormat::kFull, DateFormat::kFull, Locale("ar_EG@calendar=islamic")), +// DateFormat::createDateTimeInstance(DateFormat::kFull, DateFormat::kFull, Locale("ja_JP@calendar=japanese")), + NULL + }; + + UDate d = Calendar::getNow(); + UnicodeString buf; + FieldPosition fpos; + ParsePosition ppos; + + for (int i = 0; formatters[i] != NULL; i++) { + buf.remove(); + fpos.setBeginIndex(0); + fpos.setEndIndex(0); + calendars[i]->setTime(d, status); + + // Normal case output - the given calendar matches the calendar + // used by the formatter + formatters[i]->format(*calendars[i], buf, fpos); + UnicodeString refStr(buf); + + for (int j = 0; calendars[j] != NULL; j++) { + if (j == i) { + continue; + } + buf.remove(); + fpos.setBeginIndex(0); + fpos.setEndIndex(0); + calendars[j]->setTime(d, status); + + // Even the different calendar type is specified, + // we should get the same result. + formatters[i]->format(*calendars[j], buf, fpos); + if (refStr != buf) { + errln((UnicodeString)"FAIL: Different format result with a different calendar for the same time -" + + "\n Reference calendar type=" + calendars[i]->getType() + + "\n Another calendar type=" + calendars[j]->getType() + + "\n Expected result=" + refStr + + "\n Actual result=" + buf); + } + } + + calendars[i]->setTimeZone(*gmt); + calendars[i]->clear(); + ppos.setErrorIndex(-1); + ppos.setIndex(0); + + // Normal case parse result - the given calendar matches the calendar + // used by the formatter + formatters[i]->parse(refStr, *calendars[i], ppos); + + for (int j = 0; calendars[j] != NULL; j++) { + if (j == i) { + continue; + } + calendars[j]->setTimeZone(*gmt); + calendars[j]->clear(); + ppos.setErrorIndex(-1); + ppos.setIndex(0); + + // Even the different calendar type is specified, + // we should get the same time and time zone. + formatters[i]->parse(refStr, *calendars[j], ppos); + if (calendars[i]->getTime(status) != calendars[j]->getTime(status) + || calendars[i]->getTimeZone() != calendars[j]->getTimeZone()) { + UnicodeString tzid; + errln((UnicodeString)"FAIL: Different parse result with a different calendar for the same string -" + + "\n Reference calendar type=" + calendars[i]->getType() + + "\n Another calendar type=" + calendars[j]->getType() + + "\n Date string=" + refStr + + "\n Expected time=" + calendars[i]->getTime(status) + + "\n Expected time zone=" + calendars[i]->getTimeZone().getID(tzid) + + "\n Actual time=" + calendars[j]->getTime(status) + + "\n Actual time zone=" + calendars[j]->getTimeZone().getID(tzid)); + } + } + if (U_FAILURE(status)) { + errln((UnicodeString)"FAIL: " + u_errorName(status)); + break; + } + } + + delete tz; + delete gmt; + for (int i = 0; calendars[i] != NULL; i++) { + delete calendars[i]; + } + for (int i = 0; formatters[i] != NULL; i++) { + delete formatters[i]; + } +} + /* void DateFormatTest::TestRelativeError(void) { diff --git a/icu4c/source/test/intltest/dtfmttst.h b/icu4c/source/test/intltest/dtfmttst.h index 091c459786..f39a277167 100644 --- a/icu4c/source/test/intltest/dtfmttst.h +++ b/icu4c/source/test/intltest/dtfmttst.h @@ -1,6 +1,6 @@ /******************************************************************** * COPYRIGHT: - * Copyright (c) 1997-2007, International Business Machines Corporation and + * Copyright (c) 1997-2009, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -185,12 +185,15 @@ public: void TestHostClone(void); + void TestTimeZoneDisplayName(void); + + void TestRoundtripWithCalendar(void); + public: /*** * Test Relative Dates */ void TestRelative(void); - void TestTimeZoneDisplayName(void); /* void TestRelativeError(void); void TestRelativeOther(void); */