diff --git a/icu4c/source/i18n/calendar.cpp b/icu4c/source/i18n/calendar.cpp index 10d6be35f32..fb8f481e248 100644 --- a/icu4c/source/i18n/calendar.cpp +++ b/icu4c/source/i18n/calendar.cpp @@ -31,6 +31,10 @@ #if !UCONFIG_NO_FORMATTING #include "unicode/gregocal.h" +#include "unicode/basictz.h" +#include "unicode/simpletz.h" +#include "unicode/rbtz.h" +#include "unicode/vtzone.h" #include "gregoimp.h" #include "buddhcal.h" #include "taiwncal.h" @@ -51,6 +55,7 @@ #include "uresimp.h" #include "ustrenum.h" #include "uassert.h" +#include "olsontz.h" #if !UCONFIG_NO_SERVICE static icu::ICULocaleService* gService = NULL; @@ -665,7 +670,9 @@ fAreFieldsVirtuallySet(FALSE), fNextStamp((int32_t)kMinimumUserStamp), fTime(0), fLenient(TRUE), -fZone(0) +fZone(0), +fRepeatedWallTime(UCAL_WALLTIME_LAST), +fSkippedWallTime(UCAL_WALLTIME_LAST) { clear(); fZone = TimeZone::createDefault(); @@ -686,7 +693,9 @@ fAreFieldsVirtuallySet(FALSE), fNextStamp((int32_t)kMinimumUserStamp), fTime(0), fLenient(TRUE), -fZone(0) +fZone(0), +fRepeatedWallTime(UCAL_WALLTIME_LAST), +fSkippedWallTime(UCAL_WALLTIME_LAST) { if(zone == 0) { #if defined (U_DEBUG_CAL) @@ -714,7 +723,9 @@ fAreFieldsVirtuallySet(FALSE), fNextStamp((int32_t)kMinimumUserStamp), fTime(0), fLenient(TRUE), -fZone(0) +fZone(0), +fRepeatedWallTime(UCAL_WALLTIME_LAST), +fSkippedWallTime(UCAL_WALLTIME_LAST) { clear(); fZone = zone.clone(); @@ -755,6 +766,8 @@ Calendar::operator=(const Calendar &right) fAreFieldsSet = right.fAreFieldsSet; fAreFieldsVirtuallySet = right.fAreFieldsVirtuallySet; fLenient = right.fLenient; + fRepeatedWallTime = right.fRepeatedWallTime; + fSkippedWallTime = right.fSkippedWallTime; if (fZone != NULL) { delete fZone; } @@ -936,6 +949,8 @@ Calendar::isEquivalentTo(const Calendar& other) const { return typeid(*this) == typeid(other) && fLenient == other.fLenient && + fRepeatedWallTime == other.fRepeatedWallTime && + fSkippedWallTime == other.fSkippedWallTime && fFirstDayOfWeek == other.fFirstDayOfWeek && fMinimalDaysInFirstWeek == other.fMinimalDaysInFirstWeek && fWeekendOnset == other.fWeekendOnset && @@ -2094,6 +2109,40 @@ Calendar::isLenient() const // ------------------------------------- +void +Calendar::setRepeatedWallTimeOption(UCalendarWallTimeOption option) +{ + if (option == UCAL_WALLTIME_LAST || option == UCAL_WALLTIME_FIRST) { + fRepeatedWallTime = option; + } +} + +// ------------------------------------- + +UCalendarWallTimeOption +Calendar::getRepeatedWallTimeOption(void) const +{ + return fRepeatedWallTime; +} + +// ------------------------------------- + +void +Calendar::setSkippedWallTimeOption(UCalendarWallTimeOption option) +{ + fSkippedWallTime = option; +} + +// ------------------------------------- + +UCalendarWallTimeOption +Calendar::getSkippedWallTimeOption(void) const +{ + return fSkippedWallTime; +} + +// ------------------------------------- + void Calendar::setFirstDayOfWeek(UCalendarDaysOfWeek value) { @@ -2589,33 +2638,97 @@ void Calendar::computeTime(UErrorCode& status) { // is legacy behavior. Without this, clear(MONTH) has no effect, // since the internally set JULIAN_DAY is used. if (fStamp[UCAL_MILLISECONDS_IN_DAY] >= ((int32_t)kMinimumUserStamp) && - newestStamp(UCAL_AM_PM, UCAL_MILLISECOND, kUnset) <= fStamp[UCAL_MILLISECONDS_IN_DAY]) { - millisInDay = internalGet(UCAL_MILLISECONDS_IN_DAY); - } else { - millisInDay = computeMillisInDay(); - } + newestStamp(UCAL_AM_PM, UCAL_MILLISECOND, kUnset) <= fStamp[UCAL_MILLISECONDS_IN_DAY]) { + millisInDay = internalGet(UCAL_MILLISECONDS_IN_DAY); + } else { + millisInDay = computeMillisInDay(); + } + UDate t = 0; + if (fStamp[UCAL_ZONE_OFFSET] >= ((int32_t)kMinimumUserStamp) || fStamp[UCAL_DST_OFFSET] >= ((int32_t)kMinimumUserStamp)) { + t = millis + millisInDay - (internalGet(UCAL_ZONE_OFFSET) + internalGet(UCAL_DST_OFFSET)); + } else { // Compute the time zone offset and DST offset. There are two potential // ambiguities here. We'll assume a 2:00 am (wall time) switchover time // for discussion purposes here. - // 1. The transition into DST. Here, a designated time of 2:00 am - 2:59 am - // can be in standard or in DST depending. However, 2:00 am is an invalid - // representation (the representation jumps from 1:59:59 am Std to 3:00:00 am DST). - // We assume standard time, that is, 2:30 am is interpreted as 3:30 am DST. - // 2. The transition out of DST. Here, a designated time of 1:00 am - 1:59 am - // can be in standard or DST. Both are valid representations (the rep - // jumps from 1:59:59 DST to 1:00:00 Std). - // Again, we assume standard time, that is, 1:30 am is interpreted as 1:30 am Std. + // + // 1. The positive offset change such as transition into DST. + // Here, a designated time of 2:00 am - 2:59 am does not actually exist. + // For this case, skippedWallTime option specifies the behavior. + // For example, 2:30 am is interpreted as; + // - WALLTIME_LAST(default): 3:30 am (DST) (interpreting 2:30 am as 31 minutes after 1:59 am (STD)) + // - WALLTIME_FIRST: 1:30 am (STD) (interpreting 2:30 am as 30 minutes before 3:00 am (DST)) + // - WALLTIME_NEXT_VALID: 3:00 am (DST) (next valid time after 2:30 am on a wall clock) + // 2. The negative offset change such as transition out of DST. + // Here, a designated time of 1:00 am - 1:59 am can be in standard or DST. Both are valid + // representations (the rep jumps from 1:59:59 DST to 1:00:00 Std). + // For this case, repeatedWallTime option specifies the behavior. + // For example, 1:30 am is interpreted as; + // - WALLTIME_LAST(default): 1:30 am (STD) - latter occurrence + // - WALLTIME_FIRST: 1:30 am (DST) - former occurrence + // + // In addition to above, when calendar is strict (not default), wall time falls into + // the skipped time range will be processed as an error case. + // + // These special cases are mostly handled in #computeZoneOffset(long), except WALLTIME_NEXT_VALID + // at positive offset change. The protected method computeZoneOffset(long) is exposed to Calendar + // subclass implementations and marked as @stable. Strictly speaking, WALLTIME_NEXT_VALID + // should be also handled in the same place, but we cannot change the code flow without deprecating + // the protected method. + // // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET // or DST_OFFSET fields; then we use those fields. - if (fStamp[UCAL_ZONE_OFFSET] >= ((int32_t)kMinimumUserStamp) || - fStamp[UCAL_DST_OFFSET] >= ((int32_t)kMinimumUserStamp)) { - millisInDay -= internalGet(UCAL_ZONE_OFFSET) + internalGet(UCAL_DST_OFFSET); - } else { - millisInDay -= computeZoneOffset(millis, millisInDay,status); - } - internalSetTime(millis + millisInDay); + if (!isLenient() || fSkippedWallTime == UCAL_WALLTIME_NEXT_VALID) { + // When strict, invalidate a wall time falls into a skipped wall time range. + // When lenient and skipped wall time option is WALLTIME_NEXT_VALID, + // the result time will be adjusted to the next valid time (on wall clock). + int32_t zoneOffset = computeZoneOffset(millis, millisInDay, status); + UDate tmpTime = millis + millisInDay - zoneOffset; + + int32_t raw, dst; + fZone->getOffset(tmpTime, FALSE, raw, dst, status); + + if (U_SUCCESS(status)) { + // zoneOffset != (raw + dst) only when the given wall time fall into + // a skipped wall time range caused by positive zone offset transition. + if (zoneOffset != (raw + dst)) { + if (!isLenient()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + } else { + U_ASSERT(fSkippedWallTime == UCAL_WALLTIME_NEXT_VALID); + // Adjust time to the next valid wall clock time. + // At this point, tmpTime is on or after the zone offset transition causing + // the skipped time range. + + BasicTimeZone *btz = getBasicTimeZone(); + if (btz) { + TimeZoneTransition transition; + UBool hasTransition = btz->getPreviousTransition(tmpTime, TRUE, transition); + if (hasTransition) { + t = transition.getTime(); + } else { + // Could not find any transitions. + // Note: This should never happen. + status = U_INTERNAL_PROGRAM_ERROR; + } + } else { + // If not BasicTimeZone, return unsupported error for now. + // TODO: We may support non-BasicTimeZone in future. + status = U_UNSUPPORTED_ERROR; + } + } + } else { + t = tmpTime; + } + } + } else { + t = millis + millisInDay - computeZoneOffset(millis, millisInDay, status); + } + } + if (U_SUCCESS(status)) { + internalSetTime(t); + } } /** @@ -2672,12 +2785,49 @@ int32_t Calendar::computeMillisInDay() { */ int32_t Calendar::computeZoneOffset(double millis, int32_t millisInDay, UErrorCode &ec) { int32_t rawOffset, dstOffset; - getTimeZone().getOffset(millis+millisInDay, TRUE, rawOffset, dstOffset, ec); + UDate wall = millis + millisInDay; + BasicTimeZone* btz = getBasicTimeZone(); + if (btz) { + int duplicatedTimeOpt = (fRepeatedWallTime == UCAL_WALLTIME_FIRST) ? BasicTimeZone::kFormer : BasicTimeZone::kLatter; + int nonExistingTimeOpt = (fSkippedWallTime == UCAL_WALLTIME_FIRST) ? BasicTimeZone::kLatter : BasicTimeZone::kFormer; + btz->getOffsetFromLocal(wall, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, ec); + } else { + const TimeZone& tz = getTimeZone(); + // By default, TimeZone::getOffset behaves UCAL_WALLTIME_LAST for both. + tz.getOffset(wall, TRUE, rawOffset, dstOffset, ec); + + UBool sawRecentNegativeShift = FALSE; + if (fRepeatedWallTime == UCAL_WALLTIME_FIRST) { + // Check if the given wall time falls into repeated time range + UDate tgmt = wall - (rawOffset + dstOffset); + + // Any negative zone transition within last 6 hours? + // Note: The maximum historic negative zone transition is -3 hours in the tz database. + // 6 hour window would be sufficient for this purpose. + int32_t tmpRaw, tmpDst; + tz.getOffset(tgmt - 6*60*60*1000, FALSE, tmpRaw, tmpDst, ec); + int32_t offsetDelta = (rawOffset + dstOffset) - (tmpRaw + tmpDst); + + U_ASSERT(offsetDelta < -6*60*60*1000); + if (offsetDelta < 0) { + sawRecentNegativeShift = TRUE; + // Negative shift within last 6 hours. When UCAL_WALLTIME_FIRST is used and the given wall time falls + // into the repeated time range, use offsets before the transition. + // Note: If it does not fall into the repeated time range, offsets remain unchanged below. + tz.getOffset(wall + offsetDelta, TRUE, rawOffset, dstOffset, ec); + } + } + if (!sawRecentNegativeShift && fSkippedWallTime == UCAL_WALLTIME_FIRST) { + // When skipped wall time option is WALLTIME_FIRST, + // recalculate offsets from the resolved time (non-wall). + // When the given wall time falls into skipped wall time, + // the offsets will be based on the zone offsets AFTER + // the transition (which means, earliest possibe interpretation). + UDate tgmt = wall - (rawOffset + dstOffset); + tz.getOffset(tgmt, FALSE, rawOffset, dstOffset, ec); + } + } return rawOffset + dstOffset; - // Note: Because we pass in wall millisInDay, rather than - // standard millisInDay, we interpret "1:00 am" on the day - // of cessation of DST as "1:00 am Std" (assuming the time - // of cessation is 2:00 am). } int32_t Calendar::computeJulianDay() @@ -3420,6 +3570,17 @@ Calendar::internalSet(EDateFields field, int32_t value) internalSet((UCalendarDateFields) field, value); } +BasicTimeZone* +Calendar::getBasicTimeZone(void) const { + if (dynamic_cast(fZone) != NULL + || dynamic_cast(fZone) != NULL + || dynamic_cast(fZone) != NULL + || dynamic_cast(fZone) != NULL) { + return (BasicTimeZone*)fZone; + } + return NULL; +} + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/ucal.cpp b/icu4c/source/i18n/ucal.cpp index 52677852806..2a8db94b99a 100644 --- a/icu4c/source/i18n/ucal.cpp +++ b/icu4c/source/i18n/ucal.cpp @@ -308,6 +308,12 @@ ucal_getAttribute( const UCalendar* cal, case UCAL_MINIMAL_DAYS_IN_FIRST_WEEK: return ((Calendar*)cal)->getMinimalDaysInFirstWeek(); + case UCAL_REPEATED_WALL_TIME: + return ((Calendar*)cal)->getRepeatedWallTimeOption(); + + case UCAL_SKIPPED_WALL_TIME: + return ((Calendar*)cal)->getSkippedWallTimeOption(); + default: break; } @@ -332,6 +338,14 @@ ucal_setAttribute( UCalendar* cal, case UCAL_MINIMAL_DAYS_IN_FIRST_WEEK: ((Calendar*)cal)->setMinimalDaysInFirstWeek((uint8_t)newValue); break; + + case UCAL_REPEATED_WALL_TIME: + ((Calendar*)cal)->setRepeatedWallTimeOption((UCalendarWallTimeOption)newValue); + break; + + case UCAL_SKIPPED_WALL_TIME: + ((Calendar*)cal)->setSkippedWallTimeOption((UCalendarWallTimeOption)newValue); + break; } } diff --git a/icu4c/source/i18n/unicode/calendar.h b/icu4c/source/i18n/unicode/calendar.h index 248eaf9f71d..32c2bb4f2f3 100644 --- a/icu4c/source/i18n/unicode/calendar.h +++ b/icu4c/source/i18n/unicode/calendar.h @@ -48,6 +48,7 @@ class ICUServiceFactory; */ typedef int32_t UFieldResolutionTable[12][8]; +class BasicTimeZone; /** * Calendar is an abstract base class for converting between * a UDate object and a set of integer fields such as @@ -821,6 +822,74 @@ public: */ UBool isLenient(void) const; + /** + * Sets the behavior for handling wall time repeating multiple times + * at negative time zone offset transitions. For example, 1:30 AM on + * November 6, 2011 in US Eastern time (Ameirca/New_York) occurs twice; + * 1:30 AM EDT, then 1:30 AM EST one hour later. When UCAL_WALLTIME_FIRST + * is used, the wall time 1:30AM in this example will be interpreted as 1:30 AM EDT + * (first occurrence). When UCAL_WALLTIME_LAST is used, it will be + * interpreted as 1:30 AM EST (last occurrence). The default value is + * UCAL_WALLTIME_LAST. + *

