]> granicus.if.org Git - icu/commitdiff
ICU-11632 icu4j changes for integer overflow in calendar support
authorJugu Dannie Sundar <jugu.87@gmail.com>
Tue, 5 Sep 2017 22:22:46 +0000 (22:22 +0000)
committerJugu Dannie Sundar <jugu.87@gmail.com>
Tue, 5 Sep 2017 22:22:46 +0000 (22:22 +0000)
X-SVN-Rev: 40370

icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java

index ea0aee2680487b68cd6b4e8ed3bfcc2fa192a602..6529867e708d9ef8cc8d2ea93d725be97d02e5ef 100644 (file)
@@ -1301,6 +1301,11 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
      */
     protected static final Date MAX_DATE = new Date(MAX_MILLIS);
 
+    /**
+     * The maximum supported hours for millisecond calculations
+     */
+    private static final int MAX_HOURS = 548;
+
     // Internal notes:
     // Calendar contains two kinds of time representations: current "time" in
     // milliseconds, and a set of time "fields" representing the current time.
@@ -5398,7 +5403,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
 
         long millis = julianDayToMillis(julianDay);
 
-        int millisInDay;
+        long millisInDay;
 
         // We only use MILLISECONDS_IN_DAY if it has been set by the user.
         // This makes it possible for the caller to set the calendar to a
