java.time: add support for time zones

This commit is contained in:
Alexey Andreev 2020-05-05 18:15:44 +03:00 committed by Alexey Andreev
parent 81878548b4
commit 2924af963d
11 changed files with 252 additions and 162 deletions

View File

@ -15,6 +15,7 @@
*/
package org.teavm.classlib.impl.tz;
import java.time.zone.ZoneRules;
import org.teavm.classlib.impl.Base46;
public class AliasDateTimeZone extends StorableDateTimeZone {
@ -55,4 +56,9 @@ public class AliasDateTimeZone extends StorableDateTimeZone {
Base46.encodeUnsigned(sb, ALIAS);
sb.append(innerZone.getID());
}
@Override
public ZoneRules asZoneRules() {
return innerZone.asZoneRules();
}
}

View File

@ -15,6 +15,8 @@
*/
package org.teavm.classlib.impl.tz;
import java.time.zone.ZoneRules;
/**
* 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
@ -97,6 +99,11 @@ public final class CachedDateTimeZone extends StorableDateTimeZone {
return iZone.previousTransition(instant);
}
@Override
public ZoneRules asZoneRules() {
return iZone.asZoneRules();
}
// Although accessed by multiple threads, this method doesn't need to be
// synchronized.

View File

@ -15,6 +15,8 @@
*/
package org.teavm.classlib.impl.tz;
import java.time.zone.ZoneRules;
/**
* DateTimeZone represents a time zone.
* <p>
@ -485,4 +487,6 @@ public abstract class DateTimeZone {
* @return milliseconds from 1970-01-01T00:00:00Z
*/
public abstract long previousTransition(long instant);
public abstract ZoneRules asZoneRules();
}

View File