+ * Note:When UCAL_WALLTIME_NEXT_VALID is not a valid + * option for this. When the argument is neither UCAL_WALLTIME_FIRST + * nor UCAL_WALLTIME_LAST, this method has no effect and will keep + * the current setting. + * + * @param option the behavior for handling repeating wall time, either + * UCAL_WALLTIME_FIRST or UCAL_WALLTIME_LAST. + * @see #getRepeatedWallTimeOption + * @draft ICU 49 + */ + void setRepeatedWallTimeOption(UCalendarWallTimeOption option); + + /** + * Gets the behavior for handling wall time repeating multiple times + * at negative time zone offset transitions. + * + * @return the behavior for handling repeating wall time, either + * UCAL_WALLTIME_FIRST or UCAL_WALLTIME_LAST. + * @see #setRepeatedWallTimeOption + * @draft ICU 49 + */ + UCalendarWallTimeOption getRepeatedWallTimeOption(void) const; + + /** + * Sets the behavior for handling skipped wall time at positive time zone offset + * transitions. For example, 2:30 AM on March 13, 2011 in US Eastern time (America/New_York) + * does not exist because the wall time jump from 1:59 AM EST to 3:00 AM EDT. When + * UCAL_WALLTIME_FIRST is used, 2:30 AM is interpreted as 30 minutes before 3:00 AM + * EDT, therefore, it will be resolved as 1:30 AM EST. When UCAL_WALLTIME_LAST + * is used, 2:30 AM is interpreted as 31 minutes after 1:59 AM EST, therefore, it will be + * resolved as 3:30 AM EDT. When UCAL_WALLTIME_NEXT_VALID is used, 2:30 AM will + * be resolved as next valid wall time, that is 3:00 AM EDT. The default value is + * UCAL_WALLTIME_LAST. + *

