]> granicus.if.org Git - icu/commitdiff
ICU-22023 Fix Calendar::get() return out of bound value and SimpleDateTime::format...
authorFrank Yung-Fong Tang <ftang@chromium.org>
Wed, 18 May 2022 20:19:49 +0000 (20:19 +0000)
committerFrank Yung-Fong Tang <ftang@google.com>
Thu, 19 May 2022 20:45:59 +0000 (13:45 -0700)
See #2086

14 files changed:
icu4c/source/i18n/calendar.cpp
icu4c/source/i18n/cecal.cpp
icu4c/source/i18n/chnsecal.cpp
icu4c/source/i18n/gregocal.cpp
icu4c/source/i18n/gregoimp.cpp
icu4c/source/i18n/gregoimp.h
icu4c/source/i18n/indiancal.cpp
icu4c/source/i18n/persncal.cpp
icu4c/source/i18n/simpletz.cpp
icu4c/source/i18n/timezone.cpp
icu4c/source/test/intltest/calregts.cpp
icu4c/source/test/intltest/calregts.h
icu4c/source/test/intltest/dtfmttst.cpp
icu4c/source/test/intltest/dtfmttst.h

index 233400be70d59f00a278972a737b69f4d179b449..0f5bf52219e2a0c134fadfd95f1f0c918764b9b8 100644 (file)
@@ -639,8 +639,8 @@ static const int32_t kCalendarLimits[UCAL_FIELD_COUNT][4] = {
     {           0,            0,            59,            59  }, // MINUTE
     {           0,            0,            59,            59  }, // SECOND
     {           0,            0,           999,           999  }, // MILLISECOND
-    {-12*kOneHour, -12*kOneHour,   12*kOneHour,   15*kOneHour  }, // ZONE_OFFSET
-    {           0,            0,    1*kOneHour,    1*kOneHour  }, // DST_OFFSET
+    {-12*kOneHour, -12*kOneHour,   12*kOneHour,   30*kOneHour  }, // ZONE_OFFSET
+    { -1*kOneHour,  -1*kOneHour,    2*kOneHour,    2*kOneHour  }, // DST_OFFSET
     {/*N/A*/-1,       /*N/A*/-1,     /*N/A*/-1,       /*N/A*/-1}, // YEAR_WOY
     {           1,            1,             7,             7  }, // DOW_LOCAL
     {/*N/A*/-1,       /*N/A*/-1,     /*N/A*/-1,       /*N/A*/-1}, // EXTENDED_YEAR
@@ -1537,7 +1537,8 @@ void Calendar::computeFields(UErrorCode &ec)
     // JULIAN_DAY field and also removes some inelegant code. - Liu
     // 11/6/00
 
-    int32_t days =  (int32_t)ClockMath::floorDivide(localMillis, (double)kOneDay);
+    int32_t millisInDay;
+    int32_t days = ClockMath::floorDivide(localMillis, kOneDay, &millisInDay);
 
     internalSet(UCAL_JULIAN_DAY,days + kEpochStartAsJulianDay);
 
@@ -1561,19 +1562,47 @@ void Calendar::computeFields(UErrorCode &ec)
     // Compute time-related fields.  These are independent of the date and
     // of the subclass algorithm.  They depend only on the local zone
     // wall milliseconds in day.
-    int32_t millisInDay =  (int32_t) (localMillis - (days * kOneDay));
+
     fFields[UCAL_MILLISECONDS_IN_DAY] = millisInDay;
+    U_ASSERT(getMinimum(UCAL_MILLISECONDS_IN_DAY) <=
+             fFields[UCAL_MILLISECONDS_IN_DAY]);
+    U_ASSERT(fFields[UCAL_MILLISECONDS_IN_DAY] <=
+             getMaximum(UCAL_MILLISECONDS_IN_DAY));
+
     fFields[UCAL_MILLISECOND] = millisInDay % 1000;
+    U_ASSERT(getMinimum(UCAL_MILLISECOND) <= fFields[UCAL_MILLISECOND]);
+    U_ASSERT(fFields[UCAL_MILLISECOND] <= getMaximum(UCAL_MILLISECOND));
+
     millisInDay /= 1000;
     fFields[UCAL_SECOND] = millisInDay % 60;
+    U_ASSERT(getMinimum(UCAL_SECOND) <= fFields[UCAL_SECOND]);
+    U_ASSERT(fFields[UCAL_SECOND] <= getMaximum(UCAL_SECOND));
+
     millisInDay /= 60;
     fFields[UCAL_MINUTE] = millisInDay % 60;
+    U_ASSERT(getMinimum(UCAL_MINUTE) <= fFields[UCAL_MINUTE]);
+    U_ASSERT(fFields[UCAL_MINUTE] <= getMaximum(UCAL_MINUTE));
+
     millisInDay /= 60;
     fFields[UCAL_HOUR_OF_DAY] = millisInDay;
+    U_ASSERT(getMinimum(UCAL_HOUR_OF_DAY) <= fFields[UCAL_HOUR_OF_DAY]);
+    U_ASSERT(fFields[UCAL_HOUR_OF_DAY] <= getMaximum(UCAL_HOUR_OF_DAY));
+
     fFields[UCAL_AM_PM] = millisInDay / 12; // Assume AM == 0
+    U_ASSERT(getMinimum(UCAL_AM_PM) <= fFields[UCAL_AM_PM]);
+    U_ASSERT(fFields[UCAL_AM_PM] <= getMaximum(UCAL_AM_PM));
+
     fFields[UCAL_HOUR] = millisInDay % 12;
+    U_ASSERT(getMinimum(UCAL_HOUR) <= fFields[UCAL_HOUR]);
+    U_ASSERT(fFields[UCAL_HOUR] <= getMaximum(UCAL_HOUR));
+
     fFields[UCAL_ZONE_OFFSET] = rawOffset;
+    U_ASSERT(getMinimum(UCAL_ZONE_OFFSET) <= fFields[UCAL_ZONE_OFFSET]);
+    U_ASSERT(fFields[UCAL_ZONE_OFFSET] <= getMaximum(UCAL_ZONE_OFFSET));
+
     fFields[UCAL_DST_OFFSET] = dstOffset;
+    U_ASSERT(getMinimum(UCAL_DST_OFFSET) <= fFields[UCAL_DST_OFFSET]);
+    U_ASSERT(fFields[UCAL_DST_OFFSET] <= getMaximum(UCAL_DST_OFFSET));
 }
 
 uint8_t Calendar::julianDayToDayOfWeek(double julian)