@ -15,6 +15,14 @@
*/
package org.teavm.classlib.impl.tz;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneOffset;
import java.time.zone.ZoneOffsetTransition;
import java.time.zone.ZoneOffsetTransitionRule;
import java.time.zone.ZoneRules;
import java.util.*;
import org.teavm.classlib.impl.Base46;
import org.teavm.classlib.impl.CharFlow;
@ -323,7 +331,7 @@ public class DateTimeZoneBuilder {
}
Base46.encodeUnsigned(sb, flags);
Base46.encodeUnsigned(sb, iMonthOfYear);
Base46.encodeUnsigned(sb, iDayOfMonth);
Base46.encode(sb, iDayOfMonth);
if (iDayOfWeek != 0) {
Base46.encode(sb, iDayOfWeek);
}
@ -350,7 +358,7 @@ public class DateTimeZoneBuilder {
}
int monthOfYear = Base46.decodeUnsigned(flow);
int dayOfMonth = Base46.decodeUnsigned(flow);
int dayOfMonth = Base46.decode(flow);
int dayOfWeek = hasDayOfWeek ? Base46.decode(flow) : 0;
int millisOfDay = (int) StorableDateTimeZone.readUnsignedTime(flow);
return new OfYear(mode, monthOfYear, dayOfMonth, dayOfWeek, advance, millisOfDay);
@ -1065,6 +1073,11 @@ public class DateTimeZoneBuilder {
Recurrence endRecurrence = Recurrence.read(flow);
return new DSTZone(id, standardOffset, startRecurrence, endRecurrence);
}
@Override
public ZoneRules asZoneRules() {
return null;
}
}
static final class PrecalculatedZone extends StorableDateTimeZone {
@ -1350,6 +1363,117 @@ public class DateTimeZoneBuilder {
return false;
}
@Override
public ZoneRules asZoneRules() {
List<ZoneOffsetTransition> standardTransitions = new ArrayList<>();
List<ZoneOffsetTransition> transitions = new ArrayList<>();
ZoneOffset firstStandardOffset = ZoneOffset.ofTotalSeconds(iStandardOffsets[0] / 1000);
ZoneOffset firstOffset = ZoneOffset.ofTotalSeconds(iWallOffsets[0] / 1000);
ZoneOffset lastStandardOffset = firstStandardOffset;
ZoneOffset lastOffset = firstOffset;
long time = Long.MIN_VALUE;
for (int i = 1; i < iTransitions.length; ++i) {
time = iTransitions[i];
LocalDateTime transitionTime = LocalDateTime.ofEpochSecond(time / 1000, 0, ZoneOffset.UTC);
if (iStandardOffsets[i] != iStandardOffsets[i - 1]) {
int offsetInSeconds = iStandardOffsets[i] / 1000;
int lastOffsetInSeconds = lastStandardOffset.getTotalSeconds();
ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetInSeconds);
standardTransitions.add(ZoneOffsetTransition.of(transitionTime.plusSeconds(lastOffsetInSeconds),
lastStandardOffset, offset));
lastStandardOffset = offset;
}
if (iWallOffsets[i] != iWallOffsets[i - 1]) {
ZoneOffset offset = ZoneOffset.ofTotalSeconds(iWallOffsets[i] / 1000);
int lastOffsetInSeconds = lastOffset.getTotalSeconds();
transitions.add(ZoneOffsetTransition.of(transitionTime.plusSeconds(lastOffsetInSeconds),
lastOffset, offset));
lastOffset = offset;
}
}
List<ZoneOffsetTransitionRule> lastRules;
if (iTailZone != null) {
if (time != Long.MIN_VALUE) {
int count = 0;
while (count < 2) {
time = iTailZone.nextTransition(time);
LocalDateTime transitionTime = LocalDateTime.ofEpochSecond(time / 1000, 0, ZoneOffset.UTC);
int newOffset = iTailZone.getOffset(time) / 1000;
if (newOffset != lastOffset.getTotalSeconds()) {
transitions.add(ZoneOffsetTransition.of(
transitionTime.plusSeconds(lastOffset.getTotalSeconds()),
lastOffset, ZoneOffset.ofTotalSeconds(newOffset)
));
count++;
lastOffset = ZoneOffset.ofTotalSeconds(newOffset);
}
}
}
ZoneOffset tailStandardOffset = ZoneOffset.ofTotalSeconds(iTailZone.iStandardOffset / 1000);
ZoneOffset startRecurrenceOffset = ZoneOffset.ofTotalSeconds(
(iTailZone.iStandardOffset + iTailZone.iStartRecurrence.iSaveMillis) / 1000);
ZoneOffset endRecurrenceOffset = ZoneOffset.ofTotalSeconds(
(iTailZone.iStandardOffset + iTailZone.iEndRecurrence.iSaveMillis) / 1000);
ZoneOffsetTransitionRule firstRule = createRule(iTailZone.iStartRecurrence.iOfYear, tailStandardOffset,
endRecurrenceOffset, startRecurrenceOffset);
ZoneOffsetTransitionRule lastRule = createRule(iTailZone.iEndRecurrence.iOfYear, tailStandardOffset,
startRecurrenceOffset, endRecurrenceOffset);
lastRules = Arrays.asList(firstRule, lastRule);
} else {
lastRules = Collections.emptyList();
}
return ZoneRules.of(firstStandardOffset, firstOffset, standardTransitions, transitions, lastRules);
}
private ZoneOffsetTransitionRule createRule(OfYear ofYear, ZoneOffset standardOffset,
ZoneOffset offsetBefore, ZoneOffset offsetAfter) {
int millisOfDay = ofYear.iMillisOfDay;
boolean midnight = false;
if (millisOfDay == 24 * 60 * 60 * 1000) {
millisOfDay = 0;
midnight = true;
}
ZoneOffsetTransitionRule.TimeDefinition mode;
switch (ofYear.iMode) {
case 'u':
mode = ZoneOffsetTransitionRule.TimeDefinition.UTC;
break;
case 'w':
mode = ZoneOffsetTransitionRule.TimeDefinition.WALL;
break;
case 's':
mode = ZoneOffsetTransitionRule.TimeDefinition.STANDARD;
break;
default:
throw new IllegalArgumentException();
}
Month month = Month.of(ofYear.iMonthOfYear);
int dayOfMonthIndicator = ofYear.iDayOfMonth;
if (dayOfMonthIndicator < 0) {
if (month != Month.FEBRUARY) {
dayOfMonthIndicator = month.maxLength() - 6;
}
}
return ZoneOffsetTransitionRule.of(
month,
dayOfMonthIndicator,
ofYear.iDayOfWeek != 0 ? DayOfWeek.of(ofYear.iDayOfWeek) : null,
LocalTime.ofSecondOfDay(millisOfDay / 1000),
midnight,
mode,
standardOffset,
offsetBefore,
offsetAfter
);
}
}
static final class RuleBasedZone extends StorableDateTimeZone {
@ -1521,5 +1645,11 @@ public class DateTimeZoneBuilder {
}
return filtered;
}
@Override
public ZoneRules asZoneRules() {
initZone();
return zone.asZoneRules();
}
}
}

