From 1d11db8a35a542b88ed0ae3913bebdb33b89ce16 Mon Sep 17 00:00:00 2001 From: Mark Davis Date: Fri, 4 Jul 2014 15:48:06 +0000 Subject: [PATCH] ICU-10560 first cut, marked @internal for now X-SVN-Rev: 35998 --- .../core/src/com/ibm/icu/util/Calendar.java | 1055 +++++++++-------- .../dev/test/calendar/CalendarRegression.java | 12 +- 2 files changed, 573 insertions(+), 494 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java index 7bd68733d30..c178e7e851b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java @@ -1,7 +1,7 @@ /* -* Copyright (C) 1996-2014, International Business Machines -* Corporation and others. All Rights Reserved. -*/ + * Copyright (C) 1996-2014, International Business Machines + * Corporation and others. All Rights Reserved. + */ package com.ibm.icu.util; @@ -1627,20 +1627,20 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable nextStamp && stamp[i] < currentValue) { currentValue = stamp[i]; index = i; } } - + if (index >= 0) { stamp[index] = ++nextStamp; } else { @@ -1649,7 +1649,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable MAX_FIELD_COUNT) { + fields.length > MAX_FIELD_COUNT) { throw new IllegalStateException("Invalid fields[]"); } ///CLOVER:ON stamp = new int[fields.length]; int mask = (1 << ERA) | - (1 << YEAR) | - (1 << MONTH) | - (1 << DAY_OF_MONTH) | - (1 << DAY_OF_YEAR) | - (1 << EXTENDED_YEAR) | - (1 << IS_LEAP_MONTH); + (1 << YEAR) | + (1 << MONTH) | + (1 << DAY_OF_MONTH) | + (1 << DAY_OF_YEAR) | + (1 << EXTENDED_YEAR) | + (1 << IS_LEAP_MONTH); for (int i=BASE_FIELD_COUNT; i values = new ArrayList(); UResourceBundle rb = UResourceBundle.getBundleInstance( - ICUResourceBundle.ICU_BASE_NAME, - "supplementalData", - ICUResourceBundle.ICU_DATA_CLASS_LOADER); + ICUResourceBundle.ICU_BASE_NAME, + "supplementalData", + ICUResourceBundle.ICU_DATA_CLASS_LOADER); UResourceBundle calPref = rb.get("calendarPreferenceData"); UResourceBundle order = null; try { @@ -2024,11 +2024,11 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable 1 Dst -> 1 Std -> 2 Std // To get around this problem we don't use fields; we manipulate // the time in millis directly. - { - // Assume min == 0 in calculations below - long start = getTimeInMillis(); - int oldHour = internalGet(field); - int max = getMaximum(field); - int newHour = (oldHour + amount) % (max + 1); - if (newHour < 0) { - newHour += max + 1; - } - setTimeInMillis(start + ONE_HOUR * ((long)newHour - oldHour)); - return; + { + // Assume min == 0 in calculations below + long start = getTimeInMillis(); + int oldHour = internalGet(field); + int max = getMaximum(field); + int newHour = (oldHour + amount) % (max + 1); + if (newHour < 0) { + newHour += max + 1; } + setTimeInMillis(start + ONE_HOUR * ((long)newHour - oldHour)); + return; + } case MONTH: // Rolling the month involves both pinning the final value // and adjusting the DAY_OF_MONTH if necessary. We only adjust the // DAY_OF_MONTH if, after updating the MONTH field, it is illegal. // E.g., .roll(MONTH, 1) -> or . - { - int max = getActualMaximum(MONTH); - int mon = (internalGet(MONTH) + amount) % (max+1); - - if (mon < 0) { - mon += (max + 1); - } - set(MONTH, mon); + { + int max = getActualMaximum(MONTH); + int mon = (internalGet(MONTH) + amount) % (max+1); - // Keep the day of month in range. We don't want to spill over - // into the next month; e.g., we don't want jan31 + 1 mo -> feb31 -> - // mar3. - pinField(DAY_OF_MONTH); - return; + if (mon < 0) { + mon += (max + 1); } + set(MONTH, mon); + + // Keep the day of month in range. We don't want to spill over + // into the next month; e.g., we don't want jan31 + 1 mo -> feb31 -> + // mar3. + pinField(DAY_OF_MONTH); + return; + } case YEAR: case YEAR_WOY: // * If era==0 and years go backwards in time, change sign of amount. // * Until we have new API per #9393, we temporarily hardcode knowledge of // which calendars have era 0 years that go backwards. - { - boolean era0WithYearsThatGoBackwards = false; - int era = get(ERA); - if (era == 0) { - String calType = getType(); - if (calType.equals("gregorian") || calType.equals("roc") || calType.equals("coptic")) { - amount = -amount; - era0WithYearsThatGoBackwards = true; - } + { + boolean era0WithYearsThatGoBackwards = false; + int era = get(ERA); + if (era == 0) { + String calType = getType(); + if (calType.equals("gregorian") || calType.equals("roc") || calType.equals("coptic")) { + amount = -amount; + era0WithYearsThatGoBackwards = true; } - int newYear = internalGet(field) + amount; - if (era > 0 || newYear >= 1) { - int maxYear = getActualMaximum(field); - if (maxYear < 32768) { - // this era has real bounds, roll should wrap years - if (newYear < 1) { - newYear = maxYear - ((-newYear) % maxYear); - } else if (newYear > maxYear) { - newYear = ((newYear - 1) % maxYear) + 1; - } - // else era is unbounded, just pin low year instead of wrapping - } else if (newYear < 1) { - newYear = 1; + } + int newYear = internalGet(field) + amount; + if (era > 0 || newYear >= 1) { + int maxYear = getActualMaximum(field); + if (maxYear < 32768) { + // this era has real bounds, roll should wrap years + if (newYear < 1) { + newYear = maxYear - ((-newYear) % maxYear); + } else if (newYear > maxYear) { + newYear = ((newYear - 1) % maxYear) + 1; } + // else era is unbounded, just pin low year instead of wrapping + } else if (newYear < 1) { + newYear = 1; + } // else we are in era 0 with newYear < 1; // calendars with years that go backwards must pin the year value at 0, // other calendars can have years < 0 in era 0 - } else if (era0WithYearsThatGoBackwards) { - newYear = 1; - } - set(field, newYear); - pinField(MONTH); - pinField(DAY_OF_MONTH); - return; + } else if (era0WithYearsThatGoBackwards) { + newYear = 1; } + set(field, newYear); + pinField(MONTH); + pinField(DAY_OF_MONTH); + return; + } case EXTENDED_YEAR: // Rolling the year can involve pinning the DAY_OF_MONTH. set(field, internalGet(field) + amount); @@ -2801,202 +2801,202 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= DAY_OF_MONTH so we skip the += 7 step here. - - // Get the limit day for the blocked-off rectangular month; that - // is, the day which is one past the last day of the month, - // after the month has already been filled in with phantom days - // to fill out the last week. This day has a normalized DOW of 0. - int limit = monthLen + 7 - ldm; - - // Now roll between start and (limit - 1). - int gap = limit - start; - int day_of_month = (internalGet(DAY_OF_MONTH) + amount*7 - - start) % gap; - if (day_of_month < 0) day_of_month += gap; - day_of_month += start; - - // Finally, pin to the real start and end of the month. - if (day_of_month < 1) day_of_month = 1; - if (day_of_month > monthLen) day_of_month = monthLen; - - // Set the DAY_OF_MONTH. We rely on the fact that this field - // takes precedence over everything else (since all other fields - // are also set at this point). If this fact changes (if the - // disambiguation algorithm changes) then we will have to unset - // the appropriate fields here so that DAY_OF_MONTH is attended - // to. - set(DAY_OF_MONTH, day_of_month); - return; - } + { + // This is tricky, because during the roll we may have to shift + // to a different day of the week. For example: + + // s m t w r f s + // 1 2 3 4 5 + // 6 7 8 9 10 11 12 + + // When rolling from the 6th or 7th back one week, we go to the + // 1st (assuming that the first partial week counts). The same + // thing happens at the end of the month. + + // The other tricky thing is that we have to figure out whether + // the first partial week actually counts or not, based on the + // minimal first days in the week. And we have to use the + // correct first day of the week to delineate the week + // boundaries. + + // Here's our algorithm. First, we find the real boundaries of + // the month. Then we discard the first partial week if it + // doesn't count in this locale. Then we fill in the ends with + // phantom days, so that the first partial week and the last + // partial week are full weeks. We then have a nice square + // block of weeks. We do the usual rolling within this block, + // as is done elsewhere in this method. If we wind up on one of + // the phantom days that we added, we recognize this and pin to + // the first or the last day of the month. Easy, eh? + + // Normalize the DAY_OF_WEEK so that 0 is the first day of the week + // in this locale. We have dow in 0..6. + int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek(); + if (dow < 0) dow += 7; + + // Find the day of the week (normalized for locale) for the first + // of the month. + int fdm = (dow - internalGet(DAY_OF_MONTH) + 1) % 7; + if (fdm < 0) fdm += 7; + + // Get the first day of the first full week of the month, + // including phantom days, if any. Figure out if the first week + // counts or not; if it counts, then fill in phantom days. If + // not, advance to the first real full week (skip the partial week). + int start; + if ((7 - fdm) < getMinimalDaysInFirstWeek()) + start = 8 - fdm; // Skip the first partial week + else + start = 1 - fdm; // This may be zero or negative + + // Get the day of the week (normalized for locale) for the last + // day of the month. + int monthLen = getActualMaximum(DAY_OF_MONTH); + int ldm = (monthLen - internalGet(DAY_OF_MONTH) + dow) % 7; + // We know monthLen >= DAY_OF_MONTH so we skip the += 7 step here. + + // Get the limit day for the blocked-off rectangular month; that + // is, the day which is one past the last day of the month, + // after the month has already been filled in with phantom days + // to fill out the last week. This day has a normalized DOW of 0. + int limit = monthLen + 7 - ldm; + + // Now roll between start and (limit - 1). + int gap = limit - start; + int day_of_month = (internalGet(DAY_OF_MONTH) + amount*7 - + start) % gap; + if (day_of_month < 0) day_of_month += gap; + day_of_month += start; + + // Finally, pin to the real start and end of the month. + if (day_of_month < 1) day_of_month = 1; + if (day_of_month > monthLen) day_of_month = monthLen; + + // Set the DAY_OF_MONTH. We rely on the fact that this field + // takes precedence over everything else (since all other fields + // are also set at this point). If this fact changes (if the + // disambiguation algorithm changes) then we will have to unset + // the appropriate fields here so that DAY_OF_MONTH is attended + // to. + set(DAY_OF_MONTH, day_of_month); + return; + } case WEEK_OF_YEAR: - { - // This follows the outline of WEEK_OF_MONTH, except it applies - // to the whole year. Please see the comment for WEEK_OF_MONTH - // for general notes. - - // Normalize the DAY_OF_WEEK so that 0 is the first day of the week - // in this locale. We have dow in 0..6. - int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek(); - if (dow < 0) dow += 7; - - // Find the day of the week (normalized for locale) for the first - // of the year. - int fdy = (dow - internalGet(DAY_OF_YEAR) + 1) % 7; - if (fdy < 0) fdy += 7; - - // Get the first day of the first full week of the year, - // including phantom days, if any. Figure out if the first week - // counts or not; if it counts, then fill in phantom days. If - // not, advance to the first real full week (skip the partial week). - int start; - if ((7 - fdy) < getMinimalDaysInFirstWeek()) - start = 8 - fdy; // Skip the first partial week - else - start = 1 - fdy; // This may be zero or negative - - // Get the day of the week (normalized for locale) for the last - // day of the year. - int yearLen = getActualMaximum(DAY_OF_YEAR); - int ldy = (yearLen - internalGet(DAY_OF_YEAR) + dow) % 7; - // We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here. - - // Get the limit day for the blocked-off rectangular year; that - // is, the day which is one past the last day of the year, - // after the year has already been filled in with phantom days - // to fill out the last week. This day has a normalized DOW of 0. - int limit = yearLen + 7 - ldy; - - // Now roll between start and (limit - 1). - int gap = limit - start; - int day_of_year = (internalGet(DAY_OF_YEAR) + amount*7 - - start) % gap; - if (day_of_year < 0) day_of_year += gap; - day_of_year += start; - - // Finally, pin to the real start and end of the month. - if (day_of_year < 1) day_of_year = 1; - if (day_of_year > yearLen) day_of_year = yearLen; - - // Make sure that the year and day of year are attended to by - // clearing other fields which would normally take precedence. - // If the disambiguation algorithm is changed, this section will - // have to be updated as well. - set(DAY_OF_YEAR, day_of_year); - clear(MONTH); - return; - } + { + // This follows the outline of WEEK_OF_MONTH, except it applies + // to the whole year. Please see the comment for WEEK_OF_MONTH + // for general notes. + + // Normalize the DAY_OF_WEEK so that 0 is the first day of the week + // in this locale. We have dow in 0..6. + int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek(); + if (dow < 0) dow += 7; + + // Find the day of the week (normalized for locale) for the first + // of the year. + int fdy = (dow - internalGet(DAY_OF_YEAR) + 1) % 7; + if (fdy < 0) fdy += 7; + + // Get the first day of the first full week of the year, + // including phantom days, if any. Figure out if the first week + // counts or not; if it counts, then fill in phantom days. If + // not, advance to the first real full week (skip the partial week). + int start; + if ((7 - fdy) < getMinimalDaysInFirstWeek()) + start = 8 - fdy; // Skip the first partial week + else + start = 1 - fdy; // This may be zero or negative + + // Get the day of the week (normalized for locale) for the last + // day of the year. + int yearLen = getActualMaximum(DAY_OF_YEAR); + int ldy = (yearLen - internalGet(DAY_OF_YEAR) + dow) % 7; + // We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here. + + // Get the limit day for the blocked-off rectangular year; that + // is, the day which is one past the last day of the year, + // after the year has already been filled in with phantom days + // to fill out the last week. This day has a normalized DOW of 0. + int limit = yearLen + 7 - ldy; + + // Now roll between start and (limit - 1). + int gap = limit - start; + int day_of_year = (internalGet(DAY_OF_YEAR) + amount*7 - + start) % gap; + if (day_of_year < 0) day_of_year += gap; + day_of_year += start; + + // Finally, pin to the real start and end of the month. + if (day_of_year < 1) day_of_year = 1; + if (day_of_year > yearLen) day_of_year = yearLen; + + // Make sure that the year and day of year are attended to by + // clearing other fields which would normally take precedence. + // If the disambiguation algorithm is changed, this section will + // have to be updated as well. + set(DAY_OF_YEAR, day_of_year); + clear(MONTH); + return; + } case DAY_OF_YEAR: - { - // Roll the day of year using millis. Compute the millis for - // the start of the year, and get the length of the year. - long delta = amount * ONE_DAY; // Scale up from days to millis - long min2 = time - (internalGet(DAY_OF_YEAR) - 1) * ONE_DAY; - int yearLength = getActualMaximum(DAY_OF_YEAR); - time = (time + delta - min2) % (yearLength*ONE_DAY); - if (time < 0) time += yearLength*ONE_DAY; - setTimeInMillis(time + min2); - return; - } + { + // Roll the day of year using millis. Compute the millis for + // the start of the year, and get the length of the year. + long delta = amount * ONE_DAY; // Scale up from days to millis + long min2 = time - (internalGet(DAY_OF_YEAR) - 1) * ONE_DAY; + int yearLength = getActualMaximum(DAY_OF_YEAR); + time = (time + delta - min2) % (yearLength*ONE_DAY); + if (time < 0) time += yearLength*ONE_DAY; + setTimeInMillis(time + min2); + return; + } case DAY_OF_WEEK: case DOW_LOCAL: - { - // Roll the day of week using millis. Compute the millis for - // the start of the week, using the first day of week setting. - // Restrict the millis to [start, start+7days). - long delta = amount * ONE_DAY; // Scale up from days to millis - // Compute the number of days before the current day in this - // week. This will be a value 0..6. - int leadDays = internalGet(field); - leadDays -= (field == DAY_OF_WEEK) ? getFirstDayOfWeek() : 1; - if (leadDays < 0) leadDays += 7; - long min2 = time - leadDays * ONE_DAY; - time = (time + delta - min2) % ONE_WEEK; - if (time < 0) time += ONE_WEEK; - setTimeInMillis(time + min2); - return; - } + { + // Roll the day of week using millis. Compute the millis for + // the start of the week, using the first day of week setting. + // Restrict the millis to [start, start+7days). + long delta = amount * ONE_DAY; // Scale up from days to millis + // Compute the number of days before the current day in this + // week. This will be a value 0..6. + int leadDays = internalGet(field); + leadDays -= (field == DAY_OF_WEEK) ? getFirstDayOfWeek() : 1; + if (leadDays < 0) leadDays += 7; + long min2 = time - leadDays * ONE_DAY; + time = (time + delta - min2) % ONE_WEEK; + if (time < 0) time += ONE_WEEK; + setTimeInMillis(time + min2); + return; + } case DAY_OF_WEEK_IN_MONTH: - { - // Roll the day of week in the month using millis. Determine - // the first day of the week in the month, and then the last, - // and then roll within that range. - long delta = amount * ONE_WEEK; // Scale up from weeks to millis - // Find the number of same days of the week before this one - // in this month. - int preWeeks = (internalGet(DAY_OF_MONTH) - 1) / 7; - // Find the number of same days of the week after this one - // in this month. - int postWeeks = (getActualMaximum(DAY_OF_MONTH) - - internalGet(DAY_OF_MONTH)) / 7; - // From these compute the min and gap millis for rolling. - long min2 = time - preWeeks * ONE_WEEK; - long gap2 = ONE_WEEK * (preWeeks + postWeeks + 1); // Must add 1! - // Roll within this range - time = (time + delta - min2) % gap2; - if (time < 0) time += gap2; - setTimeInMillis(time + min2); - return; - } + { + // Roll the day of week in the month using millis. Determine + // the first day of the week in the month, and then the last, + // and then roll within that range. + long delta = amount * ONE_WEEK; // Scale up from weeks to millis + // Find the number of same days of the week before this one + // in this month. + int preWeeks = (internalGet(DAY_OF_MONTH) - 1) / 7; + // Find the number of same days of the week after this one + // in this month. + int postWeeks = (getActualMaximum(DAY_OF_MONTH) - + internalGet(DAY_OF_MONTH)) / 7; + // From these compute the min and gap millis for rolling. + long min2 = time - preWeeks * ONE_WEEK; + long gap2 = ONE_WEEK * (preWeeks + postWeeks + 1); // Must add 1! + // Roll within this range + time = (time + delta - min2) % gap2; + if (time < 0) time += gap2; + setTimeInMillis(time + min2); + return; + } case JULIAN_DAY: set(field, internalGet(field) + amount); return; default: // Other fields cannot be rolled by this method throw new IllegalArgumentException("Calendar.roll(" + fieldName(field) + - ") not supported"); + ") not supported"); } } @@ -3099,29 +3099,29 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable PATTERN_CACHE = - new SimpleCache(); + new SimpleCache(); // final fallback patterns private static final String[] DEFAULT_PATTERNS = { "HH:mm:ss z", @@ -3388,7 +3388,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable DateFormat.SHORT) { throw new IllegalArgumentException("Illegal time style " + timeStyle); } @@ -3404,7 +3404,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= 0) && (dateStyle >= 0)) { pattern = MessageFormat.format(patternData.getDateTimePattern(dateStyle), new Object[] {patternData.patterns[timeStyle], - patternData.patterns[dateStyle + 4]}); + patternData.patterns[dateStyle + 4]}); // Might need to merge the overrides from the date and time into a single // override string TODO: Right now we are forcing the date's override into the // time style. @@ -3412,9 +3412,9 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= 0) { pattern = patternData.patterns[timeStyle]; @@ -3460,7 +3460,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable 0) { @@ -4427,11 +4427,11 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= transition) - : (millisInDay < transition); + ? (millisInDay >= transition) + : (millisInDay < transition); } // (We can never reach this point.) } @@ -4503,19 +4503,50 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= march1) correction = isLeap ? 1 : 2; month = (12 * (dayOfYear + correction) + 6) / 367; // zero-based month dayOfMonth = dayOfYear - - GREGORIAN_MONTH_COUNT[month][isLeap?3:2] + 1; // one-based DOM + GREGORIAN_MONTH_COUNT[month][isLeap?3:2] + 1; // one-based DOM gregorianYear = year; gregorianMonth = month; // 0-based already @@ -4846,7 +4915,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= getMinimalDaysInFirstWeek()) && - ((dayOfYear + 7 - relDow) > lastDoy)) { + ((dayOfYear + 7 - relDow) > lastDoy)) { woy = 1; yearOfWeekOfYear++; } @@ -4936,38 +5005,38 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable=RESOLVE_REMAP)?1:0; i=RESOLVE_REMAP)?1:0; i bestStamp) { - tempBestField = line[0]; // First field refers to entire line - if (tempBestField >= RESOLVE_REMAP) { - tempBestField &= (RESOLVE_REMAP-1); - // This check is needed to resolve some issues with UCAL_YEAR precedence mapping - if (tempBestField != DATE || (stamp[WEEK_OF_MONTH] < stamp[tempBestField])) { + // Record new maximum stamp & field no. + if (lineStamp > bestStamp) { + tempBestField = line[0]; // First field refers to entire line + if (tempBestField >= RESOLVE_REMAP) { + tempBestField &= (RESOLVE_REMAP-1); + // This check is needed to resolve some issues with UCAL_YEAR precedence mapping + if (tempBestField != DATE || (stamp[WEEK_OF_MONTH] < stamp[tempBestField])) { + bestField = tempBestField; + } + } else { bestField = tempBestField; } - } else { - bestField = tempBestField; - } - if (bestField == tempBestField) { - bestStamp = lineStamp; + if (bestField == tempBestField) { + bestStamp = lineStamp; + } } } - } } return (bestField>=RESOLVE_REMAP)?(bestField&(RESOLVE_REMAP-1)):bestField; } @@ -5065,8 +5134,8 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable max) { throw new IllegalArgumentException(fieldName(field) + - '=' + value + ", valid range=" + - min + ".." + max); + '=' + value + ", valid range=" + + min + ".." + max); } } @@ -5093,14 +5162,14 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= MINIMUM_USER_STAMP && - newestStamp(AM_PM, MILLISECOND, UNSET) <= stamp[MILLISECONDS_IN_DAY]) { + newestStamp(AM_PM, MILLISECOND, UNSET) <= stamp[MILLISECONDS_IN_DAY]) { millisInDay = internalGet(MILLISECONDS_IN_DAY); } else { millisInDay = computeMillisInDay(); } if (stamp[ZONE_OFFSET] >= MINIMUM_USER_STAMP || - stamp[DST_OFFSET] >= MINIMUM_USER_STAMP) { + stamp[DST_OFFSET] >= MINIMUM_USER_STAMP) { time = millis + millisInDay - (internalGet(ZONE_OFFSET) + internalGet(DST_OFFSET)); } else { // Compute the time zone offset and DST offset. There are two potential @@ -5193,93 +5262,93 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable 0; - - long upper = base; - long lower = base - duration - 1; - int offsetU = tz.getOffset(upper); - int offsetL = tz.getOffset(lower); - if (offsetU == offsetL) { - return null; - } - return findPreviousZoneTransitionTime(tz, offsetU, upper, lower); - } - - /** - * The time units used by {@link #findPreviousZoneTransitionTime(TimeZone, int, long, long)} - * for optimizing transition time binary search. - */ - private static final int[] FIND_ZONE_TRANSITION_TIME_UNITS = { - 60*60*1000, // 1 hour - 30*60*1000, // 30 minutes - 60*1000, // 1 minute - 1000, // 1 second - }; - - /** - * Implementing binary search for zone transtion detection, used by {@link #getPreviousZoneTransitionTime(TimeZone, long, long)} - * @param tz The time zone. - * @param upperOffset The zone offset at upper - * @param upper The upper bound, inclusive. - * @param lower The lower bound, exclusive. - * @return The time of the previous zone transition, or null if not available. - */ - private static Long findPreviousZoneTransitionTime(TimeZone tz, int upperOffset, long upper, long lower) { - boolean onUnitTime = false; - long mid = 0; - - for (int unit : FIND_ZONE_TRANSITION_TIME_UNITS) { - long lunits = lower/unit; - long uunits = upper/unit; - if (uunits > lunits) { - mid = ((lunits + uunits + 1) >>> 1) * unit; - onUnitTime = true; - break; - } - } - - int midOffset; - if (!onUnitTime) { - mid = (upper + lower) >>> 1; - } - - if (onUnitTime) { - if (mid != upper) { - midOffset = tz.getOffset(mid); - if (midOffset != upperOffset) { - return findPreviousZoneTransitionTime(tz, upperOffset, upper, mid); - } - upper = mid; - } - // check mid-1 - mid--; - } else { - mid = (upper + lower) >>> 1; - } - - if (mid == lower) { - return Long.valueOf(upper); - } - midOffset = tz.getOffset(mid); - if (midOffset != upperOffset) { - if (onUnitTime) { - return Long.valueOf(upper); - } - return findPreviousZoneTransitionTime(tz, upperOffset, upper, mid); - } - return findPreviousZoneTransitionTime(tz, upperOffset, mid, lower); - } - - /** + /** + * Find the previous zone transition within the specified duration. + * Note: This method is only used when TimeZone is NOT a BasicTimeZone. + * @param tz The time zone. + * @param base The base time, inclusive. + * @param duration The range of time evaluated. + * @return The time of the previous zone transition, or null if not available. + */ + private static Long getPreviousZoneTransitionTime(TimeZone tz, long base, long duration) { + assert duration > 0; + + long upper = base; + long lower = base - duration - 1; + int offsetU = tz.getOffset(upper); + int offsetL = tz.getOffset(lower); + if (offsetU == offsetL) { + return null; + } + return findPreviousZoneTransitionTime(tz, offsetU, upper, lower); + } + + /** + * The time units used by {@link #findPreviousZoneTransitionTime(TimeZone, int, long, long)} + * for optimizing transition time binary search. + */ + private static final int[] FIND_ZONE_TRANSITION_TIME_UNITS = { + 60*60*1000, // 1 hour + 30*60*1000, // 30 minutes + 60*1000, // 1 minute + 1000, // 1 second + }; + + /** + * Implementing binary search for zone transtion detection, used by {@link #getPreviousZoneTransitionTime(TimeZone, long, long)} + * @param tz The time zone. + * @param upperOffset The zone offset at upper + * @param upper The upper bound, inclusive. + * @param lower The lower bound, exclusive. + * @return The time of the previous zone transition, or null if not available. + */ + private static Long findPreviousZoneTransitionTime(TimeZone tz, int upperOffset, long upper, long lower) { + boolean onUnitTime = false; + long mid = 0; + + for (int unit : FIND_ZONE_TRANSITION_TIME_UNITS) { + long lunits = lower/unit; + long uunits = upper/unit; + if (uunits > lunits) { + mid = ((lunits + uunits + 1) >>> 1) * unit; + onUnitTime = true; + break; + } + } + + int midOffset; + if (!onUnitTime) { + mid = (upper + lower) >>> 1; + } + + if (onUnitTime) { + if (mid != upper) { + midOffset = tz.getOffset(mid); + if (midOffset != upperOffset) { + return findPreviousZoneTransitionTime(tz, upperOffset, upper, mid); + } + upper = mid; + } + // check mid-1 + mid--; + } else { + mid = (upper + lower) >>> 1; + } + + if (mid == lower) { + return Long.valueOf(upper); + } + midOffset = tz.getOffset(mid); + if (midOffset != upperOffset) { + if (onUnitTime) { + return Long.valueOf(upper); + } + return findPreviousZoneTransitionTime(tz, upperOffset, upper, mid); + } + return findPreviousZoneTransitionTime(tz, upperOffset, mid, lower); + } + + /** * Compute the milliseconds in the day from the fields. This is a * 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 @@ -5432,7 +5501,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable of the Gregorian calendar. @@ -5792,7 +5861,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= 0) ? - numerator / denominator : - ((numerator + 1) / denominator) - 1; + numerator / denominator : + ((numerator + 1) / denominator) - 1; } /** @@ -5885,8 +5954,8 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable= 0) ? - numerator / denominator : - ((numerator + 1) / denominator) - 1; + numerator / denominator : + ((numerator + 1) / denominator) - 1; } /** @@ -5909,7 +5978,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable world", Calendar.getWeekDataForRegion("001"), Calendar.getWeekDataForRegion("xx")); + assertEquals("FR = US", Calendar.getWeekDataForRegion("FR"), Calendar.getWeekDataForRegion("US")); + assertNotEquals("IN ≠ world", Calendar.getWeekDataForRegion("001"), Calendar.getWeekDataForRegion("IN")); + assertNotEquals("FR ≠ EG", Calendar.getWeekDataForRegion("FR"), Calendar.getWeekDataForRegion("EG")); + } + + } //eof -- 2.40.0