@@ -1699,11 +1728,20 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
     }
     fFields[UCAL_WEEK_OF_YEAR] = woy;
     fFields[UCAL_YEAR_WOY] = yearOfWeekOfYear;
+    // min/max of years are not constrains for caller, so not assert here.
     // WEEK_OF_YEAR end
 
     int32_t dayOfMonth = fFields[UCAL_DAY_OF_MONTH];
     fFields[UCAL_WEEK_OF_MONTH] = weekNumber(dayOfMonth, dayOfWeek);
+    U_ASSERT(getMinimum(UCAL_WEEK_OF_MONTH) <= fFields[UCAL_WEEK_OF_MONTH]);
+    U_ASSERT(fFields[UCAL_WEEK_OF_MONTH] <= getMaximum(UCAL_WEEK_OF_MONTH));
+
     fFields[UCAL_DAY_OF_WEEK_IN_MONTH] = (dayOfMonth-1) / 7 + 1;
+    U_ASSERT(getMinimum(UCAL_DAY_OF_WEEK_IN_MONTH) <=
+             fFields[UCAL_DAY_OF_WEEK_IN_MONTH]);
+    U_ASSERT(fFields[UCAL_DAY_OF_WEEK_IN_MONTH] <=
+             getMaximum(UCAL_DAY_OF_WEEK_IN_MONTH));
+
 #if defined (U_DEBUG_CAL)
     if(fFields[UCAL_DAY_OF_WEEK_IN_MONTH]==0) fprintf(stderr, "%s:%d: DOWIM %d on %g\n",
         __FILE__, __LINE__,fFields[UCAL_DAY_OF_WEEK_IN_MONTH], fTime);
