diff -r 3fd913435103 -r 3ac550392e43 jdk/src/share/classes/java/time/zone/ZoneRules.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/time/zone/ZoneRules.java Tue Jan 22 20:59:21 2013 -0800 @@ -0,0 +1,959 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package java.time.zone; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.Year; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * The rules defining how the zone offset varies for a single time-zone. + *
+ * The rules model all the historic and future transitions for a time-zone. + * {@link ZoneOffsetTransition} is used for known transitions, typically historic. + * {@link ZoneOffsetTransitionRule} is used for future transitions that are based + * on the result of an algorithm. + *
+ * The rules are loaded via {@link ZoneRulesProvider} using a {@link ZoneId}. + * The same rules may be shared internally between multiple zone IDs. + *
+ * Serializing an instance of {@code ZoneRules} will store the entire set of rules. + * It does not store the zone ID as it is not part of the state of this object. + *
+ * A rule implementation may or may not store full information about historic + * and future transitions, and the information stored is only as accurate as + * that supplied to the implementation by the rules provider. + * Applications should treat the data provided as representing the best information + * available to the implementation of this rule. + * + *
+ * The mapping from an instant to an offset is simple, there is only + * one valid offset for each instant. + * This method returns that offset. + * + * @param instant the instant to find the offset for, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the offset, not null + */ + public ZoneOffset getOffset(Instant instant) { + if (savingsInstantTransitions.length == 0) { + return standardOffsets[0]; + } + long epochSec = instant.getEpochSecond(); + // check if using last rules + if (lastRules.length > 0 && + epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) { + int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); + ZoneOffsetTransition[] transArray = findTransitionArray(year); + ZoneOffsetTransition trans = null; + for (int i = 0; i < transArray.length; i++) { + trans = transArray[i]; + if (epochSec < trans.toEpochSecond()) { + return trans.getOffsetBefore(); + } + } + return trans.getOffsetAfter(); + } + + // using historic rules + int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); + if (index < 0) { + // switch negative insert position to start of matched range + index = -index - 2; + } + return wallOffsets[index + 1]; + } + + /** + * Gets a suitable offset for the specified local date-time in these rules. + *
+ * The mapping from a local date-time to an offset is not straightforward. + * There are three cases: + *
+ * Thus, for any given local date-time there can be zero, one or two valid offsets. + * This method returns the single offset in the Normal case, and in the Gap or Overlap + * case it returns the offset before the transition. + *
+ * Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather + * than the "correct" value, it should be treated with care. Applications that care + * about the correct offset should use a combination of this method, + * {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}. + * + * @param localDateTime the local date-time to query, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the best available offset for the local date-time, not null + */ + public ZoneOffset getOffset(LocalDateTime localDateTime) { + Object info = getOffsetInfo(localDateTime); + if (info instanceof ZoneOffsetTransition) { + return ((ZoneOffsetTransition) info).getOffsetBefore(); + } + return (ZoneOffset) info; + } + + /** + * Gets the offset applicable at the specified local date-time in these rules. + *
+ * The mapping from a local date-time to an offset is not straightforward. + * There are three cases: + *
+ * Thus, for any given local date-time there can be zero, one or two valid offsets. + * This method returns that list of valid offsets, which is a list of size 0, 1 or 2. + * In the case where there are two offsets, the earlier offset is returned at index 0 + * and the later offset at index 1. + *
+ * There are various ways to handle the conversion from a {@code LocalDateTime}. + * One technique, using this method, would be: + *
+ * List<ZoneOffset> validOffsets = rules.getOffset(localDT); + * if (validOffsets.size() == 1) { + * // Normal case: only one valid offset + * zoneOffset = validOffsets.get(0); + * } else { + * // Gap or Overlap: determine what to do from transition (which will be non-null) + * ZoneOffsetTransition trans = rules.getTransition(localDT); + * } + *+ *
+ * In theory, it is possible for there to be more than two valid offsets.
+ * This would happen if clocks to be put back more than once in quick succession.
+ * This has never happened in the history of time-zones and thus has no special handling.
+ * However, if it were to happen, then the list would return more than 2 entries.
+ *
+ * @param localDateTime the local date-time to query for valid offsets, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @return the list of valid offsets, may be immutable, not null
+ */
+ public List
+ * The mapping from a local date-time to an offset is not straightforward.
+ * There are three cases:
+ *
+ * A transition is used to model the cases of a Gap or Overlap.
+ * The Normal case will return null.
+ *
+ * There are various ways to handle the conversion from a {@code LocalDateTime}.
+ * One technique, using this method, would be:
+ *
+ * This provides access to historic information on how the standard offset
+ * has changed over time.
+ * The standard offset is the offset before any daylight saving time is applied.
+ * This is typically the offset applicable during winter.
+ *
+ * @param instant the instant to find the offset information for, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @return the standard offset, not null
+ */
+ public ZoneOffset getStandardOffset(Instant instant) {
+ if (savingsInstantTransitions.length == 0) {
+ return standardOffsets[0];
+ }
+ long epochSec = instant.getEpochSecond();
+ int index = Arrays.binarySearch(standardTransitions, epochSec);
+ if (index < 0) {
+ // switch negative insert position to start of matched range
+ index = -index - 2;
+ }
+ return standardOffsets[index + 1];
+ }
+
+ /**
+ * Gets the amount of daylight savings in use for the specified instant in this zone.
+ *
+ * This provides access to historic information on how the amount of daylight
+ * savings has changed over time.
+ * This is the difference between the standard offset and the actual offset.
+ * Typically the amount is zero during winter and one hour during summer.
+ * Time-zones are second-based, so the nanosecond part of the duration will be zero.
+ *
+ * This default implementation calculates the duration from the
+ * {@link #getOffset(java.time.Instant) actual} and
+ * {@link #getStandardOffset(java.time.Instant) standard} offsets.
+ *
+ * @param instant the instant to find the daylight savings for, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @return the difference between the standard and actual offset, not null
+ */
+ public Duration getDaylightSavings(Instant instant) {
+ if (savingsInstantTransitions.length == 0) {
+ return Duration.ZERO;
+ }
+ ZoneOffset standardOffset = getStandardOffset(instant);
+ ZoneOffset actualOffset = getOffset(instant);
+ return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds());
+ }
+
+ /**
+ * Checks if the specified instant is in daylight savings.
+ *
+ * This checks if the standard offset and the actual offset are the same
+ * for the specified instant.
+ * If they are not, it is assumed that daylight savings is in operation.
+ *
+ * This default implementation compares the {@link #getOffset(java.time.Instant) actual}
+ * and {@link #getStandardOffset(java.time.Instant) standard} offsets.
+ *
+ * @param instant the instant to find the offset information for, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @return the standard offset, not null
+ */
+ public boolean isDaylightSavings(Instant instant) {
+ return (getStandardOffset(instant).equals(getOffset(instant)) == false);
+ }
+
+ /**
+ * Checks if the offset date-time is valid for these rules.
+ *
+ * To be valid, the local date-time must not be in a gap and the offset
+ * must match one of the valid offsets.
+ *
+ * This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)}
+ * contains the specified offset.
+ *
+ * @param localDateTime the date-time to check, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @param offset the offset to check, null returns false
+ * @return true if the offset date-time is valid for these rules
+ */
+ public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) {
+ return getValidOffsets(localDateTime).contains(offset);
+ }
+
+ /**
+ * Gets the next transition after the specified instant.
+ *
+ * This returns details of the next transition after the specified instant.
+ * For example, if the instant represents a point where "Summer" daylight savings time
+ * applies, then the method will return the transition to the next "Winter" time.
+ *
+ * @param instant the instant to get the next transition after, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @return the next transition after the specified instant, null if this is after the last transition
+ */
+ public ZoneOffsetTransition nextTransition(Instant instant) {
+ if (savingsInstantTransitions.length == 0) {
+ return null;
+ }
+ long epochSec = instant.getEpochSecond();
+ // check if using last rules
+ if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
+ if (lastRules.length == 0) {
+ return null;
+ }
+ // search year the instant is in
+ int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
+ ZoneOffsetTransition[] transArray = findTransitionArray(year);
+ for (ZoneOffsetTransition trans : transArray) {
+ if (epochSec < trans.toEpochSecond()) {
+ return trans;
+ }
+ }
+ // use first from following year
+ if (year < Year.MAX_VALUE) {
+ transArray = findTransitionArray(year + 1);
+ return transArray[0];
+ }
+ return null;
+ }
+
+ // using historic rules
+ int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
+ if (index < 0) {
+ index = -index - 1; // switched value is the next transition
+ } else {
+ index += 1; // exact match, so need to add one to get the next
+ }
+ return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]);
+ }
+
+ /**
+ * Gets the previous transition before the specified instant.
+ *
+ * This returns details of the previous transition after the specified instant.
+ * For example, if the instant represents a point where "summer" daylight saving time
+ * applies, then the method will return the transition from the previous "winter" time.
+ *
+ * @param instant the instant to get the previous transition after, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @return the previous transition after the specified instant, null if this is before the first transition
+ */
+ public ZoneOffsetTransition previousTransition(Instant instant) {
+ if (savingsInstantTransitions.length == 0) {
+ return null;
+ }
+ long epochSec = instant.getEpochSecond();
+ if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) {
+ epochSec += 1; // allow rest of method to only use seconds
+ }
+
+ // check if using last rules
+ long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1];
+ if (lastRules.length > 0 && epochSec > lastHistoric) {
+ // search year the instant is in
+ ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1];
+ int year = findYear(epochSec, lastHistoricOffset);
+ ZoneOffsetTransition[] transArray = findTransitionArray(year);
+ for (int i = transArray.length - 1; i >= 0; i--) {
+ if (epochSec > transArray[i].toEpochSecond()) {
+ return transArray[i];
+ }
+ }
+ // use last from preceeding year
+ int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset);
+ if (--year > lastHistoricYear) {
+ transArray = findTransitionArray(year);
+ return transArray[transArray.length - 1];
+ }
+ // drop through
+ }
+
+ // using historic rules
+ int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
+ if (index < 0) {
+ index = -index - 1;
+ }
+ if (index <= 0) {
+ return null;
+ }
+ return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]);
+ }
+
+ private int findYear(long epochSecond, ZoneOffset offset) {
+ // inline for performance
+ long localSecond = epochSecond + offset.getTotalSeconds();
+ long localEpochDay = Math.floorDiv(localSecond, 86400);
+ return LocalDate.ofEpochDay(localEpochDay).getYear();
+ }
+
+ /**
+ * Gets the complete list of fully defined transitions.
+ *
+ * The complete set of transitions for this rules instance is defined by this method
+ * and {@link #getTransitionRules()}. This method returns those transitions that have
+ * been fully defined. These are typically historical, but may be in the future.
+ *
+ * The list will be empty for fixed offset rules and for any time-zone where there has
+ * only ever been a single offset. The list will also be empty if the transition rules are unknown.
+ *
+ * @return an immutable list of fully defined transitions, not null
+ */
+ public List
+ * The complete set of transitions for this rules instance is defined by this method
+ * and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule}
+ * that define an algorithm for when transitions will occur.
+ *
+ * For any given {@code ZoneRules}, this list contains the transition rules for years
+ * beyond those years that have been fully defined. These rules typically refer to future
+ * daylight saving time rule changes.
+ *
+ * If the zone defines daylight savings into the future, then the list will normally
+ * be of size two and hold information about entering and exiting daylight savings.
+ * If the zone does not have daylight savings, or information about future changes
+ * is uncertain, then the list will be empty.
+ *
+ * The list will be empty for fixed offset rules and for any time-zone where there is no
+ * daylight saving time. The list will also be empty if the transition rules are unknown.
+ *
+ * @return an immutable list of transition rules, not null
+ */
+ public List
+ * Two rule sets are equal if they will always result in the same output
+ * for any given input instant or local date-time.
+ * Rules from two different groups may return false even if they are in fact the same.
+ *
+ * This definition should result in implementations comparing their entire state.
+ *
+ * @param otherRules the other rules, null returns false
+ * @return true if this rules is the same as that specified
+ */
+ @Override
+ public boolean equals(Object otherRules) {
+ if (this == otherRules) {
+ return true;
+ }
+ if (otherRules instanceof ZoneRules) {
+ ZoneRules other = (ZoneRules) otherRules;
+ return Arrays.equals(standardTransitions, other.standardTransitions) &&
+ Arrays.equals(standardOffsets, other.standardOffsets) &&
+ Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) &&
+ Arrays.equals(wallOffsets, other.wallOffsets) &&
+ Arrays.equals(lastRules, other.lastRules);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code given the definition of {@code #equals}.
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(standardTransitions) ^
+ Arrays.hashCode(standardOffsets) ^
+ Arrays.hashCode(savingsInstantTransitions) ^
+ Arrays.hashCode(wallOffsets) ^
+ Arrays.hashCode(lastRules);
+ }
+
+ /**
+ * Returns a string describing this object.
+ *
+ * @return a string for debugging, not null
+ */
+ @Override
+ public String toString() {
+ return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]";
+ }
+
+}
+ *
+ * ZoneOffsetTransition trans = rules.getTransition(localDT);
+ * if (trans == null) {
+ * // Gap or Overlap: determine what to do from transition
+ * } else {
+ * // Normal case: only one valid offset
+ * zoneOffset = rule.getOffset(localDT);
+ * }
+ *
+ *
+ * @param localDateTime the local date-time to query for offset transition, not null, but null
+ * may be ignored if the rules have a single offset for all instants
+ * @return the offset transition, null if the local date-time is not in transition
+ */
+ public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {
+ Object info = getOffsetInfo(localDateTime);
+ return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null);
+ }
+
+ private Object getOffsetInfo(LocalDateTime dt) {
+ if (savingsInstantTransitions.length == 0) {
+ return standardOffsets[0];
+ }
+ // check if using last rules
+ if (lastRules.length > 0 &&
+ dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) {
+ ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear());
+ Object info = null;
+ for (ZoneOffsetTransition trans : transArray) {
+ info = findOffsetInfo(dt, trans);
+ if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) {
+ return info;
+ }
+ }
+ return info;
+ }
+
+ // using historic rules
+ int index = Arrays.binarySearch(savingsLocalTransitions, dt);
+ if (index == -1) {
+ // before first transition
+ return wallOffsets[0];
+ }
+ if (index < 0) {
+ // switch negative insert position to start of matched range
+ index = -index - 2;
+ } else if (index < savingsLocalTransitions.length - 1 &&
+ savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) {
+ // handle overlap immediately following gap
+ index++;
+ }
+ if ((index & 1) == 0) {
+ // gap or overlap
+ LocalDateTime dtBefore = savingsLocalTransitions[index];
+ LocalDateTime dtAfter = savingsLocalTransitions[index + 1];
+ ZoneOffset offsetBefore = wallOffsets[index / 2];
+ ZoneOffset offsetAfter = wallOffsets[index / 2 + 1];
+ if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) {
+ // gap
+ return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter);
+ } else {
+ // overlap
+ return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter);
+ }
+ } else {
+ // normal (neither gap or overlap)
+ return wallOffsets[index / 2 + 1];
+ }
+ }
+
+ /**
+ * Finds the offset info for a local date-time and transition.
+ *
+ * @param dt the date-time, not null
+ * @param trans the transition, not null
+ * @return the offset info, not null
+ */
+ private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) {
+ LocalDateTime localTransition = trans.getDateTimeBefore();
+ if (trans.isGap()) {
+ if (dt.isBefore(localTransition)) {
+ return trans.getOffsetBefore();
+ }
+ if (dt.isBefore(trans.getDateTimeAfter())) {
+ return trans;
+ } else {
+ return trans.getOffsetAfter();
+ }
+ } else {
+ if (dt.isBefore(localTransition) == false) {
+ return trans.getOffsetAfter();
+ }
+ if (dt.isBefore(trans.getDateTimeAfter())) {
+ return trans.getOffsetBefore();
+ } else {
+ return trans;
+ }
+ }
+ }
+
+ /**
+ * Finds the appropriate transition array for the given year.
+ *
+ * @param year the year, not null
+ * @return the transition array, not null
+ */
+ private ZoneOffsetTransition[] findTransitionArray(int year) {
+ Integer yearObj = year; // should use Year class, but this saves a class load
+ ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj);
+ if (transArray != null) {
+ return transArray;
+ }
+ ZoneOffsetTransitionRule[] ruleArray = lastRules;
+ transArray = new ZoneOffsetTransition[ruleArray.length];
+ for (int i = 0; i < ruleArray.length; i++) {
+ transArray[i] = ruleArray[i].createTransition(year);
+ }
+ if (year < LAST_CACHED_YEAR) {
+ lastRulesCache.putIfAbsent(yearObj, transArray);
+ }
+ return transArray;
+ }
+
+ /**
+ * Gets the standard offset for the specified instant in this zone.
+ *