+ * Note:This option is effective only when this calendar is lenient. + * When the calendar is strict, such non-existing wall time will cause an error. + * + * @param option the behavior for handling skipped wall time at positive time zone + * offset transitions, one of UCAL_WALLTIME_FIRST, UCAL_WALLTIME_LAST and + * UCAL_WALLTIME_NEXT_VALID. + * @see #getSkippedWallTimeOption + * + * @draft ICU 49 + */ + void setSkippedWallTimeOption(UCalendarWallTimeOption option); + + /** + * Gets the behavior for handling skipped wall time at positive time zone offset + * transitions. + * + * @return the behavior for handling skipped wall time, one of + * UCAL_WALLTIME_FIRST, UCAL_WALLTIME_LAST + * and UCAL_WALLTIME_NEXT_VALID. + * @see #setSkippedWallTimeOption + * @draft ICU 49 + */ + UCalendarWallTimeOption getSkippedWallTimeOption(void) const; + #ifndef U_HIDE_DEPRECATED_API /** * Sets what the first day of the week is; e.g., Sunday in US, Monday in France. @@ -2010,6 +2079,18 @@ private: */ TimeZone* fZone; + /** + * Option for rpeated wall time + * @see #setRepeatedWallTimeOption + */ + UCalendarWallTimeOption fRepeatedWallTime; + + /** + * Option for skipped wall time + * @see #setSkippedWallTimeOption + */ + UCalendarWallTimeOption fSkippedWallTime; + /** * Both firstDayOfWeek and minimalDaysInFirstWeek are locale-dependent. They are * used to figure out the week count for a specific date for a given locale. These @@ -2261,6 +2342,13 @@ private: */ const char* getLocaleID(ULocDataLocaleType type, UErrorCode &status) const; #endif /* U_HIDE_INTERNAL_API */ + +private: + /** + * Cast TimeZone used by this object to BasicTimeZone, or NULL if the TimeZone + * is not an instance of BasicTimeZone. + */ + BasicTimeZone* getBasicTimeZone() const; }; // ------------------------------------- diff --git a/icu4c/source/i18n/unicode/ucal.h b/icu4c/source/i18n/unicode/ucal.h index b822140d272..78191ed0782 100644 --- a/icu4c/source/i18n/unicode/ucal.h +++ b/icu4c/source/i18n/unicode/ucal.h @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 1996-2011, International Business Machines Corporation and + * Copyright (C) 1996-2012, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ @@ -851,24 +851,75 @@ ucal_getGregorianChange(const UCalendar *cal, UErrorCode *pErrorCode); * @stable ICU 2.0 */ enum UCalendarAttribute { - /** Lenient parsing */ + /** + * Lenient parsing + * @stable ICU 2.0 + */ UCAL_LENIENT, - /** First day of week */ + /** + * First day of week + * @stable ICU 2.0 + */ UCAL_FIRST_DAY_OF_WEEK, - /** Minimum number of days in first week */ - UCAL_MINIMAL_DAYS_IN_FIRST_WEEK + /** + * Minimum number of days in first week + * @stable ICU 2.0 + */ + UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, + /** + * The behavior for handling wall time repeating multiple times + * at negative time zone offset transitions + * @draft ICU 49 + */ + UCAL_REPEATED_WALL_TIME, + /** + * The behavior for handling skipped wall time at positive time + * zone offset transitions. + * @draft ICU 49 + */ + UCAL_SKIPPED_WALL_TIME }; /** @stable ICU 2.0 */ typedef enum UCalendarAttribute UCalendarAttribute; +/** + * Options for handling ambiguous wall time at time zone + * offset transitions. + * @draft ICU 49 + */ +enum UCalendarWallTimeOption { + /** + * An ambiguous wall time to be interpreted as the latest. + * This option is valid for UCAL_REPEATED_WALL_TIME and + * UCAL_SKIPPED_WALL_TIME. + * @draft ICU 49 + */ + UCAL_WALLTIME_LAST, + /** + * An ambiguous wall time to be interpreted as the earliest. + * This option is valid for UCAL_REPEATED_WALL_TIME and + * UCAL_SKIPPED_WALL_TIME. + * @draft ICU 49 + */ + UCAL_WALLTIME_FIRST, + /** + * An ambiguous wall time to be interpreted as the next valid + * wall time. This option is valid for UCAL_SKIPPED_WALL_TIME. + * @draft ICU 49 + */ + UCAL_WALLTIME_NEXT_VALID +}; +/** @draft ICU 49 */ +typedef enum UCalendarWallTimeOption UCalendarWallTimeOption; + /** * Get a numeric attribute associated with a UCalendar. * Numeric attributes include the first day of the week, or the minimal numbers * of days in the first week of the month. * @param cal The UCalendar to query. * @param attr The desired attribute; one of UCAL_LENIENT, UCAL_FIRST_DAY_OF_WEEK, - * or UCAL_MINIMAL_DAYS_IN_FIRST_WEEK + * UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, UCAL_REPEATED_WALL_TIME or UCAL_SKIPPED_WALL_TIME * @return The value of attr. * @see ucal_setAttribute * @stable ICU 2.0 @@ -883,7 +934,7 @@ ucal_getAttribute(const UCalendar* cal, * of days in the first week of the month. * @param cal The UCalendar to set. * @param attr The desired attribute; one of UCAL_LENIENT, UCAL_FIRST_DAY_OF_WEEK, - * or UCAL_MINIMAL_DAYS_IN_FIRST_WEEK + * UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, UCAL_REPEATED_WALL_TIME or UCAL_SKIPPED_WALL_TIME * @param newValue The new value of attr. * @see ucal_getAttribute * @stable ICU 2.0 diff --git a/icu4c/source/test/cintltst/ccaltst.c b/icu4c/source/test/cintltst/ccaltst.c index b86b4ccedbb..c5e306176cf 100644 --- a/icu4c/source/test/cintltst/ccaltst.c +++ b/icu4c/source/test/cintltst/ccaltst.c @@ -1,5 +1,5 @@ /******************************************************************** - * Copyright (c) 1997-2011, International Business Machines + * Copyright (c) 1997-2012, International Business Machines * Corporation and others. All Rights Reserved. ******************************************************************** * @@ -49,6 +49,7 @@ void addCalTest(TestNode** root) addTest(root, &TestGetKeywordValuesForLocale, "tsformat/ccaltst/TestGetKeywordValuesForLocale"); addTest(root, &TestWeekend, "tsformat/ccaltst/TestWeekend"); addTest(root, &TestFieldDifference, "tsformat/ccaltst/TestFieldDifference"); + addTest(root, &TestAmbiguousWallTime, "tsformat/ccaltst/TestAmbiguousWallTime"); } /* "GMT" */ @@ -1783,4 +1784,104 @@ void TestFieldDifference() { } } +void TestAmbiguousWallTime() { + UErrorCode status = U_ZERO_ERROR; + UChar tzID[32]; + UCalendar* ucal; + UDate t, expected; + + u_uastrcpy(tzID, "America/New_York"); + ucal = ucal_open(tzID, -1, NULL, UCAL_DEFAULT, &status); + if (U_FAILURE(status)) { + log_err("FAIL: Failed to create a calendar"); + return; + } + + if (ucal_getAttribute(ucal, UCAL_REPEATED_WALL_TIME) != UCAL_WALLTIME_LAST) { + log_err("FAIL: Default UCAL_REPEATED_WALL_TIME value is not UCAL_WALLTIME_LAST"); + } + + if (ucal_getAttribute(ucal, UCAL_SKIPPED_WALL_TIME) != UCAL_WALLTIME_LAST) { + log_err("FAIL: Default UCAL_SKIPPED_WALL_TIME value is not UCAL_WALLTIME_LAST"); + } + + /* UCAL_WALLTIME_FIRST on US fall transition */ + ucal_setAttribute(ucal, UCAL_REPEATED_WALL_TIME, UCAL_WALLTIME_FIRST); + ucal_clear(ucal); + ucal_setDateTime(ucal, 2011, 11-1, 6, 1, 30, 0, &status); + t = ucal_getMillis(ucal, &status); + expected = 1320557400000.0; /* 2011-11-06T05:30:00Z */ + if (U_FAILURE(status)) { + log_err("FAIL: Calculating time 2011-11-06 01:30:00 with UCAL_WALLTIME_FIRST - %s\n", u_errorName(status)); + status = U_ZERO_ERROR; + } else if (t != expected) { + log_err("FAIL: 2011-11-06 01:30:00 with UCAL_WALLTIME_FIRST - got: %f, expected: %f\n", t, expected); + } + + /* UCAL_WALLTIME_LAST on US fall transition */ + ucal_setAttribute(ucal, UCAL_REPEATED_WALL_TIME, UCAL_WALLTIME_LAST); + ucal_clear(ucal); + ucal_setDateTime(ucal, 2011, 11-1, 6, 1, 30, 0, &status); + t = ucal_getMillis(ucal, &status); + expected = 1320561000000.0; /* 2011-11-06T06:30:00Z */ + if (U_FAILURE(status)) { + log_err("FAIL: Calculating time 2011-11-06 01:30:00 with UCAL_WALLTIME_LAST - %s\n", u_errorName(status)); + status = U_ZERO_ERROR; + } else if (t != expected) { + log_err("FAIL: 2011-11-06 01:30:00 with UCAL_WALLTIME_LAST - got: %f, expected: %f\n", t, expected); + } + + /* UCAL_WALLTIME_FIRST on US spring transition */ + ucal_setAttribute(ucal, UCAL_SKIPPED_WALL_TIME, UCAL_WALLTIME_FIRST); + ucal_clear(ucal); + ucal_setDateTime(ucal, 2011, 3-1, 13, 2, 30, 0, &status); + t = ucal_getMillis(ucal, &status); + expected = 1299997800000.0; /* 2011-03-13T06:30:00Z */ + if (U_FAILURE(status)) { + log_err("FAIL: Calculating time 2011-03-13 02:30:00 with UCAL_WALLTIME_FIRST - %s\n", u_errorName(status)); + status = U_ZERO_ERROR; + } else if (t != expected) { + log_err("FAIL: 2011-03-13 02:30:00 with UCAL_WALLTIME_FIRST - got: %f, expected: %f\n", t, expected); + } + + /* UCAL_WALLTIME_LAST on US spring transition */ + ucal_setAttribute(ucal, UCAL_SKIPPED_WALL_TIME, UCAL_WALLTIME_LAST); + ucal_clear(ucal); + ucal_setDateTime(ucal, 2011, 3-1, 13, 2, 30, 0, &status); + t = ucal_getMillis(ucal, &status); + expected = 1300001400000.0; /* 2011-03-13T07:30:00Z */ + if (U_FAILURE(status)) { + log_err("FAIL: Calculating time 2011-03-13 02:30:00 with UCAL_WALLTIME_LAST - %s\n", u_errorName(status)); + status = U_ZERO_ERROR; + } else if (t != expected) { + log_err("FAIL: 2011-03-13 02:30:00 with UCAL_WALLTIME_LAST - got: %f, expected: %f\n", t, expected); + } + + /* UCAL_WALLTIME_NEXT_VALID on US spring transition */ + ucal_setAttribute(ucal, UCAL_SKIPPED_WALL_TIME, UCAL_WALLTIME_NEXT_VALID); + ucal_clear(ucal); + ucal_setDateTime(ucal, 2011, 3-1, 13, 2, 30, 0, &status); + t = ucal_getMillis(ucal, &status); + expected = 1299999600000.0; /* 2011-03-13T07:00:00Z */ + if (U_FAILURE(status)) { + log_err("FAIL: Calculating time 2011-03-13 02:30:00 with UCAL_WALLTIME_NEXT_VALID - %s\n", u_errorName(status)); + status = U_ZERO_ERROR; + } else if (t != expected) { + log_err("FAIL: 2011-03-13 02:30:00 with UCAL_WALLTIME_NEXT_VALID - got: %f, expected: %f\n", t, expected); + } + + /* non-lenient on US spring transition */ + ucal_setAttribute(ucal, UCAL_LENIENT, 0); + ucal_clear(ucal); + ucal_setDateTime(ucal, 2011, 3-1, 13, 2, 30, 0, &status); + t = ucal_getMillis(ucal, &status); + if (U_SUCCESS(status)) { + /* must return error */ + log_err("FAIL: Non-lenient did not fail with 2011-03-13 02:30:00\n"); + status = U_ZERO_ERROR; + } + + ucal_close(ucal); +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/cintltst/ccaltst.h b/icu4c/source/test/cintltst/ccaltst.h index 54f1a43d127..95a9f4cfd32 100644 --- a/icu4c/source/test/cintltst/ccaltst.h +++ b/icu4c/source/test/cintltst/ccaltst.h @@ -1,6 +1,6 @@ /******************************************************************** * COPYRIGHT: - * Copyright (c) 1997-2010, International Business Machines Corporation and + * Copyright (c) 1997-2012, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ /******************************************************************************** @@ -64,6 +64,10 @@ * Test weekend-related APIs */ static void TestWeekend(void); + /** + * Test ambiguous wall time + */ + static void TestAmbiguousWallTime(void); /*Internal functions used*/ /** diff --git a/icu4c/source/test/intltest/caltest.cpp b/icu4c/source/test/intltest/caltest.cpp index 53c7a6ec5a9..308101ff609 100644 --- a/icu4c/source/test/intltest/caltest.cpp +++ b/icu4c/source/test/intltest/caltest.cpp @@ -1,6 +1,6 @@ /************************************************************************ * COPYRIGHT: - * Copyright (c) 1997-2011, International Business Machines Corporation + * Copyright (c) 1997-2012, International Business Machines Corporation * and others. All Rights Reserved. ************************************************************************/ @@ -244,6 +244,27 @@ void CalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &name, TestISO8601(); } break; + case 27: + name = "TestAmbiguousWallTimeAPIs"; + if(exec) { + logln("TestAmbiguousWallTimeAPIs---"); logln(""); + TestAmbiguousWallTimeAPIs(); + } + break; + case 28: + name = "TestRepeatedWallTime"; + if(exec) { + logln("TestRepeatedWallTime---"); logln(""); + TestRepeatedWallTime(); + } + break; + case 29: + name = "TestSkippedWallTime"; + if(exec) { + logln("TestSkippedWallTime---"); logln(""); + TestSkippedWallTime(); + } + break; default: name = ""; break; } } @@ -2257,6 +2278,377 @@ void CalendarTest::TestISO8601() { } +void +CalendarTest::TestAmbiguousWallTimeAPIs(void) { + UErrorCode status = U_ZERO_ERROR; + Calendar* cal = Calendar::createInstance(status); + if (U_FAILURE(status)) { + errln("Fail: Error creating a calendar instance."); + return; + } + + if (cal->getRepeatedWallTimeOption() != UCAL_WALLTIME_LAST) { + errln("Fail: Default repeted time option is not UCAL_WALLTIME_LAST"); + } + if (cal->getSkippedWallTimeOption() != UCAL_WALLTIME_LAST) { + errln("Fail: Default skipped time option is not UCAL_WALLTIME_LAST"); + } + + Calendar* cal2 = cal->clone(); + + if (*cal != *cal2) { + errln("Fail: Cloned calendar != the original"); + } + if (!cal->equals(*cal2, status)) { + errln("Fail: The time of cloned calendar is not equal to the original"); + } else if (U_FAILURE(status)) { + errln("Fail: Error equals"); + } + status = U_ZERO_ERROR; + + cal2->setRepeatedWallTimeOption(UCAL_WALLTIME_FIRST); + cal2->setSkippedWallTimeOption(UCAL_WALLTIME_FIRST); + + if (*cal == *cal2) { + errln("Fail: Cloned and modified calendar == the original"); + } + if (!cal->equals(*cal2, status)) { + errln("Fail: The time of cloned calendar is not equal to the original after changing wall time options"); + } else if (U_FAILURE(status)) { + errln("Fail: Error equals after changing wall time options"); + } + status = U_ZERO_ERROR; + + if (cal2->getRepeatedWallTimeOption() != UCAL_WALLTIME_FIRST) { + errln("Fail: Repeted time option is not UCAL_WALLTIME_FIRST"); + } + if (cal2->getSkippedWallTimeOption() != UCAL_WALLTIME_FIRST) { + errln("Fail: Skipped time option is not UCAL_WALLTIME_FIRST"); + } + + cal2->setRepeatedWallTimeOption(UCAL_WALLTIME_NEXT_VALID); + if (cal2->getRepeatedWallTimeOption() != UCAL_WALLTIME_FIRST) { + errln("Fail: Repeated wall time option was updated other than UCAL_WALLTIME_FIRST"); + } + + delete cal; + delete cal2; +} + +class CalFields { +public: + CalFields(int32_t year, int32_t month, int32_t day, int32_t hour, int32_t min, int32_t sec); + CalFields(const Calendar& cal, UErrorCode& status); + void setTo(Calendar& cal) const; + char* toString(char* buf, int32_t len) const; + UBool operator==(const CalFields& rhs) const; + UBool operator!=(const CalFields& rhs) const; + +private: + int32_t year; + int32_t month; + int32_t day; + int32_t hour; + int32_t min; + int32_t sec; +}; + +CalFields::CalFields(int32_t year, int32_t month, int32_t day, int32_t hour, int32_t min, int32_t sec) + : year(year), month(month), day(day), hour(hour), min(min), sec(sec) { +} + +CalFields::CalFields(const Calendar& cal, UErrorCode& status) { + year = cal.get(UCAL_YEAR, status); + month = cal.get(UCAL_MONTH, status) + 1; + day = cal.get(UCAL_DAY_OF_MONTH, status); + hour = cal.get(UCAL_HOUR_OF_DAY, status); + min = cal.get(UCAL_MINUTE, status); + sec = cal.get(UCAL_SECOND, status); +} + +void +CalFields::setTo(Calendar& cal) const { + cal.clear(); + cal.set(year, month - 1, day, hour, min, sec); +} + +char* +CalFields::toString(char* buf, int32_t len) const { + char local[32]; + sprintf(local, "%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec); + uprv_strncpy(buf, local, len - 1); + buf[len - 1] = 0; + return buf; +} + +UBool +CalFields::operator==(const CalFields& rhs) const { + return year == rhs.year + && month == rhs.month + && day == rhs.day + && hour == rhs.hour + && min == rhs.min + && sec == rhs.sec; +} + +UBool +CalFields::operator!=(const CalFields& rhs) const { + return !(*this == rhs); +} + +typedef struct { + const char* tzid; + const CalFields in; + const CalFields expLastGMT; + const CalFields expFirstGMT; +} RepeatedWallTimeTestData; + +static const RepeatedWallTimeTestData RPDATA[] = +{ + // Time zone Input wall time WALLTIME_LAST in GMT WALLTIME_FIRST in GMT + {"America/New_York", CalFields(2011,11,6,0,59,59), CalFields(2011,11,6,4,59,59), CalFields(2011,11,6,4,59,59)}, + {"America/New_York", CalFields(2011,11,6,1,0,0), CalFields(2011,11,6,6,0,0), CalFields(2011,11,6,5,0,0)}, + {"America/New_York", CalFields(2011,11,6,1,0,1), CalFields(2011,11,6,6,0,1), CalFields(2011,11,6,5,0,1)}, + {"America/New_York", CalFields(2011,11,6,1,30,0), CalFields(2011,11,6,6,30,0), CalFields(2011,11,6,5,30,0)}, + {"America/New_York", CalFields(2011,11,6,1,59,59), CalFields(2011,11,6,6,59,59), CalFields(2011,11,6,5,59,59)}, + {"America/New_York", CalFields(2011,11,6,2,0,0), CalFields(2011,11,6,7,0,0), CalFields(2011,11,6,7,0,0)}, + {"America/New_York", CalFields(2011,11,6,2,0,1), CalFields(2011,11,6,7,0,1), CalFields(2011,11,6,7,0,1)}, + + {"Australia/Lord_Howe", CalFields(2011,4,3,1,29,59), CalFields(2011,4,2,14,29,59), CalFields(2011,4,2,14,29,59)}, + {"Australia/Lord_Howe", CalFields(2011,4,3,1,30,0), CalFields(2011,4,2,15,0,0), CalFields(2011,4,2,14,30,0)}, + {"Australia/Lord_Howe", CalFields(2011,4,3,1,45,0), CalFields(2011,4,2,15,15,0), CalFields(2011,4,2,14,45,0)}, + {"Australia/Lord_Howe", CalFields(2011,4,3,1,59,59), CalFields(2011,4,2,15,29,59), CalFields(2011,4,2,14,59,59)}, + {"Australia/Lord_Howe", CalFields(2011,4,3,2,0,0), CalFields(2011,4,2,15,30,0), CalFields(2011,4,2,15,30,0)}, + {"Australia/Lord_Howe", CalFields(2011,4,3,2,0,1), CalFields(2011,4,2,15,30,1), CalFields(2011,4,2,15,30,1)}, + + {NULL, CalFields(0,0,0,0,0,0), CalFields(0,0,0,0,0,0), CalFields(0,0,0,0,0,0)} +}; + +void CalendarTest::TestRepeatedWallTime(void) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar calGMT((const TimeZone&)*TimeZone::getGMT(), status); + GregorianCalendar calDefault(status); + GregorianCalendar calLast(status); + GregorianCalendar calFirst(status); + + if (U_FAILURE(status)) { + errln("Fail: Failed to create a calendar object."); + return; + } + + calLast.setRepeatedWallTimeOption(UCAL_WALLTIME_LAST); + calFirst.setRepeatedWallTimeOption(UCAL_WALLTIME_FIRST); + + for (int32_t i = 0; RPDATA[i].tzid != NULL; i++) { + char buf[32]; + TimeZone *tz = TimeZone::createTimeZone(RPDATA[i].tzid); + + // UCAL_WALLTIME_LAST + status = U_ZERO_ERROR; + calLast.setTimeZone(*tz); + RPDATA[i].in.setTo(calLast); + calGMT.setTime(calLast.getTime(status), status); + CalFields outLastGMT(calGMT, status); + if (U_FAILURE(status)) { + errln(UnicodeString("Fail: Failed to get/set time calLast/calGMT (UCAL_WALLTIME_LAST) - ") + + RPDATA[i].in.toString(buf, sizeof(buf)) + "[" + RPDATA[i].tzid + "]"); + } else { + if (outLastGMT != RPDATA[i].expLastGMT) { + errln(UnicodeString("Fail: UCAL_WALLTIME_LAST ") + RPDATA[i].in.toString(buf, sizeof(buf)) + "[" + RPDATA[i].tzid + "] is parsed as " + + outLastGMT.toString(buf, sizeof(buf)) + "[GMT]. Expected: " + RPDATA[i].expLastGMT.toString(buf, sizeof(buf)) + "[GMT]"); + } + } + + // default + status = U_ZERO_ERROR; + calDefault.setTimeZone(*tz); + RPDATA[i].in.setTo(calDefault); + calGMT.setTime(calDefault.getTime(status), status); + CalFields outDefGMT(calGMT, status); + if (U_FAILURE(status)) { + errln(UnicodeString("Fail: Failed to get/set time calLast/calGMT (default) - ") + + RPDATA[i].in.toString(buf, sizeof(buf)) + "[" + RPDATA[i].tzid + "]"); + } else { + if (outDefGMT != RPDATA[i].expLastGMT) { + errln(UnicodeString("Fail: (default) ") + RPDATA[i].in.toString(buf, sizeof(buf)) + "[" + RPDATA[i].tzid + "] is parsed as " + + outDefGMT.toString(buf, sizeof(buf)) + "[GMT]. Expected: " + RPDATA[i].expLastGMT.toString(buf, sizeof(buf)) + "[GMT]"); + } + } + + // UCAL_WALLTIME_FIRST + status = U_ZERO_ERROR; + calFirst.setTimeZone(*tz); + RPDATA[i].in.setTo(calFirst); + calGMT.setTime(calFirst.getTime(status), status); + CalFields outFirstGMT(calGMT, status); + if (U_FAILURE(status)) { + errln(UnicodeString("Fail: Failed to get/set time calLast/calGMT (UCAL_WALLTIME_FIRST) - ") + + RPDATA[i].in.toString(buf, sizeof(buf)) + "[" + RPDATA[i].tzid + "]"); + } else { + if (outFirstGMT != RPDATA[i].expFirstGMT) { + errln(UnicodeString("Fail: UCAL_WALLTIME_FIRST ") + RPDATA[i].in.toString(buf, sizeof(buf)) + "[" + RPDATA[i].tzid + "] is parsed as " + + outFirstGMT.toString(buf, sizeof(buf)) + "[GMT]. Expected: " + RPDATA[i].expFirstGMT.toString(buf, sizeof(buf)) + "[GMT]"); + } + } + delete tz; + } +} + +typedef struct { + const char* tzid; + const CalFields in; + UBool isValid; + const CalFields expLastGMT; + const CalFields expFirstGMT; + const CalFields expNextAvailGMT; +} SkippedWallTimeTestData; + +static SkippedWallTimeTestData SKDATA[] = +{ + // Time zone Input wall time valid? WALLTIME_LAST in GMT WALLTIME_FIRST in GMT WALLTIME_NEXT_VALID in GMT + {"America/New_York", CalFields(2011,3,13,1,59,59), TRUE, CalFields(2011,3,13,6,59,59), CalFields(2011,3,13,6,59,59), CalFields(2011,3,13,6,59,59)}, + {"America/New_York", CalFields(2011,3,13,2,0,0), FALSE, CalFields(2011,3,13,7,0,0), CalFields(2011,3,13,6,0,0), CalFields(2011,3,13,7,0,0)}, + {"America/New_York", CalFields(2011,3,13,2,1,0), FALSE, CalFields(2011,3,13,7,1,0), CalFields(2011,3,13,6,1,0), CalFields(2011,3,13,7,0,0)}, + {"America/New_York", CalFields(2011,3,13,2,30,0), FALSE, CalFields(2011,3,13,7,30,0), CalFields(2011,3,13,6,30,0), CalFields(2011,3,13,7,0,0)}, + {"America/New_York", CalFields(2011,3,13,2,59,59), FALSE, CalFields(2011,3,13,7,59,59), CalFields(2011,3,13,6,59,59), CalFields(2011,3,13,7,0,0)}, + {"America/New_York", CalFields(2011,3,13,3,0,0), TRUE, CalFields(2011,3,13,7,0,0), CalFields(2011,3,13,7,0,0), CalFields(2011,3,13,7,0,0)}, + + {"Pacific/Apia", CalFields(2011,12,29,23,59,59), TRUE, CalFields(2011,12,30,9,59,59), CalFields(2011,12,30,9,59,59), CalFields(2011,12,30,9,59,59)}, + {"Pacific/Apia", CalFields(2011,12,30,0,0,0), FALSE, CalFields(2011,12,30,10,0,0), CalFields(2011,12,29,10,0,0), CalFields(2011,12,30,10,0,0)}, + {"Pacific/Apia", CalFields(2011,12,30,12,0,0), FALSE, CalFields(2011,12,30,22,0,0), CalFields(2011,12,29,22,0,0), CalFields(2011,12,30,10,0,0)}, + {"Pacific/Apia", CalFields(2011,12,30,23,59,59), FALSE, CalFields(2011,12,31,9,59,59), CalFields(2011,12,30,9,59,59), CalFields(2011,12,30,10,0,0)}, + {"Pacific/Apia", CalFields(2011,12,31,0,0,0), TRUE, CalFields(2011,12,30,10,0,0), CalFields(2011,12,30,10,0,0), CalFields(2011,12,30,10,0,0)}, + + {NULL, CalFields(0,0,0,0,0,0), TRUE, CalFields(0,0,0,0,0,0), CalFields(0,0,0,0,0,0), CalFields(0,0,0,0,0,0)} +}; + + +void CalendarTest::TestSkippedWallTime(void) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar calGMT((const TimeZone&)*TimeZone::getGMT(), status); + GregorianCalendar calDefault(status); + GregorianCalendar calLast(status); + GregorianCalendar calFirst(status); + GregorianCalendar calNextAvail(status); + + if (U_FAILURE(status)) { + errln("Fail: Failed to create a calendar object."); + return; + } + + calLast.setSkippedWallTimeOption(UCAL_WALLTIME_LAST); + calFirst.setSkippedWallTimeOption(UCAL_WALLTIME_FIRST); + calNextAvail.setSkippedWallTimeOption(UCAL_WALLTIME_NEXT_VALID); + + for (int32_t i = 0; SKDATA[i].tzid != NULL; i++) { + UDate d; + char buf[32]; + TimeZone *tz = TimeZone::createTimeZone(SKDATA[i].tzid); + + for (int32_t j = 0; j < 2; j++) { + UBool bLenient = (j == 0); + + // UCAL_WALLTIME_LAST + status = U_ZERO_ERROR; + calLast.setLenient(bLenient); + calLast.setTimeZone(*tz); + SKDATA[i].in.setTo(calLast); + d = calLast.getTime(status); + if (bLenient || SKDATA[i].isValid) { + calGMT.setTime(d, status); + CalFields outLastGMT(calGMT, status); + if (U_FAILURE(status)) { + errln(UnicodeString("Fail: Failed to get/set time calLast/calGMT (UCAL_WALLTIME_LAST) - ") + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } else { + if (outLastGMT != SKDATA[i].expLastGMT) { + errln(UnicodeString("Fail: UCAL_WALLTIME_LAST ") + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "] is parsed as " + + outLastGMT.toString(buf, sizeof(buf)) + "[GMT]. Expected: " + SKDATA[i].expLastGMT.toString(buf, sizeof(buf)) + "[GMT]"); + } + } + } else if (U_SUCCESS(status)) { + // strict, invalid wall time - must report an error + errln(UnicodeString("Fail: An error expected (UCAL_WALLTIME_LAST)") + + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } + + // default + status = U_ZERO_ERROR; + calDefault.setLenient(bLenient); + calDefault.setTimeZone(*tz); + SKDATA[i].in.setTo(calDefault); + d = calDefault.getTime(status); + if (bLenient || SKDATA[i].isValid) { + calGMT.setTime(d, status); + CalFields outDefGMT(calGMT, status); + if (U_FAILURE(status)) { + errln(UnicodeString("Fail: Failed to get/set time calDefault/calGMT (default) - ") + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } else { + if (outDefGMT != SKDATA[i].expLastGMT) { + errln(UnicodeString("Fail: (default) ") + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "] is parsed as " + + outDefGMT.toString(buf, sizeof(buf)) + "[GMT]. Expected: " + SKDATA[i].expLastGMT.toString(buf, sizeof(buf)) + "[GMT]"); + } + } + } else if (U_SUCCESS(status)) { + // strict, invalid wall time - must report an error + errln(UnicodeString("Fail: An error expected (default)") + + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } + + // UCAL_WALLTIME_FIRST + status = U_ZERO_ERROR; + calFirst.setLenient(bLenient); + calFirst.setTimeZone(*tz); + SKDATA[i].in.setTo(calFirst); + d = calFirst.getTime(status); + if (bLenient || SKDATA[i].isValid) { + calGMT.setTime(d, status); + CalFields outFirstGMT(calGMT, status); + if (U_FAILURE(status)) { + errln(UnicodeString("Fail: Failed to get/set time calFirst/calGMT (UCAL_WALLTIME_FIRST) - ") + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } else { + if (outFirstGMT != SKDATA[i].expFirstGMT) { + errln(UnicodeString("Fail: UCAL_WALLTIME_FIRST ") + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "] is parsed as " + + outFirstGMT.toString(buf, sizeof(buf)) + "[GMT]. Expected: " + SKDATA[i].expFirstGMT.toString(buf, sizeof(buf)) + "[GMT]"); + } + } + } else if (U_SUCCESS(status)) { + // strict, invalid wall time - must report an error + errln(UnicodeString("Fail: An error expected (UCAL_WALLTIME_FIRST)") + + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } + + // UCAL_WALLTIME_NEXT_VALID + status = U_ZERO_ERROR; + calNextAvail.setLenient(bLenient); + calNextAvail.setTimeZone(*tz); + SKDATA[i].in.setTo(calNextAvail); + d = calNextAvail.getTime(status); + if (bLenient || SKDATA[i].isValid) { + calGMT.setTime(d, status); + CalFields outNextAvailGMT(calGMT, status); + if (U_FAILURE(status)) { + errln(UnicodeString("Fail: Failed to get/set time calNextAvail/calGMT (UCAL_WALLTIME_NEXT_VALID) - ") + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } else { + if (outNextAvailGMT != SKDATA[i].expNextAvailGMT) { + errln(UnicodeString("Fail: UCAL_WALLTIME_NEXT_VALID ") + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "] is parsed as " + + outNextAvailGMT.toString(buf, sizeof(buf)) + "[GMT]. Expected: " + SKDATA[i].expNextAvailGMT.toString(buf, sizeof(buf)) + "[GMT]"); + } + } + } else if (U_SUCCESS(status)) { + // strict, invalid wall time - must report an error + errln(UnicodeString("Fail: An error expected (UCAL_WALLTIME_NEXT_VALID)") + + + SKDATA[i].in.toString(buf, sizeof(buf)) + "[" + SKDATA[i].tzid + "]"); + } + } + + delete tz; + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu4c/source/test/intltest/caltest.h b/icu4c/source/test/intltest/caltest.h index c4717607d30..c65ec725d3c 100644 --- a/icu4c/source/test/intltest/caltest.h +++ b/icu4c/source/test/intltest/caltest.h @@ -1,5 +1,5 @@ /*********************************************************************** - * Copyright (c) 1997-2011, International Business Machines Corporation + * Copyright (c) 1997-2012, International Business Machines Corporation * and others. All Rights Reserved. ***********************************************************************/ @@ -228,6 +228,13 @@ public: // package * Test the ISO8601 calendar type */ void TestISO8601(void); + + /** + * Test cases for [set|get][Repeated|Skipped]WallTimeOption + */ + void TestAmbiguousWallTimeAPIs(void); + void TestRepeatedWallTime(void); + void TestSkippedWallTime(void); }; #endif /* #if !UCONFIG_NO_FORMATTING */