index cb97c40a3c7498ce013e11589ef1db674f9e4760..cd9871e4d954acdf3ecc9ae93935195b327bd7b4 100644 (file)
@@ -135,7 +135,7 @@ CECalendar::jdToCE(int32_t julianDay, int32_t jdEpochOffset, int32_t& year, int3
     int32_t c4; // number of 4 year cycle (1461 days)
     int32_t r4; // remainder of 4 year cycle, always positive
 
-    c4 = ClockMath::floorDivide(julianDay - jdEpochOffset, 1461, r4);
+    c4 = ClockMath::floorDivide(julianDay - jdEpochOffset, 1461, &r4);
 
     year = 4 * c4 + (r4/365 - r4/1460); // 4 * <number of 4year cycle> + <years within the last cycle>
 
index a58272e8a56a6941ad44e5a3b1c3dd34afbaa8e4..678de09611cdc2c132be86dc32ae7ed516564932 100644 (file)
@@ -328,7 +328,7 @@ int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
     // modify the extended year value accordingly.
     if (month < 0 || month > 11) {
         double m = month;
-        eyear += (int32_t)ClockMath::floorDivide(m, 12.0, m);
+        eyear += (int32_t)ClockMath::floorDivide(m, 12.0, &m);
         month = (int32_t)m;
     }
 
@@ -727,7 +727,7 @@ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t
 
         // 0->0,60  1->1,1  60->1,60  61->2,1  etc.
         int32_t yearOfCycle;
-        int32_t cycle = ClockMath::floorDivide(cycle_year - 1, 60, yearOfCycle);
+        int32_t cycle = ClockMath::floorDivide(cycle_year - 1, 60, &yearOfCycle);
         internalSet(UCAL_ERA, cycle + 1);
         internalSet(UCAL_YEAR, yearOfCycle + 1);
 
index 6e26ed83c6281693442a4aa0de6c2d84bc8f1514..886c30b74df8bf06fa404049d907fd06cbf75b41 100644 (file)
@@ -388,7 +388,7 @@ void GregorianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& statu
         // The Julian epoch day (not the same as Julian Day)
         // is zero on Saturday December 30, 0 (Gregorian).
         int32_t julianEpochDay = julianDay - (kJan1_1JulianDay - 2);
-               eyear = (int32_t) ClockMath::floorDivide((4.0*julianEpochDay) + 1464.0, (int32_t) 1461, unusedRemainder);
+               eyear = (int32_t) ClockMath::floorDivide((4.0*julianEpochDay) + 1464.0, (int32_t) 1461, &unusedRemainder);
 
         // Compute the Julian calendar day number for January 1, eyear
         int32_t january1 = 365*(eyear-1) + ClockMath::floorDivide(eyear-1, (int32_t)4);
@@ -537,7 +537,7 @@ int32_t GregorianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month,
     // If the month is out of range, adjust it into range, and
     // modify the extended year value accordingly.
     if (month < 0 || month > 11) {
-        eyear += ClockMath::floorDivide(month, 12, month);
+        eyear += ClockMath::floorDivide(month, 12, &month);
     }
 
     UBool isLeap = eyear%4 == 0;
@@ -580,7 +580,7 @@ int32_t GregorianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t mo
     // If the month is out of range, adjust it into range, and
     // modify the extended year value accordingly.
     if (month < 0 || month > 11) {
-        extendedYear += ClockMath::floorDivide(month, 12, month);
+        extendedYear += ClockMath::floorDivide(month, 12, &month);
     }
 
     return isLeapYear(extendedYear) ? kLeapMonthLength[month] : kMonthLength[month];
index 101a8b8b396be94bd75f4d380b3431b5a739dc78..f862cd1d831424788de40aed32139c9e3bc2e01f 100644 (file)
@@ -33,28 +33,33 @@ int64_t ClockMath::floorDivide(int64_t numerator, int64_t denominator) {
 }
 
 int32_t ClockMath::floorDivide(double numerator, int32_t denominator,
-                          int32_t& remainder) {
-    double quotient;
-    quotient = uprv_floor(numerator / denominator);
-    remainder = (int32_t) (numerator - (quotient * denominator));
+                          int32_t* remainder) {
+    // For an integer n and representable ⌊x/n⌋, ⌊RN(x/n)⌋=⌊x/n⌋, where RN is
+    // rounding to nearest.
+    double quotient = uprv_floor(numerator / denominator);
+    // For doubles x and n, where n is an integer and ⌊x+n⌋ < 2³¹, the
+    // expression `(int32_t) (x + n)` evaluated with rounding to nearest
+    // differs from ⌊x+n⌋ if 0 < ⌈x⌉−x ≪ x+n, as `x + n` is rounded up to
+    // n+⌈x⌉ = ⌊x+n⌋ + 1.  Rewriting it as ⌊x⌋+n makes the addition exact.
+    *remainder = (int32_t) (uprv_floor(numerator) - (quotient * denominator));
     return (int32_t) quotient;
 }
 
 double ClockMath::floorDivide(double dividend, double divisor,
-                         double& remainder) {
+                         double* remainder) {
     // Only designed to work for positive divisors
     U_ASSERT(divisor > 0);
     double quotient = floorDivide(dividend, divisor);
-    remainder = dividend - (quotient * divisor);
+    *remainder = dividend - (quotient * divisor);
     // N.B. For certain large dividends, on certain platforms, there
     // is a bug such that the quotient is off by one.  If you doubt
     // this to be true, set a breakpoint below and run cintltst.
-    if (remainder < 0 || remainder >= divisor) {
+    if (*remainder < 0 || *remainder >= divisor) {
         // E.g. 6.7317038241449352e+022 / 86400000.0 is wrong on my
         // machine (too high by one).  4.1792057231752762e+024 /
         // 86400000.0 is wrong the other way (too low).
         double q = quotient;
-        quotient += (remainder < 0) ? -1 : +1;
+        quotient += (*remainder < 0) ? -1 : +1;
         if (q == quotient) {
             // For quotients > ~2^53, we won't be able to add or
             // subtract one, since the LSB of the mantissa will be >
@@ -65,12 +70,12 @@ double ClockMath::floorDivide(double dividend, double divisor,
             // values give back an approximate answer rather than
             // crashing.  For example, UDate values above a ~10^25
             // might all have a time of midnight.
-            remainder = 0;
+            *remainder = 0;
         } else {
-            remainder = dividend - (quotient * divisor);
+            *remainder = dividend - (quotient * divisor);
         }
     }
-    U_ASSERT(0 <= remainder && remainder < divisor);
+    U_ASSERT(0 <= *remainder && *remainder < divisor);
     return quotient;
 }
 
@@ -106,10 +111,10 @@ void Grego::dayToFields(double day, int32_t& year, int32_t& month,
     // representation.  We use 400-year, 100-year, and 4-year cycles.
     // For example, the 4-year cycle has 4 years + 1 leap day; giving
     // 1461 == 365*4 + 1 days.
-    int32_t n400 = ClockMath::floorDivide(day, 146097, doy); // 400-year cycle length
-    int32_t n100 = ClockMath::floorDivide(doy, 36524, doy); // 100-year cycle length
-    int32_t n4   = ClockMath::floorDivide(doy, 1461, doy); // 4-year cycle length
-    int32_t n1   = ClockMath::floorDivide(doy, 365, doy);
+    int32_t n400 = ClockMath::floorDivide(day, 146097, &doy); // 400-year cycle length
+    int32_t n100 = ClockMath::floorDivide(doy, 36524, &doy); // 100-year cycle length
+    int32_t n4   = ClockMath::floorDivide(doy, 1461, &doy); // 4-year cycle length
+    int32_t n1   = ClockMath::floorDivide(doy, 365, &doy);
     year = 400*n400 + 100*n100 + 4*n4 + n1;
     if (n100 == 4 || n1 == 4) {
         doy = 365; // Dec 31 at end of 4- or 400-year cycle
@@ -137,14 +142,14 @@ void Grego::dayToFields(double day, int32_t& year, int32_t& month,
 void Grego::timeToFields(UDate time, int32_t& year, int32_t& month,
                         int32_t& dom, int32_t& dow, int32_t& doy, int32_t& mid) {
     double millisInDay;
-    double day = ClockMath::floorDivide((double)time, (double)U_MILLIS_PER_DAY, millisInDay);
+    double day = ClockMath::floorDivide((double)time, (double)U_MILLIS_PER_DAY, &millisInDay);
     mid = (int32_t)millisInDay;
     dayToFields(day, year, month, dom, dow, doy);
 }
 
 int32_t Grego::dayOfWeek(double day) {
     int32_t dow;
-    ClockMath::floorDivide(day + int{UCAL_THURSDAY}, 7, dow);
+    ClockMath::floorDivide(day + int{UCAL_THURSDAY}, 7, &dow);
     return (dow == 0) ? UCAL_SATURDAY : dow;
 }
 
index b1a5bc22c22d2827bacf55a7e682ad10997853d9..d65d6a4f88e928f9660ec5ac2d8cae02e10be02f 100644 (file)
@@ -78,7 +78,7 @@ class ClockMath {
      * @return the floor of the quotient
      */
     static int32_t floorDivide(double numerator, int32_t denominator,
-                               int32_t& remainder);
+                               int32_t* remainder);
 
     /**
      * For a positive divisor, return the quotient and remainder
@@ -91,7 +91,7 @@ class ClockMath {
      * Calling with a divisor <= 0 is disallowed.
      */
     static double floorDivide(double dividend, double divisor,
-                              double& remainder);
+                              double* remainder);
 };
 
 // Useful millisecond constants
index e879cdf183aa8be3baef4948f05eb93eaae77773..5755892d11e211b32432a6128105cd9d312f863d 100644 (file)
@@ -110,7 +110,7 @@ static UBool isGregorianLeap(int32_t year)
  */
 int32_t IndianCalendar::handleGetMonthLength(int32_t eyear, int32_t month) const {
    if (month < 0 || month > 11) {
-      eyear += ClockMath::floorDivide(month, 12, month);
+      eyear += ClockMath::floorDivide(month, 12, &month);
    }
 
    if (isGregorianLeap(eyear + INDIAN_ERA_START) && month == 0) {
@@ -210,7 +210,7 @@ int32_t IndianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UB
 
     // If the month is out of range, adjust it into range, and adjust the extended year accordingly
    if (month < 0 || month > 11) {
-      eyear += (int32_t)ClockMath::floorDivide(month, 12, month);
+      eyear += (int32_t)ClockMath::floorDivide(month, 12, &month);
    }
 
    if(month == 12){
index 873f4e5878427f38adcab420446a787b57723463..2a15cae04448146d3cc23b5890399c7d718932ef 100644 (file)
@@ -110,7 +110,7 @@ int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType li
 UBool PersianCalendar::isLeapYear(int32_t year)
 {
     int32_t remainder;
-    ClockMath::floorDivide(25 * year + 11, 33, remainder);
+    ClockMath::floorDivide(25 * year + 11, 33, &remainder);
     return (remainder < 8);
 }
     
@@ -147,7 +147,7 @@ int32_t PersianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t mont
     // If the month is out of range, adjust it into range, and
     // modify the extended year value accordingly.
     if (month < 0 || month > 11) {
-        extendedYear += ClockMath::floorDivide(month, 12, month);
+        extendedYear += ClockMath::floorDivide(month, 12, &month);
     }
 
     return isLeapYear(extendedYear) ? kPersianLeapMonthLength[month] : kPersianMonthLength[month];
@@ -169,7 +169,7 @@ int32_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
     // If the month is out of range, adjust it into range, and
     // modify the extended year value accordingly.
     if (month < 0 || month > 11) {
-        eyear += ClockMath::floorDivide(month, 12, month);
+        eyear += ClockMath::floorDivide(month, 12, &month);
     }
 
     int32_t julianDay = PERSIAN_EPOCH - 1 + 365 * (eyear - 1) + ClockMath::floorDivide(8 * eyear + 21, 33);
index d9b0cd8e1e629da471ceeab3c17314fa1dc07192..0036effcd84e32761420657a860e18dfb9e420f1 100644 (file)
@@ -518,9 +518,8 @@ SimpleTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingT
     }
 
     rawOffsetGMT = getRawOffset();
-    int32_t year, month, dom, dow;
-    double day = uprv_floor(date / U_MILLIS_PER_DAY);
-    int32_t millis = (int32_t) (date - day * U_MILLIS_PER_DAY);
+    int32_t year, month, dom, dow, millis;
+    int32_t day = ClockMath::floorDivide(date, U_MILLIS_PER_DAY, &millis);
 
     Grego::dayToFields(day, year, month, dom, dow);
 
@@ -549,8 +548,7 @@ SimpleTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingT
         }
     }
     if (recalc) {
-        day = uprv_floor(date / U_MILLIS_PER_DAY);
-        millis = (int32_t) (date - day * U_MILLIS_PER_DAY);
+        day = ClockMath::floorDivide(date, U_MILLIS_PER_DAY, &millis);
         Grego::dayToFields(day, year, month, dom, dow);
         savingsDST = getOffset(GregorianCalendar::AD, year, month, dom,
                           (uint8_t) dow, millis,
index a0bc7460160d34a1436946315447423d51384100..b5a1aec0c9a7829bb90e24e0e1b6c8353e76893a 100644 (file)
@@ -729,9 +729,8 @@ void TimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset,
     // (with 7 args) twice when local == true and DST is
     // detected in the initial call.
     for (int32_t pass=0; ; ++pass) {
-        int32_t year, month, dom, dow;
-        double day = uprv_floor(date / U_MILLIS_PER_DAY);
-        int32_t millis = (int32_t) (date - day * U_MILLIS_PER_DAY);
+        int32_t year, month, dom, dow, millis;
+        double day = ClockMath::floorDivide(date, U_MILLIS_PER_DAY, &millis);
 
         Grego::dayToFields(day, year, month, dom, dow);
 
index 1a5a972810c801ddacd1f17bcaef4628342c7487..930f5032969a8454d3c0a813873a780e4197d510 100644 (file)
@@ -97,6 +97,7 @@ CalendarRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* &
         CASE(53,TestIslamicCalOverflow);
         CASE(54,TestWeekOfYear13548);
         CASE(55,Test13745);
+        CASE(56,TestUTCWrongAMPM22023);
     default: name = ""; break;
     }
 }
@@ -3089,6 +3090,105 @@ void CalendarRegressionTest::TestIslamicCalOverflow(void) {
     }
 }
 
+void CalendarRegressionTest::VerifyGetStayInBound(double time) {
+    UErrorCode status = U_ZERO_ERROR;
+    LocalPointer<Calendar> utc(
+        Calendar::createInstance(TimeZone::createTimeZone(u"UTC"), status));
+    utc->setTime(time, status);
+    if (U_FAILURE(status)) {
+        errln("UTC setTime(%e, status)", time);
+    }
+
+    status = U_ZERO_ERROR;
+    LocalPointer<Calendar> gmt(Calendar::createInstance(
+        *TimeZone::getGMT(), status));
+    gmt->setTime(time, status);
+    if (U_FAILURE(status)) {
+        errln("UTC setTime(%e, status)", time);
+    }
+
+    status = U_ZERO_ERROR;
+    int32_t value = utc->get(UCAL_AM_PM, status);
+    if (U_FAILURE(status)) {
+        errln("UTC %e get(UCAL_AM_PM, status)", time);
+    }
+    if (value != UCAL_AM && value != UCAL_PM) {
+        errln("UTC %e UCAL_AM_PM should be either UCAL_AM | UCAL_PM  but is %d",
+              time, value);
+    }
+
+    status = U_ZERO_ERROR;
+    value = gmt->get(UCAL_AM_PM, status);
+    if (U_FAILURE(status)) {
+        errln("GMT %e get(UCAL_AM_PM, status)", time);
+    }
+    if (value != UCAL_AM && value != UCAL_PM) {
+        errln("GMT %e UCAL_AM_PM should be either UCAL_AM | UCAL_PM  but is %d",
+              time, value);
+    }
+
+    int32_t fields[] = {
+        UCAL_WEEK_OF_YEAR,
+        UCAL_YEAR_WOY,
+        UCAL_DAY_OF_MONTH,
+        UCAL_WEEK_OF_MONTH,
+        UCAL_DAY_OF_WEEK_IN_MONTH,
+        UCAL_MILLISECONDS_IN_DAY,
+        UCAL_MILLISECOND,
+        UCAL_SECOND,
+        UCAL_MINUTE,
+        UCAL_HOUR_OF_DAY,
+        UCAL_AM_PM,
+        UCAL_HOUR,
+        UCAL_ZONE_OFFSET,
+        UCAL_DST_OFFSET
+    };
+    for (auto& f : fields) {
+        UnicodeString info("Fields = ");
+        info += f;
+        status = U_ZERO_ERROR;
+        UCalendarDateFields field = static_cast<UCalendarDateFields>(f);
+        value = utc->get(field, status);
+        if (U_FAILURE(status)) {
+            errln("UTC %e get(%d)", time, field);
+        }
+        int32_t min = utc->getMinimum(field);
+        int32_t max = utc->getMaximum(field);
+        if (value < min) {
+            errln("UTC %e get(%d) < getMinimum(%d) : %d < %d", time, field,
+                  field, value, min);
+        }
+        if (max < value) {
+            errln("UTC %e getMaximum(%d) < get(%d) : %d < %d", time, field,
+                  field, max, value);
+        }
+
+        status = U_ZERO_ERROR;
+        value = gmt->get(field, status);
+        if (U_FAILURE(status)) {
+            errln("GMT %e get(%d)", time, field);
+        }
+        min = gmt->getMinimum(field);
+        max = gmt->getMaximum(field);
+        if (value < min) {
+            errln("GMT %e get(%d) < getMinimum(%d) : %d < %d", time, field,
+                  field, value, min);
+        }
+        if (max < value) {
+            errln("GMT %e getMaximum(%d) < get(%d) : %d < %d", time, field,
+                  field, max, value);
+        }
+    }
+}
+
+void CalendarRegressionTest::TestUTCWrongAMPM22023(void) {
+    VerifyGetStayInBound(-1);
+    VerifyGetStayInBound(0);
+    VerifyGetStayInBound(-1e-8);
+    VerifyGetStayInBound(-1e-9);
+    VerifyGetStayInBound(-1e-15);
+}
+
 void CalendarRegressionTest::TestWeekOfYear13548(void) {
     int32_t year = 2000;
     UErrorCode status = U_ZERO_ERROR;
index 5770be51d639f890d96edfe5b36026c94cbfeb13..9581eac20b918f29f02c7a94b8eaef0be30b86b2 100644 (file)
@@ -81,12 +81,14 @@ public:
     void TestPersianCalOverflow(void);
     void TestIslamicCalOverflow(void);
     void TestWeekOfYear13548(void);
+    void TestUTCWrongAMPM22023(void);
 
     void Test13745(void);
 
     void printdate(GregorianCalendar *cal, const char *string);
     void dowTest(UBool lenient) ;
 
+    void VerifyGetStayInBound(double test_value);
 
     static UDate getAssociatedDate(UDate d, UErrorCode& status);
     static UDate makeDate(int32_t y, int32_t m = 0, int32_t d = 0, int32_t hr = 0, int32_t min = 0, int32_t sec = 0);
index 920ec75c8b940e3b2d032a12233104ca00ef9161..ecc90e94ec0d6462c03c151d38c58e3f54ce9211 100644 (file)
@@ -130,6 +130,7 @@ void DateFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &nam
     TESTCASE_AUTO(TestParseRegression13744);
     TESTCASE_AUTO(TestAdoptCalendarLeak);
     TESTCASE_AUTO(Test20741_ABFields);
+    TESTCASE_AUTO(Test22023_UTCWithMinusZero);
 
     TESTCASE_AUTO_END;
 }
@@ -5726,6 +5727,22 @@ void DateFormatTest::Test20741_ABFields() {
     }
 }
 
+void DateFormatTest::Test22023_UTCWithMinusZero() {
+    IcuTestErrorCode status(*this, "Test22023_UTCWithMinusZero");
+    Locale locale("en");
+    SimpleDateFormat fmt("h a", locale, status);
+    ASSERT_OK(status);
+    fmt.adoptCalendar(Calendar::createInstance(
+        TimeZone::createTimeZone("UTC"), locale, status));
+    ASSERT_OK(status);
+    FieldPositionIterator fp_iter;
+    icu::UnicodeString formatted;
+    // very small negative value in double cause it to be -0
+    // internally and trigger the assertion and bug.
+    fmt.format(-1e-9, formatted, &fp_iter, status);
+    ASSERT_OK(status);
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
 
 //eof
index 2fe94984470c6ffd753271a4b0e4bfc5ea85003a..f5e076977455ffcf2421c4501669b97974b40cbd 100644 (file)
@@ -266,6 +266,7 @@ public:
     void TestParseRegression13744();
     void TestAdoptCalendarLeak();
     void Test20741_ABFields();
+    void Test22023_UTCWithMinusZero();
 
 private:
     UBool showParse(DateFormat &format, const UnicodeString &formattedString);