View File

@ -15,6 +15,9 @@
*/
package org.teavm.classlib.impl.tz;
import java.time.ZoneOffset;
import java.time.zone.ZoneRules;
import java.util.Collections;
import org.teavm.classlib.impl.Base46;
import org.teavm.classlib.impl.CharFlow;
@ -78,4 +81,19 @@ public final class FixedDateTimeZone extends StorableDateTimeZone {
int standardOffset = (int) readTime(flow);
return new FixedDateTimeZone(id, wallOffset, standardOffset);
}
@Override
public ZoneRules asZoneRules() {
if (iWallOffset == iStandardOffset) {
return ZoneRules.of(ZoneOffset.ofTotalSeconds(iStandardOffset / 1000));
} else {
return ZoneRules.of(
ZoneOffset.ofTotalSeconds(iStandardOffset / 1000),
ZoneOffset.ofTotalSeconds(iWallOffset / 1000),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList()
);
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2020 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.threeten.bp.zone;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import org.teavm.classlib.impl.tz.DateTimeZone;
import org.teavm.classlib.impl.tz.DateTimeZoneProvider;
class JodaRulesProvider extends ZoneRulesProvider {
private Set<String> zoneIds;
private Map<String, ZoneRules> cache = new HashMap<>();
@Override
protected Set<String> provideZoneIds() {
if (zoneIds == null) {
zoneIds = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(DateTimeZoneProvider.getIds())));
}
return zoneIds;
}
@Override
protected ZoneRules provideRules(String regionId, boolean forCaching) {
ZoneRules result = cache.get(regionId);
if (result == null) {
DateTimeZone zone = DateTimeZoneProvider.getTimeZone(regionId);
if (zone == null) {
throw new ZoneRulesException(regionId);
}
result = (ZoneRules) (Object) zone.asZoneRules();
cache.put(regionId, result);
}
return result;
}
@Override
protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
ZoneRules result = provideRules(zoneId, false);
TreeMap<String, ZoneRules> map = new TreeMap<>();
map.put("", result);
return map;
}
}

View File

