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
10
NOTICE
10
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
|
||||
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