@@ -5409,7 +5414,18 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
                 newestStamp(AM_PM, MILLISECOND, UNSET) <= stamp[MILLISECONDS_IN_DAY]) {
             millisInDay = internalGet(MILLISECONDS_IN_DAY);
         } else {
-            millisInDay = computeMillisInDay();
+            int hour = Math.abs(internalGet(HOUR_OF_DAY));
+            hour = Math.max(hour, Math.abs(internalGet(HOUR)));
+            // if hour field value is greater than 596, then the
+            // milliseconds value exceeds integer range, hence
+            // using a conservative estimate of 548, we invoke
+            // the long return version of the compute millis method if
+            // the hour value exceeds 548
+            if (hour > MAX_HOURS) {
+                millisInDay = computeMillisInDayLong();
+            } else {
+                millisInDay = computeMillisInDay();
+            }
         }
 
         if (stamp[ZONE_OFFSET] >= MINIMUM_USER_STAMP ||
@@ -5597,8 +5613,9 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
      * value from 0 to 23:59:59.999 inclusive, unless fields are out of
      * range, in which case it can be an arbitrary value.  This value
      * reflects local zone wall time.
-     * @stable ICU 2.0
+     * @deprecated ICU 60
      */
+    @Deprecated
     protected int computeMillisInDay() {
         // Do the time portion of the conversion.
 
@@ -5637,14 +5654,62 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
         return millisInDay;
     }
 
+    /**
+     * Compute the milliseconds in the day from the fields.  The standard
+     * value range is from 0 to 23:59:59.999 inclusive. This value
+     * reflects local zone wall time.
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    @Deprecated
+    protected long computeMillisInDayLong() {
+        // Do the time portion of the conversion.
+
+        long millisInDay = 0;
+
+        // Find the best set of fields specifying the time of day.  There
+        // are only two possibilities here; the HOUR_OF_DAY or the
+        // AM_PM and the HOUR.
+        int hourOfDayStamp = stamp[HOUR_OF_DAY];
+        int hourStamp = Math.max(stamp[HOUR], stamp[AM_PM]);
+        int bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp;
+
+        // Hours
+        if (bestStamp != UNSET) {
+            if (bestStamp == hourOfDayStamp) {
+                // Don't normalize here; let overflow bump into the next period.
+                // This is consistent with how we handle other fields.
+                millisInDay += internalGet(HOUR_OF_DAY);
+            } else {
+                // Don't normalize here; let overflow bump into the next period.
+                // This is consistent with how we handle other fields.
+                millisInDay += internalGet(HOUR);
+                millisInDay += 12 * internalGet(AM_PM); // Default works for unset AM_PM
+            }
+        }
+
+        // We use the fact that unset == 0; we start with millisInDay
+        // == HOUR_OF_DAY.
+        millisInDay *= 60;
+        millisInDay += internalGet(MINUTE); // now have minutes
+        millisInDay *= 60;
+        millisInDay += internalGet(SECOND); // now have seconds
+        millisInDay *= 1000;
+        millisInDay += internalGet(MILLISECOND); // now have millis
+
+        return millisInDay;
+    }
+
+
     /**
      * This method can assume EXTENDED_YEAR has been set.
      * @param millis milliseconds of the date fields (local midnight millis)
      * @param millisInDay milliseconds of the time fields; may be out
      * or range.
      * @return total zone offset (raw + DST) for the given moment
-     * @stable ICU 2.0
+     * @deprecated ICU 60
      */
+    @Deprecated
     protected int computeZoneOffset(long millis, int millisInDay) {
         int[] offsets = new int[2];
         long wall = millis + millisInDay;
@@ -5689,6 +5754,59 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
         return offsets[0] + offsets[1];
     }
 
+    /**
+     * This method can assume EXTENDED_YEAR has been set.
+     * @param millis milliseconds of the date fields (local midnight millis)
+     * @param millisInDay milliseconds of the time fields
+     * @return total zone offset (raw + DST) for the given moment
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    @Deprecated
+    protected int computeZoneOffset(long millis, long millisInDay) {
+        int[] offsets = new int[2];
+        long wall = millis + millisInDay;
+        if (zone instanceof BasicTimeZone) {
+            int duplicatedTimeOpt = (repeatedWallTime == WALLTIME_FIRST) ? BasicTimeZone.LOCAL_FORMER : BasicTimeZone.LOCAL_LATTER;
+            int nonExistingTimeOpt = (skippedWallTime == WALLTIME_FIRST) ? BasicTimeZone.LOCAL_LATTER : BasicTimeZone.LOCAL_FORMER;
+            ((BasicTimeZone)zone).getOffsetFromLocal(wall, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
+        } else {
+            // By default, TimeZone#getOffset behaves WALLTIME_LAST for both.
+            zone.getOffset(wall, true, offsets);
+
+            boolean sawRecentNegativeShift = false;
+            if (repeatedWallTime == WALLTIME_FIRST) {
+                // Check if the given wall time falls into repeated time range
+                long tgmt = wall - (offsets[0] + offsets[1]);
+
+                // 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.
+                int offsetBefore6 = zone.getOffset(tgmt - 6*60*60*1000);
+                int offsetDelta = (offsets[0] + offsets[1]) - offsetBefore6;
+
+                assert offsetDelta > -6*60*60*1000 : offsetDelta;
+                if (offsetDelta < 0) {
+                    sawRecentNegativeShift = true;
+                    // Negative shift within last 6 hours. When 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.
+                    zone.getOffset(wall + offsetDelta, true, offsets);
+                }
+            }
+            if (!sawRecentNegativeShift && skippedWallTime == 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).
+                long tgmt = wall - (offsets[0] + offsets[1]);
+                zone.getOffset(tgmt, false, offsets);
+            }
+        }
+        return offsets[0] + offsets[1];
+    }
+
     /**
      * Compute the Julian day number as specified by this calendar's fields.
      * @stable ICU 2.0
index 379d91370149c146535495a4d78fa3eedecba044..71ed611ef118b1f95ba4be6f65619b2402e462e9 100644 (file)
@@ -2479,5 +2479,26 @@ public class CalendarRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
         assertEquals("Fail: dateformat doesn't interpret calendar correctly", expectedFormat, actualFormat);
     }
 
+    @Test
+    public void TestTicket11632() {
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(Calendar.HOUR, 596);
+        // hour value set upto 596 lies within the integer range for millisecond calculations
+        assertEquals("Incorrect time for integer range milliseconds","Sun Jan 25 20:00:00 PST 1970", cal.getTime().toString());
+        cal.clear();
+        //  hour value set above 596 lies outside the integer range for millisecond calculations. This will invoke
+        // the long version of the compute millis in day method in the ICU internal API
+        cal.set(Calendar.HOUR, 597);
+        assertEquals("Incorrect time for long range milliseconds","Sun Jan 25 21:00:00 PST 1970", cal.getTime().toString());
+        cal.clear();
+        cal.set(Calendar.HOUR, 597);
+        cal.set(Calendar.MINUTE, 60*24);
+        assertEquals("Incorrect time for long range milliseconds","Mon Jan 26 21:00:00 PST 1970", cal.getTime().toString());
+        cal.clear();
+        cal.set(Calendar.HOUR_OF_DAY, 597);
+        assertEquals("Incorrect time for long range milliseconds","Sun Jan 25 21:00:00 PST 1970", cal.getTime().toString());
+    }
+
 }
 //eof