@ -38,7 +38,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.threeten.bp.Duration;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
@ -90,7 +89,7 @@ final class StandardZoneRules extends ZoneRules implements Serializable {
/**
* The map of recent transitions.
*/
private final Map<Integer, ZoneOffsetTransition[]> lastRulesCache = new HashMap<>();
private final Map<Integer, ZoneOffsetTransition[]> lastRulesCache = new HashMap<>();
/**
* Creates an instance.

View File

@ -1,137 +0,0 @@
/*
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of JSR-310 nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.threeten.bp.zone;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
/**
* Controls how the time-zone rules are initialized.
* <p>
* The default behavior is to use {@link ServiceLoader} to find instances of {@link ZoneRulesProvider}.
* Use the {@link #setInitializer(ZoneRulesInitializer)} method to replace this behavior.
* The initializer instance must perform the work of creating the {@code ZoneRulesProvider} within
* the {@link #initializeProviders()} method to ensure that the provider is not initialized too early.
* <p>
* <b>The initializer must be set before class loading of any other ThreeTen-Backport class to have any effect!</b>
* <p>
* This class has been added primarily for the benefit of Android.
*/
public abstract class ZoneRulesInitializer {
/**
* An instance that does nothing.
* Call {@link #setInitializer(ZoneRulesInitializer)} with this instance to
* block the service loader search. This will leave the system with no providers.
*/
public static final ZoneRulesInitializer DO_NOTHING = new DoNothingZoneRulesInitializer();
private static boolean INITIALIZED;
private static ZoneRulesInitializer INITIALIZER;
/**
* Sets the initializer to use.
* <p>
* This can only be invoked before the {@link ZoneRulesProvider} class is loaded.
* Invoking this method at a later point will throw an exception.
*
* @param initializer the initializer to use
* @throws IllegalStateException if initialization has already occurred or another initializer has been set
*/
public static void setInitializer(ZoneRulesInitializer initializer) {
if (INITIALIZED) {
throw new IllegalStateException("Already initialized");
}
if (INITIALIZER != null) {
throw new IllegalStateException("Initializer was already set, possibly with a default during initialization");
}
INITIALIZER = initializer;
}
//-----------------------------------------------------------------------
// initialize the providers
static void initialize() {
if (INITIALIZED) {
throw new IllegalStateException("Already initialized");
}
INITIALIZED = true;
// Set the default initializer if none has been provided yet.
if (INITIALIZER == null) {
INITIALIZER = new ServiceLoaderZoneRulesInitializer();
}
INITIALIZER.initializeProviders();
}
/**
* Initialize the providers.
* <p>
* The implementation should perform whatever work is necessary to initialize the providers.
* This will result in one or more calls to {@link ZoneRulesProvider#registerProvider(ZoneRulesProvider)}.
* <p>
* It is vital that the instance of {@link ZoneRulesProvider} is not created until this method is invoked.
* <p>
* It is guaranteed that this method will be invoked once and only once.
*/
protected abstract void initializeProviders();
//-----------------------------------------------------------------------
/**
* Implementation that does nothing.
*/
static class DoNothingZoneRulesInitializer extends ZoneRulesInitializer {
@Override
protected void initializeProviders() {
}
}
/**
* Implementation that uses the service loader.
*/
static class ServiceLoaderZoneRulesInitializer extends ZoneRulesInitializer {
@Override
protected void initializeProviders() {
ServiceLoader<ZoneRulesProvider> loader = ServiceLoader.load(ZoneRulesProvider.class, ZoneRulesProvider.class.getClassLoader());
for (ZoneRulesProvider provider : loader) {
try {
ZoneRulesProvider.registerProvider(provider);
} catch (ServiceConfigurationError ex) {
if (!(ex.getCause() instanceof SecurityException)) {
throw ex;
}
}
}
}
}
}

View File

@ -79,8 +79,9 @@ public abstract class ZoneRulesProvider {
* The lookup from zone region ID to provider.
*/
private static final Map<String, ZoneRulesProvider> ZONES = new HashMap<>();
static {
ZoneRulesInitializer.initialize();
registerProvider0(new JodaRulesProvider());
}
//-------------------------------------------------------------------------

View File

@ -452,12 +452,12 @@ public class TestLocalDate extends AbstractDateTimeTest {
assertEquals(LocalDate.ofEpochDay(minValidEpochdays), LocalDate.of(Year.MIN_VALUE, 1, 1));
LocalDate test = LocalDate.of(0, 1, 1);
for (long i = date0000x01x01; i < 700000; i++) {
for (long i = date0000x01x01; i < date0000x01x01 + 1000; i++) {
assertEquals(LocalDate.ofEpochDay(i), test);
test = next(test);
}
test = LocalDate.of(0, 1, 1);
for (long i = date0000x01x01; i > -2000000; i--) {
for (long i = date0000x01x01; i > date0000x01x01 - 1000; i--) {
assertEquals(LocalDate.ofEpochDay(i), test);
test = previous(test);
}
@ -1815,12 +1815,12 @@ public class TestLocalDate extends AbstractDateTimeTest {
long date0000x01x01 = -678941 - 40587;
LocalDate test = LocalDate.of(0, 1, 1);
for (long i = date0000x01x01; i < 700000; i++) {
for (long i = date0000x01x01; i < date0000x01x01 + 1000; i++) {
assertEquals(test.toEpochDay(), i);
test = next(test);
}
test = LocalDate.of(0, 1, 1);
for (long i = date0000x01x01; i > -2000000; i--) {
for (long i = date0000x01x01; i > date0000x01x01 - 1000; i--) {
assertEquals(test.toEpochDay(), i);
test = previous(test);
}

View File

@ -1398,7 +1398,7 @@ public class TestLocalDateTime extends AbstractDateTimeTest {
@Test(dataProvider = "samplePlusWeeksSymmetry")
public void test_plusWeeks_symmetry(LocalDateTime reference) {
for (int weeks = 0; weeks < 365 * 8; weeks++) {
for (int weeks = 0; weeks < 52 * 8; weeks++) {
LocalDateTime t = reference.plusWeeks(weeks).plusWeeks(-weeks);
assertEquals(t, reference);
@ -2108,7 +2108,7 @@ public class TestLocalDateTime extends AbstractDateTimeTest {
@Test(dataProvider = "sampleMinusWeeksSymmetry")
public void test_minusWeeks_symmetry(LocalDateTime reference) {
for (int weeks = 0; weeks < 365 * 8; weeks++) {
for (int weeks = 0; weeks < 52 * 8; weeks++) {
LocalDateTime t = reference.minusWeeks(weeks).minusWeeks(-weeks);
assertEquals(t, reference);
@ -2728,7 +2728,7 @@ public class TestLocalDateTime extends AbstractDateTimeTest {
public void test_toEpochSecond_afterEpoch() {
for (int i = -5; i < 5; i++) {
ZoneOffset offset = ZoneOffset.ofHours(i);
for (int j = 0; j < 100000; j++) {
for (int j = 0; j < 1000; j++) {
LocalDateTime a = LocalDateTime.of(1970, 1, 1, 0, 0).plusSeconds(j);
assertEquals(a.toEpochSecond(offset), j - i * 3600);
}
@ -2737,7 +2737,7 @@ public class TestLocalDateTime extends AbstractDateTimeTest {
@Test
public void test_toEpochSecond_beforeEpoch() {
for (int i = 0; i < 100000; i++) {
for (int i = 0; i < 1000; i++) {
LocalDateTime a = LocalDateTime.of(1970, 1, 1, 0, 0).minusSeconds(i);
assertEquals(a.toEpochSecond(ZoneOffset.UTC), -i);
}
@ -2808,20 +2808,20 @@ public class TestLocalDateTime extends AbstractDateTimeTest {
for (int j = 0; j < localDateTimes.length; j++) {
LocalDateTime b = localDateTimes[j];
if (i < j) {
assertTrue(a.compareTo(b) < 0, a + " <=> " + b);
assertEquals(a.isBefore(b), true, a + " <=> " + b);
assertEquals(a.isAfter(b), false, a + " <=> " + b);
assertEquals(a.equals(b), false, a + " <=> " + b);
assertTrue(a.compareTo(b) < 0);
assertTrue(a.isBefore(b));
assertFalse(a.isAfter(b));
assertFalse(a.equals(b));
} else if (i > j) {
assertTrue(a.compareTo(b) > 0, a + " <=> " + b);
assertEquals(a.isBefore(b), false, a + " <=> " + b);
assertEquals(a.isAfter(b), true, a + " <=> " + b);
assertEquals(a.equals(b), false, a + " <=> " + b);
assertTrue(a.compareTo(b) > 0);
assertFalse(a.isBefore(b));
assertTrue(a.isAfter(b));
assertFalse(a.equals(b));
} else {
assertEquals(a.compareTo(b), 0, a + " <=> " + b);
assertEquals(a.isBefore(b), false, a + " <=> " + b);
assertEquals(a.isAfter(b), false, a + " <=> " + b);
assertEquals(a.equals(b), true, a + " <=> " + b);
assertEquals(a.compareTo(b), 0);
assertFalse(a.isBefore(b));
assertFalse(a.isAfter(b));
assertTrue(a.equals(b));
}
}
}