771 lines
28 KiB
Java
771 lines
28 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
// © 2016 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 2007-2014, International Business Machines Corporation and *
|
|
* others. All Rights Reserved. *
|
|
*******************************************************************************
|
|
*/
|
|
package android.icu.util;
|
|
import java.util.ArrayList;
|
|
import java.util.BitSet;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
|
|
import android.icu.impl.Grego;
|
|
|
|
/**
|
|
* <code>RuleBasedTimeZone</code> is a concrete subclass of <code>TimeZone</code> that allows users to define
|
|
* custom historic time transition rules.
|
|
*
|
|
* @see android.icu.util.TimeZoneRule
|
|
*
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public class RuleBasedTimeZone extends BasicTimeZone {
|
|
|
|
private static final long serialVersionUID = 7580833058949327935L;
|
|
|
|
private final InitialTimeZoneRule initialRule;
|
|
private List<TimeZoneRule> historicRules;
|
|
private AnnualTimeZoneRule[] finalRules;
|
|
|
|
private transient List<TimeZoneTransition> historicTransitions;
|
|
private transient boolean upToDate;
|
|
|
|
/**
|
|
* Constructs a <code>RuleBasedTimeZone</code> object with the ID and the
|
|
* <code>InitialTimeZoneRule</code>
|
|
*
|
|
* @param id The time zone ID.
|
|
* @param initialRule The initial time zone rule.
|
|
*/
|
|
public RuleBasedTimeZone(String id, InitialTimeZoneRule initialRule) {
|
|
super(id);
|
|
this.initialRule = initialRule;
|
|
}
|
|
|
|
/**
|
|
* Adds the <code>TimeZoneRule</code> which represents time transitions.
|
|
* The <code>TimeZoneRule</code> must have start times, that is, the result
|
|
* of {@link android.icu.util.TimeZoneRule#isTransitionRule()} must be true.
|
|
* Otherwise, <code>IllegalArgumentException</code> is thrown.
|
|
*
|
|
* @param rule The <code>TimeZoneRule</code>.
|
|
*/
|
|
public void addTransitionRule(TimeZoneRule rule) {
|
|
if (isFrozen()) {
|
|
throw new UnsupportedOperationException("Attempt to modify a frozen RuleBasedTimeZone instance.");
|
|
}
|
|
if (!rule.isTransitionRule()) {
|
|
throw new IllegalArgumentException("Rule must be a transition rule");
|
|
}
|
|
if (rule instanceof AnnualTimeZoneRule
|
|
&& ((AnnualTimeZoneRule)rule).getEndYear() == AnnualTimeZoneRule.MAX_YEAR) {
|
|
// One of the final rules applicable in future forever
|
|
if (finalRules == null) {
|
|
finalRules = new AnnualTimeZoneRule[2];
|
|
finalRules[0] = (AnnualTimeZoneRule)rule;
|
|
} else if (finalRules[1] == null) {
|
|
finalRules[1] = (AnnualTimeZoneRule)rule;
|
|
} else {
|
|
// Only a pair of AnnualTimeZoneRule is allowed.
|
|
throw new IllegalStateException("Too many final rules");
|
|
}
|
|
} else {
|
|
// If this is not a final rule, add it to the historic rule list
|
|
if (historicRules == null) {
|
|
historicRules = new ArrayList<>();
|
|
}
|
|
historicRules.add(rule);
|
|
}
|
|
// Mark dirty, so transitions are recalculated when offset information is
|
|
// accessed next time.
|
|
upToDate = false;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public int getOffset(int era, int year, int month, int day, int dayOfWeek,
|
|
int milliseconds) {
|
|
if (era == GregorianCalendar.BC) {
|
|
// Convert to extended year
|
|
year = 1 - year;
|
|
}
|
|
long time = Grego.fieldsToDay(year, month, day) * Grego.MILLIS_PER_DAY + milliseconds;
|
|
int[] offsets = new int[2];
|
|
getOffset(time, true, LOCAL_DST, LOCAL_STD, offsets);
|
|
return (offsets[0] + offsets[1]);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void getOffset(long time, boolean local, int[] offsets) {
|
|
getOffset(time, local, LOCAL_FORMER, LOCAL_LATTER, offsets);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void getOffsetFromLocal(long date,
|
|
LocalOption nonExistingTimeOpt, LocalOption duplicatedTimeOpt, int[] offsets) {
|
|
int nonExistingTimeOptVal = getLocalOptionValue(nonExistingTimeOpt);
|
|
int duplicatedTimeOptVal = getLocalOptionValue(duplicatedTimeOpt);
|
|
getOffset(date, true, nonExistingTimeOptVal, duplicatedTimeOptVal, offsets);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public int getRawOffset() {
|
|
// Note: This implementation returns standard GMT offset
|
|
// as of current time.
|
|
long now = System.currentTimeMillis();
|
|
int[] offsets = new int[2];
|
|
getOffset(now, false, offsets);
|
|
return offsets[0];
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean inDaylightTime(Date date) {
|
|
int[] offsets = new int[2];
|
|
getOffset(date.getTime(), false, offsets);
|
|
return (offsets[1] != 0);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
///CLOVER:OFF
|
|
public void setRawOffset(int offsetMillis) {
|
|
// TODO: Do nothing for now..
|
|
throw new UnsupportedOperationException("setRawOffset in RuleBasedTimeZone is not supported.");
|
|
}
|
|
///CLOVER:ON
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean useDaylightTime() {
|
|
// Note: This implementation returns true when
|
|
// daylight saving time is used as of now or
|
|
// after the next transition.
|
|
long now = System.currentTimeMillis();
|
|
int[] offsets = new int[2];
|
|
getOffset(now, false, offsets);
|
|
if (offsets[1] != 0) {
|
|
return true;
|
|
}
|
|
// If DST is not used now, check if DST is used after the next transition
|
|
TimeZoneTransition tt = getNextTransition(now, false);
|
|
if (tt != null && tt.getTo().getDSTSavings() != 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean observesDaylightTime() {
|
|
long time = System.currentTimeMillis();
|
|
|
|
// Check if daylight saving time is observed now.
|
|
int[] offsets = new int[2];
|
|
getOffset(time, false, offsets);
|
|
if (offsets[1] != 0) {
|
|
return true;
|
|
}
|
|
|
|
// If DST is not used now, check if DST is used after each transition.
|
|
BitSet checkFinals = finalRules == null ? null : new BitSet(finalRules.length);
|
|
while (true) {
|
|
TimeZoneTransition tt = getNextTransition(time, false);
|
|
if (tt == null) {
|
|
// no more transition
|
|
break;
|
|
}
|
|
TimeZoneRule toRule = tt.getTo();
|
|
if (toRule.getDSTSavings() != 0) {
|
|
return true;
|
|
}
|
|
if (checkFinals != null) {
|
|
// final rules exist - check if we saw all of them
|
|
for (int i = 0; i < finalRules.length; i++) {
|
|
if (finalRules[i].equals(toRule)) {
|
|
checkFinals.set(i);
|
|
}
|
|
}
|
|
if (checkFinals.cardinality() == finalRules.length) {
|
|
// already saw all final rules
|
|
break;
|
|
}
|
|
}
|
|
time = tt.getTime();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean hasSameRules(TimeZone other) {
|
|
if (this == other) {
|
|
return true;
|
|
}
|
|
|
|
if (!(other instanceof RuleBasedTimeZone)) {
|
|
// We cannot reasonably compare rules in different types
|
|
return false;
|
|
}
|
|
RuleBasedTimeZone otherRBTZ = (RuleBasedTimeZone)other;
|
|
|
|
// initial rule
|
|
if (!initialRule.isEquivalentTo(otherRBTZ.initialRule)) {
|
|
return false;
|
|
}
|
|
|
|
// final rules
|
|
if (finalRules != null && otherRBTZ.finalRules != null) {
|
|
for (int i = 0; i < finalRules.length; i++) {
|
|
if (finalRules[i] == null && otherRBTZ.finalRules[i] == null) {
|
|
continue;
|
|
}
|
|
if (finalRules[i] != null && otherRBTZ.finalRules[i] != null
|
|
&& finalRules[i].isEquivalentTo(otherRBTZ.finalRules[i])) {
|
|
continue;
|
|
|
|
}
|
|
return false;
|
|
}
|
|
} else if (finalRules != null || otherRBTZ.finalRules != null) {
|
|
return false;
|
|
}
|
|
|
|
// historic rules
|
|
if (historicRules != null && otherRBTZ.historicRules != null) {
|
|
if (historicRules.size() != otherRBTZ.historicRules.size()) {
|
|
return false;
|
|
}
|
|
for (TimeZoneRule rule : historicRules) {
|
|
boolean foundSameRule = false;
|
|
for (TimeZoneRule orule : otherRBTZ.historicRules) {
|
|
if (rule.isEquivalentTo(orule)) {
|
|
foundSameRule = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundSameRule) {
|
|
return false;
|
|
}
|
|
}
|
|
} else if (historicRules != null || otherRBTZ.historicRules != null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// BasicTimeZone methods
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public TimeZoneRule[] getTimeZoneRules() {
|
|
int size = 1;
|
|
if (historicRules != null) {
|
|
size += historicRules.size();
|
|
}
|
|
|
|
if (finalRules != null) {
|
|
if (finalRules[1] != null) {
|
|
size += 2;
|
|
} else {
|
|
size++;
|
|
}
|
|
}
|
|
TimeZoneRule[] rules = new TimeZoneRule[size];
|
|
rules[0] = initialRule;
|
|
|
|
int idx = 1;
|
|
if (historicRules != null) {
|
|
for (; idx < historicRules.size() + 1; idx++) {
|
|
rules[idx] = historicRules.get(idx - 1);
|
|
}
|
|
}
|
|
if (finalRules != null) {
|
|
rules[idx++] = finalRules[0];
|
|
if (finalRules[1] != null) {
|
|
rules[idx] = finalRules[1];
|
|
}
|
|
}
|
|
return rules;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public TimeZoneTransition getNextTransition(long base, boolean inclusive) {
|
|
complete();
|
|
if (historicTransitions == null) {
|
|
return null;
|
|
}
|
|
boolean isFinal = false;
|
|
TimeZoneTransition result;
|
|
TimeZoneTransition tzt = historicTransitions.get(0);
|
|
long tt = tzt.getTime();
|
|
if (tt > base || (inclusive && tt == base)) {
|
|
result = tzt;
|
|
} else {
|
|
int idx = historicTransitions.size() - 1;
|
|
tzt = historicTransitions.get(idx);
|
|
tt = tzt.getTime();
|
|
if (inclusive && tt == base) {
|
|
result = tzt;
|
|
} else if (tt <= base) {
|
|
if (finalRules != null) {
|
|
// Find a transion time with finalRules
|
|
Date start0 = finalRules[0].getNextStart(base,
|
|
finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), inclusive);
|
|
Date start1 = finalRules[1].getNextStart(base,
|
|
finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), inclusive);
|
|
|
|
if (start1.after(start0)) {
|
|
tzt = new TimeZoneTransition(start0.getTime(), finalRules[1], finalRules[0]);
|
|
} else {
|
|
tzt = new TimeZoneTransition(start1.getTime(), finalRules[0], finalRules[1]);
|
|
}
|
|
result = tzt;
|
|
isFinal = true;
|
|
} else {
|
|
return null;
|
|
}
|
|
} else {
|
|
// Find a transition within the historic transitions
|
|
idx--;
|
|
TimeZoneTransition prev = tzt;
|
|
while (idx > 0) {
|
|
tzt = historicTransitions.get(idx);
|
|
tt = tzt.getTime();
|
|
if (tt < base || (!inclusive && tt == base)) {
|
|
break;
|
|
}
|
|
idx--;
|
|
prev = tzt;
|
|
}
|
|
result = prev;
|
|
}
|
|
}
|
|
// For now, this implementation ignore transitions with only zone name changes.
|
|
TimeZoneRule from = result.getFrom();
|
|
TimeZoneRule to = result.getTo();
|
|
if (from.getRawOffset() == to.getRawOffset()
|
|
&& from.getDSTSavings() == to.getDSTSavings()) {
|
|
// No offset changes. Try next one if not final
|
|
if (isFinal) {
|
|
return null;
|
|
} else {
|
|
result = getNextTransition(result.getTime(), false /* always exclusive */);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) {
|
|
complete();
|
|
if (historicTransitions == null) {
|
|
return null;
|
|
}
|
|
TimeZoneTransition result;
|
|
TimeZoneTransition tzt = historicTransitions.get(0);
|
|
long tt = tzt.getTime();
|
|
if (inclusive && tt == base) {
|
|
result = tzt;
|
|
} else if (tt >= base) {
|
|
return null;
|
|
} else {
|
|
int idx = historicTransitions.size() - 1;
|
|
tzt = historicTransitions.get(idx);
|
|
tt = tzt.getTime();
|
|
if (inclusive && tt == base) {
|
|
result = tzt;
|
|
} else if (tt < base) {
|
|
if (finalRules != null) {
|
|
// Find a transion time with finalRules
|
|
Date start0 = finalRules[0].getPreviousStart(base,
|
|
finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), inclusive);
|
|
Date start1 = finalRules[1].getPreviousStart(base,
|
|
finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), inclusive);
|
|
|
|
if (start1.before(start0)) {
|
|
tzt = new TimeZoneTransition(start0.getTime(), finalRules[1], finalRules[0]);
|
|
} else {
|
|
tzt = new TimeZoneTransition(start1.getTime(), finalRules[0], finalRules[1]);
|
|
}
|
|
}
|
|
result = tzt;
|
|
} else {
|
|
// Find a transition within the historic transitions
|
|
idx--;
|
|
while (idx >= 0) {
|
|
tzt = historicTransitions.get(idx);
|
|
tt = tzt.getTime();
|
|
if (tt < base || (inclusive && tt == base)) {
|
|
break;
|
|
}
|
|
idx--;
|
|
}
|
|
result = tzt;
|
|
}
|
|
}
|
|
// For now, this implementation ignore transitions with only zone name changes.
|
|
TimeZoneRule from = result.getFrom();
|
|
TimeZoneRule to = result.getTo();
|
|
if (from.getRawOffset() == to.getRawOffset()
|
|
&& from.getDSTSavings() == to.getDSTSavings()) {
|
|
// No offset changes. Try previous one
|
|
result = getPreviousTransition(result.getTime(), false /* always exclusive */);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Object clone() {
|
|
if (isFrozen()) {
|
|
return this;
|
|
}
|
|
return cloneAsThawed();
|
|
}
|
|
|
|
// private stuff
|
|
|
|
/*
|
|
* Resolve historic transition times and update fields used for offset
|
|
* calculation.
|
|
*/
|
|
private void complete() {
|
|
if (upToDate) {
|
|
// No rules were added since last time.
|
|
return;
|
|
}
|
|
|
|
// Make sure either no final rules or a pair of AnnualTimeZoneRules
|
|
// are available.
|
|
if (finalRules != null && finalRules[1] == null) {
|
|
throw new IllegalStateException("Incomplete final rules");
|
|
}
|
|
|
|
// Create a TimezoneTransition and add to the list
|
|
if (historicRules != null || finalRules != null) {
|
|
TimeZoneRule curRule = initialRule;
|
|
long lastTransitionTime = Grego.MIN_MILLIS;
|
|
|
|
// Build the transition array which represents historical time zone
|
|
// transitions.
|
|
if (historicRules != null) {
|
|
BitSet done = new BitSet(historicRules.size()); // for skipping rules already processed
|
|
|
|
while (true) {
|
|
int curStdOffset = curRule.getRawOffset();
|
|
int curDstSavings = curRule.getDSTSavings();
|
|
long nextTransitionTime = Grego.MAX_MILLIS;
|
|
TimeZoneRule nextRule = null;
|
|
Date d;
|
|
long tt;
|
|
|
|
for (int i = 0; i < historicRules.size(); i++) {
|
|
if (done.get(i)) {
|
|
continue;
|
|
}
|
|
TimeZoneRule r = historicRules.get(i);
|
|
d = r.getNextStart(lastTransitionTime, curStdOffset, curDstSavings, false);
|
|
if (d == null) {
|
|
// No more transitions from this rule - skip this rule next time
|
|
done.set(i);
|
|
} else {
|
|
if (r == curRule ||
|
|
(r.getName().equals(curRule.getName())
|
|
&& r.getRawOffset() == curRule.getRawOffset()
|
|
&& r.getDSTSavings() == curRule.getDSTSavings())) {
|
|
continue;
|
|
}
|
|
tt = d.getTime();
|
|
if (tt < nextTransitionTime) {
|
|
nextTransitionTime = tt;
|
|
nextRule = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextRule == null) {
|
|
// Check if all historic rules are done
|
|
boolean bDoneAll = true;
|
|
for (int j = 0; j < historicRules.size(); j++) {
|
|
if (!done.get(j)) {
|
|
bDoneAll = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bDoneAll) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (finalRules != null) {
|
|
// Check if one of final rules has earlier transition date
|
|
for (int i = 0; i < 2 /* finalRules.length */; i++) {
|
|
if (finalRules[i] == curRule) {
|
|
continue;
|
|
}
|
|
d = finalRules[i].getNextStart(lastTransitionTime, curStdOffset, curDstSavings, false);
|
|
if (d != null) {
|
|
tt = d.getTime();
|
|
if (tt < nextTransitionTime) {
|
|
nextTransitionTime = tt;
|
|
nextRule = finalRules[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextRule == null) {
|
|
// Nothing more
|
|
break;
|
|
}
|
|
|
|
if (historicTransitions == null) {
|
|
historicTransitions = new ArrayList<>();
|
|
}
|
|
historicTransitions.add(new TimeZoneTransition(nextTransitionTime, curRule, nextRule));
|
|
lastTransitionTime = nextTransitionTime;
|
|
curRule = nextRule;
|
|
}
|
|
}
|
|
if (finalRules != null) {
|
|
if (historicTransitions == null) {
|
|
historicTransitions = new ArrayList<>();
|
|
}
|
|
// Append the first transition for each
|
|
Date d0 = finalRules[0].getNextStart(lastTransitionTime, curRule.getRawOffset(), curRule.getDSTSavings(), false);
|
|
Date d1 = finalRules[1].getNextStart(lastTransitionTime, curRule.getRawOffset(), curRule.getDSTSavings(), false);
|
|
if (d1.after(d0)) {
|
|
historicTransitions.add(new TimeZoneTransition(d0.getTime(), curRule, finalRules[0]));
|
|
d1 = finalRules[1].getNextStart(d0.getTime(), finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), false);
|
|
historicTransitions.add(new TimeZoneTransition(d1.getTime(), finalRules[0], finalRules[1]));
|
|
} else {
|
|
historicTransitions.add(new TimeZoneTransition(d1.getTime(), curRule, finalRules[1]));
|
|
d0 = finalRules[0].getNextStart(d1.getTime(), finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), false);
|
|
historicTransitions.add(new TimeZoneTransition(d0.getTime(), finalRules[1], finalRules[0]));
|
|
}
|
|
}
|
|
}
|
|
upToDate = true;
|
|
}
|
|
|
|
/*
|
|
* getOffset internal implementation
|
|
*/
|
|
private void getOffset(long time, boolean local, int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets) {
|
|
complete();
|
|
TimeZoneRule rule = null;
|
|
if (historicTransitions == null) {
|
|
rule = initialRule;
|
|
} else {
|
|
long tstart = getTransitionTime(historicTransitions.get(0),
|
|
local, NonExistingTimeOpt, DuplicatedTimeOpt);
|
|
if (time < tstart) {
|
|
rule = initialRule;
|
|
} else {
|
|
int idx = historicTransitions.size() - 1;
|
|
long tend = getTransitionTime(historicTransitions.get(idx),
|
|
local, NonExistingTimeOpt, DuplicatedTimeOpt);
|
|
if (time > tend) {
|
|
if (finalRules != null) {
|
|
rule = findRuleInFinal(time, local, NonExistingTimeOpt, DuplicatedTimeOpt);
|
|
}
|
|
if (rule == null) {
|
|
// no final rules or the given time is before the first transition
|
|
// specified by the final rules -> use the last rule
|
|
rule = (historicTransitions.get(idx)).getTo();
|
|
}
|
|
} else {
|
|
// Find a historical transition
|
|
while (idx >= 0) {
|
|
if (time >= getTransitionTime(historicTransitions.get(idx),
|
|
local, NonExistingTimeOpt, DuplicatedTimeOpt)) {
|
|
break;
|
|
}
|
|
idx--;
|
|
}
|
|
rule = (historicTransitions.get(idx)).getTo();
|
|
}
|
|
}
|
|
}
|
|
offsets[0] = rule.getRawOffset();
|
|
offsets[1] = rule.getDSTSavings();
|
|
}
|
|
|
|
/*
|
|
* Find a time zone rule applicable to the specified time
|
|
*/
|
|
private TimeZoneRule findRuleInFinal(long time, boolean local, int NonExistingTimeOpt, int DuplicatedTimeOpt) {
|
|
if (finalRules == null || finalRules.length != 2 || finalRules[0] == null || finalRules[1] == null) {
|
|
return null;
|
|
}
|
|
|
|
Date start0, start1;
|
|
long base;
|
|
int localDelta;
|
|
|
|
base = time;
|
|
if (local) {
|
|
localDelta = getLocalDelta(finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(),
|
|
finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(),
|
|
NonExistingTimeOpt, DuplicatedTimeOpt);
|
|
base -= localDelta;
|
|
}
|
|
start0 = finalRules[0].getPreviousStart(base, finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(), true);
|
|
|
|
base = time;
|
|
if (local) {
|
|
localDelta = getLocalDelta(finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(),
|
|
finalRules[1].getRawOffset(), finalRules[1].getDSTSavings(),
|
|
NonExistingTimeOpt, DuplicatedTimeOpt);
|
|
base -= localDelta;
|
|
}
|
|
start1 = finalRules[1].getPreviousStart(base, finalRules[0].getRawOffset(), finalRules[0].getDSTSavings(), true);
|
|
|
|
if (start0 == null || start1 == null) {
|
|
if (start0 != null) {
|
|
return finalRules[0];
|
|
} else if (start1 != null) {
|
|
return finalRules[1];
|
|
}
|
|
// Both rules take effect after the given time
|
|
return null;
|
|
}
|
|
|
|
return start0.after(start1) ? finalRules[0] : finalRules[1];
|
|
}
|
|
|
|
/*
|
|
* Get the transition time in local wall clock
|
|
*/
|
|
private static long getTransitionTime(TimeZoneTransition tzt, boolean local,
|
|
int NonExistingTimeOpt, int DuplicatedTimeOpt) {
|
|
long time = tzt.getTime();
|
|
if (local) {
|
|
time += getLocalDelta(tzt.getFrom().getRawOffset(), tzt.getFrom().getDSTSavings(),
|
|
tzt.getTo().getRawOffset(), tzt.getTo().getDSTSavings(),
|
|
NonExistingTimeOpt, DuplicatedTimeOpt);
|
|
}
|
|
return time;
|
|
}
|
|
|
|
/*
|
|
* Returns amount of local time adjustment used for checking rule transitions
|
|
*/
|
|
private static int getLocalDelta(int rawBefore, int dstBefore, int rawAfter, int dstAfter,
|
|
int NonExistingTimeOpt, int DuplicatedTimeOpt) {
|
|
int delta = 0;
|
|
|
|
int offsetBefore = rawBefore + dstBefore;
|
|
int offsetAfter = rawAfter + dstAfter;
|
|
|
|
boolean dstToStd = (dstBefore != 0) && (dstAfter == 0);
|
|
boolean stdToDst = (dstBefore == 0) && (dstAfter != 0);
|
|
|
|
if (offsetAfter - offsetBefore >= 0) {
|
|
// Positive transition, which makes a non-existing local time range
|
|
if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd)
|
|
|| ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) {
|
|
delta = offsetBefore;
|
|
} else if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst)
|
|
|| ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) {
|
|
delta = offsetAfter;
|
|
} else if ((NonExistingTimeOpt & FORMER_LATTER_MASK) == LOCAL_LATTER) {
|
|
delta = offsetBefore;
|
|
} else {
|
|
// Interprets the time with rule before the transition,
|
|
// default for non-existing time range
|
|
delta = offsetAfter;
|
|
}
|
|
} else {
|
|
// Negative transition, which makes a duplicated local time range
|
|
if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd)
|
|
|| ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) {
|
|
delta = offsetAfter;
|
|
} else if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst)
|
|
|| ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) {
|
|
delta = offsetBefore;
|
|
} else if ((DuplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) {
|
|
delta = offsetBefore;
|
|
} else {
|
|
// Interprets the time with rule after the transition,
|
|
// default for duplicated local time range
|
|
delta = offsetAfter;
|
|
}
|
|
}
|
|
return delta;
|
|
}
|
|
|
|
// Freezable stuffs
|
|
private volatile transient boolean isFrozen = false;
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean isFrozen() {
|
|
return isFrozen;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public TimeZone freeze() {
|
|
complete();
|
|
isFrozen = true;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public TimeZone cloneAsThawed() {
|
|
RuleBasedTimeZone tz = (RuleBasedTimeZone)super.cloneAsThawed();
|
|
if (historicRules != null) {
|
|
tz.historicRules = new ArrayList<>(historicRules); // rules are immutable
|
|
}
|
|
if (finalRules != null) {
|
|
tz.finalRules = finalRules.clone();
|
|
}
|
|
tz.isFrozen = false;
|
|
return tz;
|
|
}
|
|
}
|