mirror of
https://github.com/konsoletyper/teavm.git
synced 2024-11-27 01:30:35 +08:00
Borrow some code from Joda Time to parse tz data and expose offsets. Add
tz data archive
This commit is contained in:
parent
b264e34ef8
commit
13fbf7c544
12
NOTICE
12
NOTICE
@ -1,2 +1,12 @@
|
|||||||
|
This product includes software developed by Alexey Andreev
|
||||||
|
(http://teavm.org).
|
||||||
|
|
||||||
|
|
||||||
This product includes software developed by The Apache Software
|
This product includes software developed by The Apache Software
|
||||||
Foundation (http://www.apache.org/).
|
Foundation (http://www.apache.org/).
|
||||||
|
|
||||||
|
=============================================================================
|
||||||
|
= NOTICE file corresponding to section 4d of the Apache License Version 2.0 =
|
||||||
|
=============================================================================
|
||||||
|
This product includes software developed by
|
||||||
|
Joda.org (http://www.joda.org/).
|
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2001-2012 Stephen Colebourne
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.impl.tz;
|
||||||
|
|
||||||
|
import org.teavm.classlib.impl.Base46;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Improves the performance of requesting time zone offsets and name keys by
|
||||||
|
* caching the results. Time zones that have simple rules or are fixed should
|
||||||
|
* not be cached, as it is unlikely to improve performance.
|
||||||
|
* <p>
|
||||||
|
* CachedDateTimeZone is thread-safe and immutable.
|
||||||
|
*
|
||||||
|
* @author Brian S O'Neill
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public class CachedDateTimeZone extends StorableDateTimeZone {
|
||||||
|
|
||||||
|
private static final int cInfoCacheMask;
|
||||||
|
|
||||||
|
static {
|
||||||
|
cInfoCacheMask = 511;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new CachedDateTimeZone unless given zone is already cached.
|
||||||
|
*/
|
||||||
|
public static CachedDateTimeZone forZone(StorableDateTimeZone zone) {
|
||||||
|
if (zone instanceof CachedDateTimeZone) {
|
||||||
|
return (CachedDateTimeZone)zone;
|
||||||
|
}
|
||||||
|
return new CachedDateTimeZone(zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Caching is performed by breaking timeline down into periods of 2^32
|
||||||
|
* milliseconds, or about 49.7 days. A year has about 7.3 periods, usually
|
||||||
|
* with only 2 time zone offset periods. Most of the 49.7 day periods will
|
||||||
|
* have no transition, about one quarter have one transition, and very rare
|
||||||
|
* cases have multiple transitions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private final StorableDateTimeZone iZone;
|
||||||
|
|
||||||
|
private final transient Info[] iInfoCache = new Info[cInfoCacheMask + 1];
|
||||||
|
|
||||||
|
private CachedDateTimeZone(StorableDateTimeZone zone) {
|
||||||
|
super(zone.getID());
|
||||||
|
iZone = zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(StringBuilder sb) {
|
||||||
|
Base46.encodeUnsigned(sb, CACHED);
|
||||||
|
iZone.write(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the DateTimeZone being wrapped.
|
||||||
|
*/
|
||||||
|
public DateTimeZone getUncachedZone() {
|
||||||
|
return iZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset(long instant) {
|
||||||
|
return getInfo(instant).getOffset(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStandardOffset(long instant) {
|
||||||
|
return getInfo(instant).getStandardOffset(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFixed() {
|
||||||
|
return iZone.isFixed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long nextTransition(long instant) {
|
||||||
|
return iZone.nextTransition(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long previousTransition(long instant) {
|
||||||
|
return iZone.previousTransition(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Although accessed by multiple threads, this method doesn't need to be
|
||||||
|
// synchronized.
|
||||||
|
|
||||||
|
private Info getInfo(long millis) {
|
||||||
|
int period = (int)(millis >> 32);
|
||||||
|
Info[] cache = iInfoCache;
|
||||||
|
int index = period & cInfoCacheMask;
|
||||||
|
Info info = cache[index];
|
||||||
|
if (info == null || (int)((info.iPeriodStart >> 32)) != period) {
|
||||||
|
info = createInfo(millis);
|
||||||
|
cache[index] = info;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Info createInfo(long millis) {
|
||||||
|
long periodStart = millis & (0xffffffffL << 32);
|
||||||
|
Info info = new Info(iZone, periodStart);
|
||||||
|
|
||||||
|
long end = periodStart | 0xffffffffL;
|
||||||
|
Info chain = info;
|
||||||
|
while (true) {
|
||||||
|
long next = iZone.nextTransition(periodStart);
|
||||||
|
if (next == periodStart || next > end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
periodStart = next;
|
||||||
|
chain = (chain.iNextInfo = new Info(iZone, periodStart));
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static class Info {
|
||||||
|
// For first Info in chain, iPeriodStart's lower 32 bits are clear.
|
||||||
|
public final long iPeriodStart;
|
||||||
|
public final DateTimeZone iZoneRef;
|
||||||
|
|
||||||
|
Info iNextInfo;
|
||||||
|
|
||||||
|
private int iOffset = Integer.MIN_VALUE;
|
||||||
|
private int iStandardOffset = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
Info(DateTimeZone zone, long periodStart) {
|
||||||
|
iPeriodStart = periodStart;
|
||||||
|
iZoneRef = zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset(long millis) {
|
||||||
|
if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
|
||||||
|
if (iOffset == Integer.MIN_VALUE) {
|
||||||
|
iOffset = iZoneRef.getOffset(iPeriodStart);
|
||||||
|
}
|
||||||
|
return iOffset;
|
||||||
|
}
|
||||||
|
return iNextInfo.getOffset(millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStandardOffset(long millis) {
|
||||||
|
if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
|
||||||
|
if (iStandardOffset == Integer.MIN_VALUE) {
|
||||||
|
iStandardOffset = iZoneRef.getStandardOffset(iPeriodStart);
|
||||||
|
}
|
||||||
|
return iStandardOffset;
|
||||||
|
}
|
||||||
|
return iNextInfo.getStandardOffset(millis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,500 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2001-2014 Stephen Colebourne
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.impl.tz;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateTimeZone represents a time zone.
|
||||||
|
* <p>
|
||||||
|
* A time zone is a system of rules to convert time from one geographic
|
||||||
|
* location to another. For example, Paris, France is one hour ahead of
|
||||||
|
* London, England. Thus when it is 10:00 in London, it is 11:00 in Paris.
|
||||||
|
* <p>
|
||||||
|
* All time zone rules are expressed, for historical reasons, relative to
|
||||||
|
* Greenwich, London. Local time in Greenwich is referred to as Greenwich Mean
|
||||||
|
* Time (GMT). This is similar, but not precisely identical, to Universal
|
||||||
|
* Coordinated Time, or UTC. This library only uses the term UTC.
|
||||||
|
* <p>
|
||||||
|
* Using this system, America/Los_Angeles is expressed as UTC-08:00, or UTC-07:00
|
||||||
|
* in the summer. The offset -08:00 indicates that America/Los_Angeles time is
|
||||||
|
* obtained from UTC by adding -08:00, that is, by subtracting 8 hours.
|
||||||
|
* <p>
|
||||||
|
* The offset differs in the summer because of daylight saving time, or DST.
|
||||||
|
* The following definitions of time are generally used:
|
||||||
|
* <ul>
|
||||||
|
* <li>UTC - The reference time.
|
||||||
|
* <li>Standard Time - The local time without a daylight saving time offset.
|
||||||
|
* For example, in Paris, standard time is UTC+01:00.
|
||||||
|
* <li>Daylight Saving Time - The local time with a daylight saving time
|
||||||
|
* offset. This offset is typically one hour, but not always. It is typically
|
||||||
|
* used in most countries away from the equator. In Paris, daylight saving
|
||||||
|
* time is UTC+02:00.
|
||||||
|
* <li>Wall Time - This is what a local clock on the wall reads. This will be
|
||||||
|
* either Standard Time or Daylight Saving Time depending on the time of year
|
||||||
|
* and whether the location uses Daylight Saving Time.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Unlike the Java TimeZone class, DateTimeZone is immutable. It also only
|
||||||
|
* supports long format time zone ids. Thus EST and ECT are not accepted.
|
||||||
|
* However, the factory that accepts a TimeZone will attempt to convert from
|
||||||
|
* the old short id to a suitable long id.
|
||||||
|
* <p>
|
||||||
|
* There are four approaches to loading time-zone data, which are tried in this order:
|
||||||
|
* <ol>
|
||||||
|
* <li>load the specific {@link Provider} specified by the system property
|
||||||
|
* {@code org.joda.time.DateTimeZone.Provider}.
|
||||||
|
* <li>load {@link ZoneInfoProvider} using the data in the filing system folder
|
||||||
|
* pointed to by system property {@code org.joda.time.DateTimeZone.Folder}.
|
||||||
|
* <li>load {@link ZoneInfoProvider} using the data in the classpath location
|
||||||
|
* {@code org/joda/time/tz/data}.
|
||||||
|
* <li>load {@link UTCProvider}
|
||||||
|
* </ol>
|
||||||
|
* <p>
|
||||||
|
* Unless you override the standard behaviour, the default if the third approach.
|
||||||
|
* <p>
|
||||||
|
* DateTimeZone is thread-safe and immutable, and all subclasses must be as
|
||||||
|
* well.
|
||||||
|
*
|
||||||
|
* @author Brian S O'Neill
|
||||||
|
* @author Stephen Colebourne
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public abstract class DateTimeZone {
|
||||||
|
static final long MILLIS_PER_HOUR = 3600_000;
|
||||||
|
|
||||||
|
// Instance fields and methods
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
private final String iID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param id the id to use
|
||||||
|
* @throws IllegalArgumentException if the id is null
|
||||||
|
*/
|
||||||
|
protected DateTimeZone(String id) {
|
||||||
|
if (id == null) {
|
||||||
|
throw new IllegalArgumentException("Id must not be null");
|
||||||
|
}
|
||||||
|
iID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Principal methods
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the ID of this datetime zone.
|
||||||
|
*
|
||||||
|
* @return the ID of this datetime zone
|
||||||
|
*/
|
||||||
|
public final String getID() {
|
||||||
|
return iID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the millisecond offset to add to UTC to get local time.
|
||||||
|
*
|
||||||
|
* @param instant milliseconds from 1970-01-01T00:00:00Z to get the offset for
|
||||||
|
* @return the millisecond offset to add to UTC to get local time
|
||||||
|
*/
|
||||||
|
public abstract int getOffset(long instant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the standard millisecond offset to add to UTC to get local time,
|
||||||
|
* when standard time is in effect.
|
||||||
|
*
|
||||||
|
* @param instant milliseconds from 1970-01-01T00:00:00Z to get the offset for
|
||||||
|
* @return the millisecond offset to add to UTC to get local time
|
||||||
|
*/
|
||||||
|
public abstract int getStandardOffset(long instant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether, at a particular instant, the offset is standard or not.
|
||||||
|
* <p>
|
||||||
|
* This method can be used to determine whether Summer Time (DST) applies.
|
||||||
|
* As a general rule, if the offset at the specified instant is standard,
|
||||||
|
* then either Winter time applies, or there is no Summer Time. If the
|
||||||
|
* instant is not standard, then Summer Time applies.
|
||||||
|
* <p>
|
||||||
|
* The implementation of the method is simply whether {@link #getOffset(long)}
|
||||||
|
* equals {@link #getStandardOffset(long)} at the specified instant.
|
||||||
|
*
|
||||||
|
* @param instant milliseconds from 1970-01-01T00:00:00Z to get the offset for
|
||||||
|
* @return true if the offset at the given instant is the standard offset
|
||||||
|
* @since 1.5
|
||||||
|
*/
|
||||||
|
public boolean isStandardOffset(long instant) {
|
||||||
|
return getOffset(instant) == getStandardOffset(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the millisecond offset to subtract from local time to get UTC time.
|
||||||
|
* This offset can be used to undo adding the offset obtained by getOffset.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* millisLocal == millisUTC + getOffset(millisUTC)
|
||||||
|
* millisUTC == millisLocal - getOffsetFromLocal(millisLocal)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* NOTE: After calculating millisLocal, some error may be introduced. At
|
||||||
|
* offset transitions (due to DST or other historical changes), ranges of
|
||||||
|
* local times may map to different UTC times.
|
||||||
|
* <p>
|
||||||
|
* For overlaps (where the local time is ambiguous), this method returns the
|
||||||
|
* offset applicable before the gap. The effect of this is that any instant
|
||||||
|
* calculated using the offset from an overlap will be in "summer" time.
|
||||||
|
* <p>
|
||||||
|
* For gaps, this method returns the offset applicable before the gap, ie "winter" offset.
|
||||||
|
* However, the effect of this is that any instant calculated using the offset
|
||||||
|
* from a gap will be after the gap, in "summer" time.
|
||||||
|
* <p>
|
||||||
|
* For example, consider a zone with a gap from 01:00 to 01:59:<br />
|
||||||
|
* Input: 00:00 (before gap) Output: Offset applicable before gap DateTime: 00:00<br />
|
||||||
|
* Input: 00:30 (before gap) Output: Offset applicable before gap DateTime: 00:30<br />
|
||||||
|
* Input: 01:00 (in gap) Output: Offset applicable before gap DateTime: 02:00<br />
|
||||||
|
* Input: 01:30 (in gap) Output: Offset applicable before gap DateTime: 02:30<br />
|
||||||
|
* Input: 02:00 (after gap) Output: Offset applicable after gap DateTime: 02:00<br />
|
||||||
|
* Input: 02:30 (after gap) Output: Offset applicable after gap DateTime: 02:30<br />
|
||||||
|
* <p>
|
||||||
|
* NOTE: Prior to v2.0, the DST overlap behaviour was not defined and varied by hemisphere.
|
||||||
|
* Prior to v1.5, the DST gap behaviour was also not defined.
|
||||||
|
* In v2.4, the documentation was clarified again.
|
||||||
|
*
|
||||||
|
* @param instantLocal the millisecond instant, relative to this time zone, to get the offset for
|
||||||
|
* @return the millisecond offset to subtract from local time to get UTC time
|
||||||
|
*/
|
||||||
|
public int getOffsetFromLocal(long instantLocal) {
|
||||||
|
// get the offset at instantLocal (first estimate)
|
||||||
|
final int offsetLocal = getOffset(instantLocal);
|
||||||
|
// adjust instantLocal using the estimate and recalc the offset
|
||||||
|
final long instantAdjusted = instantLocal - offsetLocal;
|
||||||
|
final int offsetAdjusted = getOffset(instantAdjusted);
|
||||||
|
// if the offsets differ, we must be near a DST boundary
|
||||||
|
if (offsetLocal != offsetAdjusted) {
|
||||||
|
// we need to ensure that time is always after the DST gap
|
||||||
|
// this happens naturally for positive offsets, but not for negative
|
||||||
|
if ((offsetLocal - offsetAdjusted) < 0) {
|
||||||
|
// if we just return offsetAdjusted then the time is pushed
|
||||||
|
// back before the transition, whereas it should be
|
||||||
|
// on or after the transition
|
||||||
|
long nextLocal = nextTransition(instantAdjusted);
|
||||||
|
if (nextLocal == (instantLocal - offsetLocal)) {
|
||||||
|
nextLocal = Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
long nextAdjusted = nextTransition(instantLocal - offsetAdjusted);
|
||||||
|
if (nextAdjusted == (instantLocal - offsetAdjusted)) {
|
||||||
|
nextAdjusted = Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
if (nextLocal != nextAdjusted) {
|
||||||
|
return offsetLocal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (offsetLocal >= 0) {
|
||||||
|
long prev = previousTransition(instantAdjusted);
|
||||||
|
if (prev < instantAdjusted) {
|
||||||
|
int offsetPrev = getOffset(prev);
|
||||||
|
int diff = offsetPrev - offsetLocal;
|
||||||
|
if (instantAdjusted - prev <= diff) {
|
||||||
|
return offsetPrev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offsetAdjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a standard UTC instant to a local instant with the same
|
||||||
|
* local time. This conversion is used before performing a calculation
|
||||||
|
* so that the calculation can be done using a simple local zone.
|
||||||
|
*
|
||||||
|
* @param instantUTC the UTC instant to convert to local
|
||||||
|
* @return the local instant with the same local time
|
||||||
|
* @throws ArithmeticException if the result overflows a long
|
||||||
|
* @since 1.5
|
||||||
|
*/
|
||||||
|
public long convertUTCToLocal(long instantUTC) {
|
||||||
|
int offset = getOffset(instantUTC);
|
||||||
|
long instantLocal = instantUTC + offset;
|
||||||
|
// If there is a sign change, but the two values have the same sign...
|
||||||
|
if ((instantUTC ^ instantLocal) < 0 && (instantUTC ^ offset) >= 0) {
|
||||||
|
throw new ArithmeticException("Adding time zone offset caused overflow");
|
||||||
|
}
|
||||||
|
return instantLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a local instant to a standard UTC instant with the same
|
||||||
|
* local time attempting to use the same offset as the original.
|
||||||
|
* <p>
|
||||||
|
* This conversion is used after performing a calculation
|
||||||
|
* where the calculation was done using a simple local zone.
|
||||||
|
* Whenever possible, the same offset as the original offset will be used.
|
||||||
|
* This is most significant during a daylight savings overlap.
|
||||||
|
*
|
||||||
|
* @param instantLocal the local instant to convert to UTC
|
||||||
|
* @param strict whether the conversion should reject non-existent local times
|
||||||
|
* @param originalInstantUTC the original instant that the calculation is based on
|
||||||
|
* @return the UTC instant with the same local time,
|
||||||
|
* @throws ArithmeticException if the result overflows a long
|
||||||
|
* @throws IllegalArgumentException if the zone has no equivalent local time
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public long convertLocalToUTC(long instantLocal, boolean strict, long originalInstantUTC) {
|
||||||
|
int offsetOriginal = getOffset(originalInstantUTC);
|
||||||
|
long instantUTC = instantLocal - offsetOriginal;
|
||||||
|
int offsetLocalFromOriginal = getOffset(instantUTC);
|
||||||
|
if (offsetLocalFromOriginal == offsetOriginal) {
|
||||||
|
return instantUTC;
|
||||||
|
}
|
||||||
|
return convertLocalToUTC(instantLocal, strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a local instant to a standard UTC instant with the same
|
||||||
|
* local time. This conversion is used after performing a calculation
|
||||||
|
* where the calculation was done using a simple local zone.
|
||||||
|
*
|
||||||
|
* @param instantLocal the local instant to convert to UTC
|
||||||
|
* @param strict whether the conversion should reject non-existent local times
|
||||||
|
* @return the UTC instant with the same local time,
|
||||||
|
* @throws ArithmeticException if the result overflows a long
|
||||||
|
* @throws IllegalInstantException if the zone has no equivalent local time
|
||||||
|
* @since 1.5
|
||||||
|
*/
|
||||||
|
public long convertLocalToUTC(long instantLocal, boolean strict) {
|
||||||
|
// get the offset at instantLocal (first estimate)
|
||||||
|
int offsetLocal = getOffset(instantLocal);
|
||||||
|
// adjust instantLocal using the estimate and recalc the offset
|
||||||
|
int offset = getOffset(instantLocal - offsetLocal);
|
||||||
|
// if the offsets differ, we must be near a DST boundary
|
||||||
|
if (offsetLocal != offset) {
|
||||||
|
// if strict then always check if in DST gap
|
||||||
|
// otherwise only check if zone in Western hemisphere (as the
|
||||||
|
// value of offset is already correct for Eastern hemisphere)
|
||||||
|
if (strict || offsetLocal < 0) {
|
||||||
|
// determine if we are in the DST gap
|
||||||
|
long nextLocal = nextTransition(instantLocal - offsetLocal);
|
||||||
|
if (nextLocal == (instantLocal - offsetLocal)) {
|
||||||
|
nextLocal = Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
long nextAdjusted = nextTransition(instantLocal - offset);
|
||||||
|
if (nextAdjusted == (instantLocal - offset)) {
|
||||||
|
nextAdjusted = Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
if (nextLocal != nextAdjusted) {
|
||||||
|
// yes we are in the DST gap
|
||||||
|
if (strict) {
|
||||||
|
// DST gap is not acceptable
|
||||||
|
throw new RuntimeException(getID());
|
||||||
|
} else {
|
||||||
|
// DST gap is acceptable, but for the Western hemisphere
|
||||||
|
// the offset is wrong and will result in local times
|
||||||
|
// before the cutover so use the offsetLocal instead
|
||||||
|
offset = offsetLocal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check for overflow
|
||||||
|
long instantUTC = instantLocal - offset;
|
||||||
|
// If there is a sign change, but the two values have different signs...
|
||||||
|
if ((instantLocal ^ instantUTC) < 0 && (instantLocal ^ offset) < 0) {
|
||||||
|
throw new ArithmeticException("Subtracting time zone offset caused overflow");
|
||||||
|
}
|
||||||
|
return instantUTC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the millisecond instant in another zone keeping the same local time.
|
||||||
|
* <p>
|
||||||
|
* The conversion is performed by converting the specified UTC millis to local
|
||||||
|
* millis in this zone, then converting back to UTC millis in the new zone.
|
||||||
|
*
|
||||||
|
* @param newZone the new zone, null means default
|
||||||
|
* @param oldInstant the UTC millisecond instant to convert
|
||||||
|
* @return the UTC millisecond instant with the same local time in the new zone
|
||||||
|
*/
|
||||||
|
public long getMillisKeepLocal(DateTimeZone newZone, long oldInstant) {
|
||||||
|
if (newZone == this) {
|
||||||
|
return oldInstant;
|
||||||
|
}
|
||||||
|
long instantLocal = convertUTCToLocal(oldInstant);
|
||||||
|
return newZone.convertLocalToUTC(instantLocal, false, oldInstant);
|
||||||
|
}
|
||||||
|
|
||||||
|
// //-----------------------------------------------------------------------
|
||||||
|
// /**
|
||||||
|
// * Checks if the given {@link LocalDateTime} is within an overlap.
|
||||||
|
// * <p>
|
||||||
|
// * When switching from Daylight Savings Time to standard time there is
|
||||||
|
// * typically an overlap where the same clock hour occurs twice. This
|
||||||
|
// * method identifies whether the local datetime refers to such an overlap.
|
||||||
|
// *
|
||||||
|
// * @param localDateTime the time to check, not null
|
||||||
|
// * @return true if the given datetime refers to an overlap
|
||||||
|
// */
|
||||||
|
// public boolean isLocalDateTimeOverlap(LocalDateTime localDateTime) {
|
||||||
|
// if (isFixed()) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// long instantLocal = localDateTime.toDateTime(DateTimeZone.UTC).getMillis();
|
||||||
|
// // get the offset at instantLocal (first estimate)
|
||||||
|
// int offsetLocal = getOffset(instantLocal);
|
||||||
|
// // adjust instantLocal using the estimate and recalc the offset
|
||||||
|
// int offset = getOffset(instantLocal - offsetLocal);
|
||||||
|
// // if the offsets differ, we must be near a DST boundary
|
||||||
|
// if (offsetLocal != offset) {
|
||||||
|
// long nextLocal = nextTransition(instantLocal - offsetLocal);
|
||||||
|
// long nextAdjusted = nextTransition(instantLocal - offset);
|
||||||
|
// if (nextLocal != nextAdjusted) {
|
||||||
|
// // in DST gap
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// long diff = Math.abs(offset - offsetLocal);
|
||||||
|
// DateTime dateTime = localDateTime.toDateTime(this);
|
||||||
|
// DateTime adjusted = dateTime.plus(diff);
|
||||||
|
// if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
|
||||||
|
// dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
|
||||||
|
// dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// adjusted = dateTime.minus(diff);
|
||||||
|
// if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
|
||||||
|
// dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
|
||||||
|
// dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// DateTime dateTime = null;
|
||||||
|
// try {
|
||||||
|
// dateTime = localDateTime.toDateTime(this);
|
||||||
|
// } catch (IllegalArgumentException ex) {
|
||||||
|
// return false; // it is a gap, not an overlap
|
||||||
|
// }
|
||||||
|
// long offset1 = Math.abs(getOffset(dateTime.getMillis() + 1) - getStandardOffset(dateTime.getMillis() + 1));
|
||||||
|
// long offset2 = Math.abs(getOffset(dateTime.getMillis() - 1) - getStandardOffset(dateTime.getMillis() - 1));
|
||||||
|
// long offset = Math.max(offset1, offset2);
|
||||||
|
// if (offset == 0) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// DateTime adjusted = dateTime.plus(offset);
|
||||||
|
// if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
|
||||||
|
// dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
|
||||||
|
// dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// adjusted = dateTime.minus(offset);
|
||||||
|
// if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
|
||||||
|
// dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
|
||||||
|
// dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
|
||||||
|
// long millis = dateTime.getMillis();
|
||||||
|
// long nextTransition = nextTransition(millis);
|
||||||
|
// long previousTransition = previousTransition(millis);
|
||||||
|
// long deltaToPreviousTransition = millis - previousTransition;
|
||||||
|
// long deltaToNextTransition = nextTransition - millis;
|
||||||
|
// if (deltaToNextTransition < deltaToPreviousTransition) {
|
||||||
|
// int offset = getOffset(nextTransition);
|
||||||
|
// int standardOffset = getStandardOffset(nextTransition);
|
||||||
|
// if (Math.abs(offset - standardOffset) >= deltaToNextTransition) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// int offset = getOffset(previousTransition);
|
||||||
|
// int standardOffset = getStandardOffset(previousTransition);
|
||||||
|
// if (Math.abs(offset - standardOffset) >= deltaToPreviousTransition) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the offset to be the earlier or later one during an overlap.
|
||||||
|
*
|
||||||
|
* @param instant the instant to adjust
|
||||||
|
* @param earlierOrLater false for earlier, true for later
|
||||||
|
* @return the adjusted instant millis
|
||||||
|
*/
|
||||||
|
public long adjustOffset(long instant, boolean earlierOrLater) {
|
||||||
|
// a bit messy, but will work in all non-pathological cases
|
||||||
|
|
||||||
|
// evaluate 3 hours before and after to work out if anything is happening
|
||||||
|
long instantBefore = instant - 3 * MILLIS_PER_HOUR;
|
||||||
|
long instantAfter = instant + 3 * MILLIS_PER_HOUR;
|
||||||
|
long offsetBefore = getOffset(instantBefore);
|
||||||
|
long offsetAfter = getOffset(instantAfter);
|
||||||
|
if (offsetBefore <= offsetAfter) {
|
||||||
|
return instant; // not an overlap (less than is a gap, equal is normal case)
|
||||||
|
}
|
||||||
|
|
||||||
|
// work out range of instants that have duplicate local times
|
||||||
|
long diff = offsetBefore - offsetAfter;
|
||||||
|
long transition = nextTransition(instantBefore);
|
||||||
|
long overlapStart = transition - diff;
|
||||||
|
long overlapEnd = transition + diff;
|
||||||
|
if (instant < overlapStart || instant >= overlapEnd) {
|
||||||
|
return instant; // not an overlap
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate result
|
||||||
|
long afterStart = instant - overlapStart;
|
||||||
|
if (afterStart >= diff) {
|
||||||
|
// currently in later offset
|
||||||
|
return earlierOrLater ? instant : instant - diff;
|
||||||
|
} else {
|
||||||
|
// currently in earlier offset
|
||||||
|
return earlierOrLater ? instant + diff : instant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// System.out.println(new DateTime(transitionStart, DateTimeZone.UTC) + " " + new DateTime(transitionStart, this));
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Returns true if this time zone has no transitions.
|
||||||
|
*
|
||||||
|
* @return true if no transitions
|
||||||
|
*/
|
||||||
|
public abstract boolean isFixed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances the given instant to where the time zone offset or name changes.
|
||||||
|
* If the instant returned is exactly the same as passed in, then
|
||||||
|
* no changes occur after the given instant.
|
||||||
|
*
|
||||||
|
* @param instant milliseconds from 1970-01-01T00:00:00Z
|
||||||
|
* @return milliseconds from 1970-01-01T00:00:00Z
|
||||||
|
*/
|
||||||
|
public abstract long nextTransition(long instant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retreats the given instant to where the time zone offset or name changes.
|
||||||
|
* If the instant returned is exactly the same as passed in, then
|
||||||
|
* no changes occur before the given instant.
|
||||||
|
*
|
||||||
|
* @param instant milliseconds from 1970-01-01T00:00:00Z
|
||||||
|
* @return milliseconds from 1970-01-01T00:00:00Z
|
||||||
|
*/
|
||||||
|
public abstract long previousTransition(long instant);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2001-2005 Stephen Colebourne
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.impl.tz;
|
||||||
|
|
||||||
|
import org.teavm.classlib.impl.Base46;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic DateTimeZone implementation that has a fixed name key and offsets.
|
||||||
|
* <p>
|
||||||
|
* FixedDateTimeZone is thread-safe and immutable.
|
||||||
|
*
|
||||||
|
* @author Brian S O'Neill
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public final class FixedDateTimeZone extends StorableDateTimeZone {
|
||||||
|
private final int iWallOffset;
|
||||||
|
private final int iStandardOffset;
|
||||||
|
|
||||||
|
public FixedDateTimeZone(String id, int wallOffset, int standardOffset) {
|
||||||
|
super(id);
|
||||||
|
iWallOffset = wallOffset;
|
||||||
|
iStandardOffset = standardOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset(long instant) {
|
||||||
|
return iWallOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStandardOffset(long instant) {
|
||||||
|
return iStandardOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffsetFromLocal(long instantLocal) {
|
||||||
|
return iWallOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFixed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long nextTransition(long instant) {
|
||||||
|
return instant;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long previousTransition(long instant) {
|
||||||
|
return instant;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(StringBuilder sb) {
|
||||||
|
Base46.encodeUnsigned(sb, FIXED);
|
||||||
|
writeTime(sb, iWallOffset);
|
||||||
|
writeTime(sb, iStandardOffset);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.impl.tz;
|
||||||
|
|
||||||
|
import org.teavm.classlib.impl.Base46;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Alexey Andreev
|
||||||
|
*/
|
||||||
|
public abstract class StorableDateTimeZone extends DateTimeZone {
|
||||||
|
public static int PRECALCULATED = 0;
|
||||||
|
public static int FIXED = 1;
|
||||||
|
public static int CACHED = 2;
|
||||||
|
public static int DST = 3;
|
||||||
|
|
||||||
|
public StorableDateTimeZone(String id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void write(StringBuilder sb);
|
||||||
|
|
||||||
|
public static void writeTime(StringBuilder sb, long time) {
|
||||||
|
if (time % 1800_000 == 0) {
|
||||||
|
Base46.encode(sb, (int)((time / 1800_000) << 1));
|
||||||
|
} else {
|
||||||
|
Base46.encode(sb, (int)(((time / 60_000) << 1) | 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeUnsignedTime(StringBuilder sb, long time) {
|
||||||
|
if (time % 1800_000 == 0) {
|
||||||
|
Base46.encodeUnsigned(sb, (int)((time / 1800_000) << 1));
|
||||||
|
} else {
|
||||||
|
Base46.encodeUnsigned(sb, (int)(((time / 60_000) << 1) | 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.impl.tz;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Alexey Andreev
|
||||||
|
*/
|
||||||
|
public class TimeZoneWriter {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,650 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2001-2013 Stephen Colebourne
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.impl.tz;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import org.joda.time.Chronology;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeField;
|
||||||
|
import org.joda.time.LocalDate;
|
||||||
|
import org.joda.time.MutableDateTime;
|
||||||
|
import org.joda.time.chrono.ISOChronology;
|
||||||
|
import org.joda.time.chrono.LenientChronology;
|
||||||
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
|
import org.joda.time.format.ISODateTimeFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles IANA ZoneInfo database files into binary files for each time zone
|
||||||
|
* in the database. {@link DateTimeZoneBuilder} is used to construct and encode
|
||||||
|
* compiled data files. {@link ZoneInfoProvider} loads the encoded files and
|
||||||
|
* converts them back into {@link DateTimeZone} objects.
|
||||||
|
* <p>
|
||||||
|
* Although this tool is similar to zic, the binary formats are not
|
||||||
|
* compatible. The latest IANA time zone database files may be obtained
|
||||||
|
* <a href="http://www.iana.org/time-zones">here</a>.
|
||||||
|
* <p>
|
||||||
|
* ZoneInfoCompiler is mutable and not thread-safe, although the main method
|
||||||
|
* may be safely invoked by multiple threads.
|
||||||
|
*
|
||||||
|
* @author Brian S O'Neill
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public class ZoneInfoCompiler {
|
||||||
|
static DateTimeOfYear cStartOfYear;
|
||||||
|
|
||||||
|
static Chronology cLenientISO;
|
||||||
|
|
||||||
|
static DateTimeOfYear getStartOfYear() {
|
||||||
|
if (cStartOfYear == null) {
|
||||||
|
cStartOfYear = new DateTimeOfYear();
|
||||||
|
}
|
||||||
|
return cStartOfYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Chronology getLenientISOChronology() {
|
||||||
|
if (cLenientISO == null) {
|
||||||
|
cLenientISO = LenientChronology.getInstance(ISOChronology.getInstanceUTC());
|
||||||
|
}
|
||||||
|
return cLenientISO;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parseYear(String str, int def) {
|
||||||
|
str = str.toLowerCase();
|
||||||
|
if (str.equals("minimum") || str.equals("min")) {
|
||||||
|
return Integer.MIN_VALUE;
|
||||||
|
} else if (str.equals("maximum") || str.equals("max")) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
} else if (str.equals("only")) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parseMonth(String str) {
|
||||||
|
DateTimeField field = ISOChronology.getInstanceUTC().monthOfYear();
|
||||||
|
return field.get(field.set(0, str, Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parseDayOfWeek(String str) {
|
||||||
|
DateTimeField field = ISOChronology.getInstanceUTC().dayOfWeek();
|
||||||
|
return field.get(field.set(0, str, Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String parseOptional(String str) {
|
||||||
|
return (str.equals("-")) ? null : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parseTime(String str) {
|
||||||
|
DateTimeFormatter p = ISODateTimeFormat.hourMinuteSecondFraction();
|
||||||
|
MutableDateTime mdt = new MutableDateTime(0, getLenientISOChronology());
|
||||||
|
int pos = 0;
|
||||||
|
if (str.startsWith("-")) {
|
||||||
|
pos = 1;
|
||||||
|
}
|
||||||
|
int newPos = p.parseInto(mdt, str, pos);
|
||||||
|
if (newPos == ~pos) {
|
||||||
|
throw new IllegalArgumentException(str);
|
||||||
|
}
|
||||||
|
int millis = (int)mdt.getMillis();
|
||||||
|
if (pos == 1) {
|
||||||
|
millis = -millis;
|
||||||
|
}
|
||||||
|
return millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char parseZoneChar(char c) {
|
||||||
|
switch (c) {
|
||||||
|
case 's': case 'S':
|
||||||
|
// Standard time
|
||||||
|
return 's';
|
||||||
|
case 'u': case 'U': case 'g': case 'G': case 'z': case 'Z':
|
||||||
|
// UTC
|
||||||
|
return 'u';
|
||||||
|
case 'w': case 'W': default:
|
||||||
|
// Wall time
|
||||||
|
return 'w';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return false if error.
|
||||||
|
*/
|
||||||
|
static boolean test(String id, DateTimeZone tz) {
|
||||||
|
if (!id.equals(tz.getID())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test to ensure that reported transitions are not duplicated.
|
||||||
|
|
||||||
|
long millis = ISOChronology.getInstanceUTC().year().set(0, 1850);
|
||||||
|
long end = ISOChronology.getInstanceUTC().year().set(0, 2050);
|
||||||
|
|
||||||
|
int offset = tz.getOffset(millis);
|
||||||
|
|
||||||
|
List<Long> transitions = new ArrayList<>();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
long next = tz.nextTransition(millis);
|
||||||
|
if (next == millis || next > end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
millis = next;
|
||||||
|
|
||||||
|
int nextOffset = tz.getOffset(millis);
|
||||||
|
|
||||||
|
if (offset == nextOffset) {
|
||||||
|
System.out.println("*d* Error in " + tz.getID() + " "
|
||||||
|
+ new DateTime(millis,
|
||||||
|
ISOChronology.getInstanceUTC()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
transitions.add(Long.valueOf(millis));
|
||||||
|
|
||||||
|
offset = nextOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now verify that reverse transitions match up.
|
||||||
|
|
||||||
|
millis = ISOChronology.getInstanceUTC().year().set(0, 2050);
|
||||||
|
end = ISOChronology.getInstanceUTC().year().set(0, 1850);
|
||||||
|
|
||||||
|
for (int i=transitions.size(); --i>= 0; ) {
|
||||||
|
long prev = tz.previousTransition(millis);
|
||||||
|
if (prev == millis || prev < end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
millis = prev;
|
||||||
|
|
||||||
|
long trans = transitions.get(i).longValue();
|
||||||
|
|
||||||
|
if (trans - 1 != millis) {
|
||||||
|
System.out.println("*r* Error in " + tz.getID() + " "
|
||||||
|
+ new DateTime(millis,
|
||||||
|
ISOChronology.getInstanceUTC()) + " != "
|
||||||
|
+ new DateTime(trans - 1,
|
||||||
|
ISOChronology.getInstanceUTC()));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps names to RuleSets.
|
||||||
|
private Map<String, RuleSet> iRuleSets;
|
||||||
|
|
||||||
|
// List of Zone objects.
|
||||||
|
private List<Zone> iZones;
|
||||||
|
|
||||||
|
// List String pairs to link.
|
||||||
|
private List<String> iGoodLinks;
|
||||||
|
|
||||||
|
// List String pairs to link.
|
||||||
|
private List<String> iBackLinks;
|
||||||
|
|
||||||
|
public ZoneInfoCompiler() {
|
||||||
|
iRuleSets = new HashMap<>();
|
||||||
|
iZones = new ArrayList<>();
|
||||||
|
iGoodLinks = new ArrayList<>();
|
||||||
|
iBackLinks = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, StorableDateTimeZone> compile() {
|
||||||
|
Map<String, StorableDateTimeZone> map = new TreeMap<>();
|
||||||
|
Map<String, Zone> sourceMap = new TreeMap<>();
|
||||||
|
|
||||||
|
// write out the standard entries
|
||||||
|
for (int i = 0; i < iZones.size(); i++) {
|
||||||
|
Zone zone = iZones.get(i);
|
||||||
|
DateTimeZoneBuilder builder = new DateTimeZoneBuilder();
|
||||||
|
zone.addToBuilder(builder, iRuleSets);
|
||||||
|
StorableDateTimeZone tz = builder.toDateTimeZone(zone.iName, true);
|
||||||
|
if (test(tz.getID(), tz)) {
|
||||||
|
map.put(tz.getID(), tz);
|
||||||
|
sourceMap.put(tz.getID(), zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// revive zones from "good" links
|
||||||
|
for (int i = 0; i < iGoodLinks.size(); i += 2) {
|
||||||
|
String baseId = iGoodLinks.get(i);
|
||||||
|
String alias = iGoodLinks.get(i + 1);
|
||||||
|
Zone sourceZone = sourceMap.get(baseId);
|
||||||
|
if (sourceZone == null) {
|
||||||
|
throw new RuntimeException("Cannot find source zone '" + baseId + "' to link alias '" +
|
||||||
|
alias + "' to");
|
||||||
|
} else {
|
||||||
|
DateTimeZoneBuilder builder = new DateTimeZoneBuilder();
|
||||||
|
sourceZone.addToBuilder(builder, iRuleSets);
|
||||||
|
StorableDateTimeZone revived = builder.toDateTimeZone(alias, true);
|
||||||
|
if (test(revived.getID(), revived)) {
|
||||||
|
map.put(revived.getID(), revived);
|
||||||
|
}
|
||||||
|
map.put(revived.getID(), revived);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store "back" links as aliases (where name is permanently mapped
|
||||||
|
for (int pass = 0; pass < 2; pass++) {
|
||||||
|
for (int i = 0; i < iBackLinks.size(); i += 2) {
|
||||||
|
String id = iBackLinks.get(i);
|
||||||
|
String alias = iBackLinks.get(i + 1);
|
||||||
|
StorableDateTimeZone tz = map.get(id);
|
||||||
|
if (tz == null) {
|
||||||
|
if (pass > 0) {
|
||||||
|
throw new RuntimeException("Cannot find time zone '" + id + "' to link alias '" +
|
||||||
|
alias + "' to");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map.put(alias, tz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parseDataFile(BufferedReader in, boolean backward) throws IOException {
|
||||||
|
Zone zone = null;
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (trimmed.length() == 0 || trimmed.charAt(0) == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = line.indexOf('#');
|
||||||
|
if (index >= 0) {
|
||||||
|
line = line.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
//System.out.println(line);
|
||||||
|
|
||||||
|
StringTokenizer st = new StringTokenizer(line, " \t");
|
||||||
|
|
||||||
|
if (Character.isWhitespace(line.charAt(0)) && st.hasMoreTokens()) {
|
||||||
|
if (zone != null) {
|
||||||
|
// Zone continuation
|
||||||
|
zone.chain(st);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (zone != null) {
|
||||||
|
iZones.add(zone);
|
||||||
|
}
|
||||||
|
zone = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.hasMoreTokens()) {
|
||||||
|
String token = st.nextToken();
|
||||||
|
if (token.equalsIgnoreCase("Rule")) {
|
||||||
|
Rule r = new Rule(st);
|
||||||
|
RuleSet rs = iRuleSets.get(r.iName);
|
||||||
|
if (rs == null) {
|
||||||
|
rs = new RuleSet(r);
|
||||||
|
iRuleSets.put(r.iName, rs);
|
||||||
|
} else {
|
||||||
|
rs.addRule(r);
|
||||||
|
}
|
||||||
|
} else if (token.equalsIgnoreCase("Zone")) {
|
||||||
|
zone = new Zone(st);
|
||||||
|
} else if (token.equalsIgnoreCase("Link")) {
|
||||||
|
String real = st.nextToken();
|
||||||
|
String alias = st.nextToken();
|
||||||
|
// links in "backward" are deprecated names
|
||||||
|
// links in other files should be kept
|
||||||
|
// special case a few to try to repair terrible damage to tzdb
|
||||||
|
if (backward || alias.equals("US/Pacific-New") || alias.startsWith("Etc/") || alias.equals("GMT")) {
|
||||||
|
iBackLinks.add(real);
|
||||||
|
iBackLinks.add(alias);
|
||||||
|
} else {
|
||||||
|
iGoodLinks.add(real);
|
||||||
|
iGoodLinks.add(alias);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Unknown line: " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zone != null) {
|
||||||
|
iZones.add(zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DateTimeOfYear {
|
||||||
|
public final int iMonthOfYear;
|
||||||
|
public final int iDayOfMonth;
|
||||||
|
public final int iDayOfWeek;
|
||||||
|
public final boolean iAdvanceDayOfWeek;
|
||||||
|
public final int iMillisOfDay;
|
||||||
|
public final char iZoneChar;
|
||||||
|
|
||||||
|
DateTimeOfYear() {
|
||||||
|
iMonthOfYear = 1;
|
||||||
|
iDayOfMonth = 1;
|
||||||
|
iDayOfWeek = 0;
|
||||||
|
iAdvanceDayOfWeek = false;
|
||||||
|
iMillisOfDay = 0;
|
||||||
|
iZoneChar = 'w';
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOfYear(StringTokenizer st) {
|
||||||
|
int month = 1;
|
||||||
|
int day = 1;
|
||||||
|
int dayOfWeek = 0;
|
||||||
|
int millis = 0;
|
||||||
|
boolean advance = false;
|
||||||
|
char zoneChar = 'w';
|
||||||
|
|
||||||
|
if (st.hasMoreTokens()) {
|
||||||
|
month = parseMonth(st.nextToken());
|
||||||
|
|
||||||
|
if (st.hasMoreTokens()) {
|
||||||
|
String str = st.nextToken();
|
||||||
|
if (str.startsWith("last")) {
|
||||||
|
day = -1;
|
||||||
|
dayOfWeek = parseDayOfWeek(str.substring(4));
|
||||||
|
advance = false;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
day = Integer.parseInt(str);
|
||||||
|
dayOfWeek = 0;
|
||||||
|
advance = false;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
int index = str.indexOf(">=");
|
||||||
|
if (index > 0) {
|
||||||
|
day = Integer.parseInt(str.substring(index + 2));
|
||||||
|
dayOfWeek = parseDayOfWeek(str.substring(0, index));
|
||||||
|
advance = true;
|
||||||
|
} else {
|
||||||
|
index = str.indexOf("<=");
|
||||||
|
if (index > 0) {
|
||||||
|
day = Integer.parseInt(str.substring(index + 2));
|
||||||
|
dayOfWeek = parseDayOfWeek(str.substring(0, index));
|
||||||
|
advance = false;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.hasMoreTokens()) {
|
||||||
|
str = st.nextToken();
|
||||||
|
zoneChar = parseZoneChar(str.charAt(str.length() - 1));
|
||||||
|
if (str.equals("24:00")) {
|
||||||
|
// handle end of year
|
||||||
|
if (month == 12 && day == 31) {
|
||||||
|
millis = parseTime("23:59:59.999");
|
||||||
|
} else {
|
||||||
|
LocalDate date = (day == -1 ?
|
||||||
|
new LocalDate(2001, month, 1).plusMonths(1) :
|
||||||
|
new LocalDate(2001, month, day).plusDays(1));
|
||||||
|
advance = (day != -1 && dayOfWeek != 0);
|
||||||
|
month = date.getMonthOfYear();
|
||||||
|
day = date.getDayOfMonth();
|
||||||
|
if (dayOfWeek != 0) {
|
||||||
|
dayOfWeek = ((dayOfWeek - 1 + 1) % 7) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
millis = parseTime(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iMonthOfYear = month;
|
||||||
|
iDayOfMonth = day;
|
||||||
|
iDayOfWeek = dayOfWeek;
|
||||||
|
iAdvanceDayOfWeek = advance;
|
||||||
|
iMillisOfDay = millis;
|
||||||
|
iZoneChar = zoneChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a recurring savings rule to the builder.
|
||||||
|
*/
|
||||||
|
public void addRecurring(DateTimeZoneBuilder builder, int saveMillis, int fromYear, int toYear)
|
||||||
|
{
|
||||||
|
builder.addRecurringSavings(saveMillis,
|
||||||
|
fromYear, toYear,
|
||||||
|
iZoneChar,
|
||||||
|
iMonthOfYear,
|
||||||
|
iDayOfMonth,
|
||||||
|
iDayOfWeek,
|
||||||
|
iAdvanceDayOfWeek,
|
||||||
|
iMillisOfDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a cutover to the builder.
|
||||||
|
*/
|
||||||
|
public void addCutover(DateTimeZoneBuilder builder, int year) {
|
||||||
|
builder.addCutover(year,
|
||||||
|
iZoneChar,
|
||||||
|
iMonthOfYear,
|
||||||
|
iDayOfMonth,
|
||||||
|
iDayOfWeek,
|
||||||
|
iAdvanceDayOfWeek,
|
||||||
|
iMillisOfDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"MonthOfYear: " + iMonthOfYear + "\n" +
|
||||||
|
"DayOfMonth: " + iDayOfMonth + "\n" +
|
||||||
|
"DayOfWeek: " + iDayOfWeek + "\n" +
|
||||||
|
"AdvanceDayOfWeek: " + iAdvanceDayOfWeek + "\n" +
|
||||||
|
"MillisOfDay: " + iMillisOfDay + "\n" +
|
||||||
|
"ZoneChar: " + iZoneChar + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Rule {
|
||||||
|
public final String iName;
|
||||||
|
public final int iFromYear;
|
||||||
|
public final int iToYear;
|
||||||
|
public final String iType;
|
||||||
|
public final DateTimeOfYear iDateTimeOfYear;
|
||||||
|
public final int iSaveMillis;
|
||||||
|
public final String iLetterS;
|
||||||
|
|
||||||
|
Rule(StringTokenizer st) {
|
||||||
|
iName = st.nextToken().intern();
|
||||||
|
iFromYear = parseYear(st.nextToken(), 0);
|
||||||
|
iToYear = parseYear(st.nextToken(), iFromYear);
|
||||||
|
if (iToYear < iFromYear) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
iType = parseOptional(st.nextToken());
|
||||||
|
iDateTimeOfYear = new DateTimeOfYear(st);
|
||||||
|
iSaveMillis = parseTime(st.nextToken());
|
||||||
|
iLetterS = parseOptional(st.nextToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a recurring savings rule to the builder.
|
||||||
|
*/
|
||||||
|
public void addRecurring(DateTimeZoneBuilder builder) {
|
||||||
|
iDateTimeOfYear.addRecurring(builder, iSaveMillis, iFromYear, iToYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"[Rule]\n" +
|
||||||
|
"Name: " + iName + "\n" +
|
||||||
|
"FromYear: " + iFromYear + "\n" +
|
||||||
|
"ToYear: " + iToYear + "\n" +
|
||||||
|
"Type: " + iType + "\n" +
|
||||||
|
iDateTimeOfYear +
|
||||||
|
"SaveMillis: " + iSaveMillis + "\n" +
|
||||||
|
"LetterS: " + iLetterS + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RuleSet {
|
||||||
|
private List<Rule> iRules;
|
||||||
|
|
||||||
|
RuleSet(Rule rule) {
|
||||||
|
iRules = new ArrayList<>();
|
||||||
|
iRules.add(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRule(Rule rule) {
|
||||||
|
if (!(rule.iName.equals(iRules.get(0).iName))) {
|
||||||
|
throw new IllegalArgumentException("Rule name mismatch");
|
||||||
|
}
|
||||||
|
iRules.add(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds recurring savings rules to the builder.
|
||||||
|
*/
|
||||||
|
public void addRecurring(DateTimeZoneBuilder builder) {
|
||||||
|
for (int i=0; i<iRules.size(); i++) {
|
||||||
|
Rule rule = iRules.get(i);
|
||||||
|
rule.addRecurring(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Zone {
|
||||||
|
public final String iName;
|
||||||
|
public final int iOffsetMillis;
|
||||||
|
public final String iRules;
|
||||||
|
public final String iFormat;
|
||||||
|
public final int iUntilYear;
|
||||||
|
public final DateTimeOfYear iUntilDateTimeOfYear;
|
||||||
|
|
||||||
|
private Zone iNext;
|
||||||
|
|
||||||
|
Zone(StringTokenizer st) {
|
||||||
|
this(st.nextToken(), st);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Zone(String name, StringTokenizer st) {
|
||||||
|
iName = name.intern();
|
||||||
|
iOffsetMillis = parseTime(st.nextToken());
|
||||||
|
iRules = parseOptional(st.nextToken());
|
||||||
|
iFormat = st.nextToken().intern();
|
||||||
|
|
||||||
|
int year = Integer.MAX_VALUE;
|
||||||
|
DateTimeOfYear dtOfYear = getStartOfYear();
|
||||||
|
|
||||||
|
if (st.hasMoreTokens()) {
|
||||||
|
year = Integer.parseInt(st.nextToken());
|
||||||
|
if (st.hasMoreTokens()) {
|
||||||
|
dtOfYear = new DateTimeOfYear(st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iUntilYear = year;
|
||||||
|
iUntilDateTimeOfYear = dtOfYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chain(StringTokenizer st) {
|
||||||
|
if (iNext != null) {
|
||||||
|
iNext.chain(st);
|
||||||
|
} else {
|
||||||
|
iNext = new Zone(iName, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public DateTimeZone buildDateTimeZone(Map ruleSets) {
|
||||||
|
DateTimeZoneBuilder builder = new DateTimeZoneBuilder();
|
||||||
|
addToBuilder(builder, ruleSets);
|
||||||
|
return builder.toDateTimeZone(iName);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds zone info to the builder.
|
||||||
|
*/
|
||||||
|
public void addToBuilder(DateTimeZoneBuilder builder, Map<String, RuleSet> ruleSets) {
|
||||||
|
addToBuilder(this, builder, ruleSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addToBuilder(Zone zone,
|
||||||
|
DateTimeZoneBuilder builder,
|
||||||
|
Map<String, RuleSet> ruleSets)
|
||||||
|
{
|
||||||
|
for (; zone != null; zone = zone.iNext) {
|
||||||
|
builder.setStandardOffset(zone.iOffsetMillis);
|
||||||
|
|
||||||
|
if (zone.iRules == null) {
|
||||||
|
builder.setFixedSavings(zone.iFormat, 0);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Check if iRules actually just refers to a savings.
|
||||||
|
int saveMillis = parseTime(zone.iRules);
|
||||||
|
builder.setFixedSavings(zone.iFormat, saveMillis);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
RuleSet rs = ruleSets.get(zone.iRules);
|
||||||
|
if (rs == null) {
|
||||||
|
throw new IllegalArgumentException
|
||||||
|
("Rules not found: " + zone.iRules);
|
||||||
|
}
|
||||||
|
rs.addRecurring(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zone.iUntilYear == Integer.MAX_VALUE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
zone.iUntilDateTimeOfYear.addCutover(builder, zone.iUntilYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String str =
|
||||||
|
"[Zone]\n" +
|
||||||
|
"Name: " + iName + "\n" +
|
||||||
|
"OffsetMillis: " + iOffsetMillis + "\n" +
|
||||||
|
"Rules: " + iRules + "\n" +
|
||||||
|
"Format: " + iFormat + "\n" +
|
||||||
|
"UntilYear: " + iUntilYear + "\n" +
|
||||||
|
iUntilDateTimeOfYear;
|
||||||
|
|
||||||
|
if (iNext == null) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + "...\n" + iNext.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user