Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/java/time/zone/ZoneRules.java
41159 views
1
/*
2
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
/*
27
* This file is available under and governed by the GNU General Public
28
* License version 2 only, as published by the Free Software Foundation.
29
* However, the following notice accompanied the original version of this
30
* file:
31
*
32
* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
33
*
34
* All rights reserved.
35
*
36
* Redistribution and use in source and binary forms, with or without
37
* modification, are permitted provided that the following conditions are met:
38
*
39
* * Redistributions of source code must retain the above copyright notice,
40
* this list of conditions and the following disclaimer.
41
*
42
* * Redistributions in binary form must reproduce the above copyright notice,
43
* this list of conditions and the following disclaimer in the documentation
44
* and/or other materials provided with the distribution.
45
*
46
* * Neither the name of JSR-310 nor the names of its contributors
47
* may be used to endorse or promote products derived from this software
48
* without specific prior written permission.
49
*
50
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
51
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
52
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
53
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
54
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
55
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
56
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
57
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
58
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
59
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
60
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61
*/
62
package java.time.zone;
63
64
import java.io.DataInput;
65
import java.io.DataOutput;
66
import java.io.IOException;
67
import java.io.InvalidObjectException;
68
import java.io.ObjectInputStream;
69
import java.io.Serializable;
70
import java.time.Duration;
71
import java.time.Instant;
72
import java.time.LocalDateTime;
73
import java.time.ZoneId;
74
import java.time.ZoneOffset;
75
import java.time.Year;
76
import java.util.ArrayList;
77
import java.util.Arrays;
78
import java.util.Collections;
79
import java.util.List;
80
import java.util.Objects;
81
import java.util.concurrent.ConcurrentHashMap;
82
import java.util.concurrent.ConcurrentMap;
83
84
/**
85
* The rules defining how the zone offset varies for a single time-zone.
86
* <p>
87
* The rules model all the historic and future transitions for a time-zone.
88
* {@link ZoneOffsetTransition} is used for known transitions, typically historic.
89
* {@link ZoneOffsetTransitionRule} is used for future transitions that are based
90
* on the result of an algorithm.
91
* <p>
92
* The rules are loaded via {@link ZoneRulesProvider} using a {@link ZoneId}.
93
* The same rules may be shared internally between multiple zone IDs.
94
* <p>
95
* Serializing an instance of {@code ZoneRules} will store the entire set of rules.
96
* It does not store the zone ID as it is not part of the state of this object.
97
* <p>
98
* A rule implementation may or may not store full information about historic
99
* and future transitions, and the information stored is only as accurate as
100
* that supplied to the implementation by the rules provider.
101
* Applications should treat the data provided as representing the best information
102
* available to the implementation of this rule.
103
*
104
* @implSpec
105
* This class is immutable and thread-safe.
106
*
107
* @since 1.8
108
*/
109
public final class ZoneRules implements Serializable {
110
111
/**
112
* Serialization version.
113
*/
114
private static final long serialVersionUID = 3044319355680032515L;
115
/**
116
* The last year to have its transitions cached.
117
*/
118
private static final int LAST_CACHED_YEAR = 2100;
119
120
/**
121
* The transitions between standard offsets (epoch seconds), sorted.
122
*/
123
private final long[] standardTransitions;
124
/**
125
* The standard offsets.
126
*/
127
private final ZoneOffset[] standardOffsets;
128
/**
129
* The transitions between instants (epoch seconds), sorted.
130
*/
131
private final long[] savingsInstantTransitions;
132
/**
133
* The transitions between local date-times, sorted.
134
* This is a paired array, where the first entry is the start of the transition
135
* and the second entry is the end of the transition.
136
*/
137
private final LocalDateTime[] savingsLocalTransitions;
138
/**
139
* The wall offsets.
140
*/
141
private final ZoneOffset[] wallOffsets;
142
/**
143
* The last rule.
144
*/
145
private final ZoneOffsetTransitionRule[] lastRules;
146
/**
147
* The map of recent transitions.
148
*/
149
private final transient ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache =
150
new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>();
151
/**
152
* The zero-length long array.
153
*/
154
private static final long[] EMPTY_LONG_ARRAY = new long[0];
155
/**
156
* The zero-length lastrules array.
157
*/
158
private static final ZoneOffsetTransitionRule[] EMPTY_LASTRULES =
159
new ZoneOffsetTransitionRule[0];
160
/**
161
* The zero-length ldt array.
162
*/
163
private static final LocalDateTime[] EMPTY_LDT_ARRAY = new LocalDateTime[0];
164
/**
165
* The number of days in a 400 year cycle.
166
*/
167
private static final int DAYS_PER_CYCLE = 146097;
168
/**
169
* The number of days from year zero to year 1970.
170
* There are five 400 year cycles from year zero to 2000.
171
* There are 7 leap years from 1970 to 2000.
172
*/
173
private static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);
174
175
/**
176
* Obtains an instance of a ZoneRules.
177
*
178
* @param baseStandardOffset the standard offset to use before legal rules were set, not null
179
* @param baseWallOffset the wall offset to use before legal rules were set, not null
180
* @param standardOffsetTransitionList the list of changes to the standard offset, not null
181
* @param transitionList the list of transitions, not null
182
* @param lastRules the recurring last rules, size 16 or less, not null
183
* @return the zone rules, not null
184
*/
185
public static ZoneRules of(ZoneOffset baseStandardOffset,
186
ZoneOffset baseWallOffset,
187
List<ZoneOffsetTransition> standardOffsetTransitionList,
188
List<ZoneOffsetTransition> transitionList,
189
List<ZoneOffsetTransitionRule> lastRules) {
190
Objects.requireNonNull(baseStandardOffset, "baseStandardOffset");
191
Objects.requireNonNull(baseWallOffset, "baseWallOffset");
192
Objects.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList");
193
Objects.requireNonNull(transitionList, "transitionList");
194
Objects.requireNonNull(lastRules, "lastRules");
195
return new ZoneRules(baseStandardOffset, baseWallOffset,
196
standardOffsetTransitionList, transitionList, lastRules);
197
}
198
199
/**
200
* Obtains an instance of ZoneRules that has fixed zone rules.
201
*
202
* @param offset the offset this fixed zone rules is based on, not null
203
* @return the zone rules, not null
204
* @see #isFixedOffset()
205
*/
206
public static ZoneRules of(ZoneOffset offset) {
207
Objects.requireNonNull(offset, "offset");
208
return new ZoneRules(offset);
209
}
210
211
/**
212
* Creates an instance.
213
*
214
* @param baseStandardOffset the standard offset to use before legal rules were set, not null
215
* @param baseWallOffset the wall offset to use before legal rules were set, not null
216
* @param standardOffsetTransitionList the list of changes to the standard offset, not null
217
* @param transitionList the list of transitions, not null
218
* @param lastRules the recurring last rules, size 16 or less, not null
219
*/
220
ZoneRules(ZoneOffset baseStandardOffset,
221
ZoneOffset baseWallOffset,
222
List<ZoneOffsetTransition> standardOffsetTransitionList,
223
List<ZoneOffsetTransition> transitionList,
224
List<ZoneOffsetTransitionRule> lastRules) {
225
super();
226
227
// convert standard transitions
228
229
this.standardTransitions = new long[standardOffsetTransitionList.size()];
230
231
this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1];
232
this.standardOffsets[0] = baseStandardOffset;
233
for (int i = 0; i < standardOffsetTransitionList.size(); i++) {
234
this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond();
235
this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter();
236
}
237
238
// convert savings transitions to locals
239
List<LocalDateTime> localTransitionList = new ArrayList<>();
240
List<ZoneOffset> localTransitionOffsetList = new ArrayList<>();
241
localTransitionOffsetList.add(baseWallOffset);
242
for (ZoneOffsetTransition trans : transitionList) {
243
if (trans.isGap()) {
244
localTransitionList.add(trans.getDateTimeBefore());
245
localTransitionList.add(trans.getDateTimeAfter());
246
} else {
247
localTransitionList.add(trans.getDateTimeAfter());
248
localTransitionList.add(trans.getDateTimeBefore());
249
}
250
localTransitionOffsetList.add(trans.getOffsetAfter());
251
}
252
this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
253
this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]);
254
255
// convert savings transitions to instants
256
this.savingsInstantTransitions = new long[transitionList.size()];
257
for (int i = 0; i < transitionList.size(); i++) {
258
this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond();
259
}
260
261
// last rules
262
Object[] temp = lastRules.toArray();
263
ZoneOffsetTransitionRule[] rulesArray = Arrays.copyOf(temp, temp.length, ZoneOffsetTransitionRule[].class);
264
if (rulesArray.length > 16) {
265
throw new IllegalArgumentException("Too many transition rules");
266
}
267
this.lastRules = rulesArray;
268
}
269
270
/**
271
* Constructor.
272
*
273
* @param standardTransitions the standard transitions, not null
274
* @param standardOffsets the standard offsets, not null
275
* @param savingsInstantTransitions the standard transitions, not null
276
* @param wallOffsets the wall offsets, not null
277
* @param lastRules the recurring last rules, size 15 or less, not null
278
*/
279
private ZoneRules(long[] standardTransitions,
280
ZoneOffset[] standardOffsets,
281
long[] savingsInstantTransitions,
282
ZoneOffset[] wallOffsets,
283
ZoneOffsetTransitionRule[] lastRules) {
284
super();
285
286
this.standardTransitions = standardTransitions;
287
this.standardOffsets = standardOffsets;
288
this.savingsInstantTransitions = savingsInstantTransitions;
289
this.wallOffsets = wallOffsets;
290
this.lastRules = lastRules;
291
292
if (savingsInstantTransitions.length == 0) {
293
this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
294
} else {
295
// convert savings transitions to locals
296
List<LocalDateTime> localTransitionList = new ArrayList<>();
297
for (int i = 0; i < savingsInstantTransitions.length; i++) {
298
ZoneOffset before = wallOffsets[i];
299
ZoneOffset after = wallOffsets[i + 1];
300
ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after);
301
if (trans.isGap()) {
302
localTransitionList.add(trans.getDateTimeBefore());
303
localTransitionList.add(trans.getDateTimeAfter());
304
} else {
305
localTransitionList.add(trans.getDateTimeAfter());
306
localTransitionList.add(trans.getDateTimeBefore());
307
}
308
}
309
this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
310
}
311
}
312
313
/**
314
* Creates an instance of ZoneRules that has fixed zone rules.
315
*
316
* @param offset the offset this fixed zone rules is based on, not null
317
* @see #isFixedOffset()
318
*/
319
private ZoneRules(ZoneOffset offset) {
320
this.standardOffsets = new ZoneOffset[1];
321
this.standardOffsets[0] = offset;
322
this.standardTransitions = EMPTY_LONG_ARRAY;
323
this.savingsInstantTransitions = EMPTY_LONG_ARRAY;
324
this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
325
this.wallOffsets = standardOffsets;
326
this.lastRules = EMPTY_LASTRULES;
327
}
328
329
/**
330
* Defend against malicious streams.
331
*
332
* @param s the stream to read
333
* @throws InvalidObjectException always
334
*/
335
private void readObject(ObjectInputStream s) throws InvalidObjectException {
336
throw new InvalidObjectException("Deserialization via serialization delegate");
337
}
338
339
/**
340
* Writes the object using a
341
* <a href="{@docRoot}/serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
342
* @serialData
343
* <pre style="font-size:1.0em">{@code
344
*
345
* out.writeByte(1); // identifies a ZoneRules
346
* out.writeInt(standardTransitions.length);
347
* for (long trans : standardTransitions) {
348
* Ser.writeEpochSec(trans, out);
349
* }
350
* for (ZoneOffset offset : standardOffsets) {
351
* Ser.writeOffset(offset, out);
352
* }
353
* out.writeInt(savingsInstantTransitions.length);
354
* for (long trans : savingsInstantTransitions) {
355
* Ser.writeEpochSec(trans, out);
356
* }
357
* for (ZoneOffset offset : wallOffsets) {
358
* Ser.writeOffset(offset, out);
359
* }
360
* out.writeByte(lastRules.length);
361
* for (ZoneOffsetTransitionRule rule : lastRules) {
362
* rule.writeExternal(out);
363
* }
364
* }
365
* </pre>
366
* <p>
367
* Epoch second values used for offsets are encoded in a variable
368
* length form to make the common cases put fewer bytes in the stream.
369
* <pre style="font-size:1.0em">{@code
370
*
371
* static void writeEpochSec(long epochSec, DataOutput out) throws IOException {
372
* if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) { // quarter hours between 1825 and 2300
373
* int store = (int) ((epochSec + 4575744000L) / 900);
374
* out.writeByte((store >>> 16) & 255);
375
* out.writeByte((store >>> 8) & 255);
376
* out.writeByte(store & 255);
377
* } else {
378
* out.writeByte(255);
379
* out.writeLong(epochSec);
380
* }
381
* }
382
* }
383
* </pre>
384
* <p>
385
* ZoneOffset values are encoded in a variable length form so the
386
* common cases put fewer bytes in the stream.
387
* <pre style="font-size:1.0em">{@code
388
*
389
* static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException {
390
* final int offsetSecs = offset.getTotalSeconds();
391
* int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72
392
* out.writeByte(offsetByte);
393
* if (offsetByte == 127) {
394
* out.writeInt(offsetSecs);
395
* }
396
* }
397
*}
398
* </pre>
399
* @return the replacing object, not null
400
*/
401
private Object writeReplace() {
402
return new Ser(Ser.ZRULES, this);
403
}
404
405
/**
406
* Writes the state to the stream.
407
*
408
* @param out the output stream, not null
409
* @throws IOException if an error occurs
410
*/
411
void writeExternal(DataOutput out) throws IOException {
412
out.writeInt(standardTransitions.length);
413
for (long trans : standardTransitions) {
414
Ser.writeEpochSec(trans, out);
415
}
416
for (ZoneOffset offset : standardOffsets) {
417
Ser.writeOffset(offset, out);
418
}
419
out.writeInt(savingsInstantTransitions.length);
420
for (long trans : savingsInstantTransitions) {
421
Ser.writeEpochSec(trans, out);
422
}
423
for (ZoneOffset offset : wallOffsets) {
424
Ser.writeOffset(offset, out);
425
}
426
out.writeByte(lastRules.length);
427
for (ZoneOffsetTransitionRule rule : lastRules) {
428
rule.writeExternal(out);
429
}
430
}
431
432
/**
433
* Reads the state from the stream.
434
*
435
* @param in the input stream, not null
436
* @return the created object, not null
437
* @throws IOException if an error occurs
438
*/
439
static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException {
440
int stdSize = in.readInt();
441
long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY
442
: new long[stdSize];
443
for (int i = 0; i < stdSize; i++) {
444
stdTrans[i] = Ser.readEpochSec(in);
445
}
446
ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1];
447
for (int i = 0; i < stdOffsets.length; i++) {
448
stdOffsets[i] = Ser.readOffset(in);
449
}
450
int savSize = in.readInt();
451
long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY
452
: new long[savSize];
453
for (int i = 0; i < savSize; i++) {
454
savTrans[i] = Ser.readEpochSec(in);
455
}
456
ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1];
457
for (int i = 0; i < savOffsets.length; i++) {
458
savOffsets[i] = Ser.readOffset(in);
459
}
460
int ruleSize = in.readByte();
461
ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ?
462
EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize];
463
for (int i = 0; i < ruleSize; i++) {
464
rules[i] = ZoneOffsetTransitionRule.readExternal(in);
465
}
466
return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules);
467
}
468
469
/**
470
* Checks of the zone rules are fixed, such that the offset never varies.
471
*
472
* @return true if the time-zone is fixed and the offset never changes
473
*/
474
public boolean isFixedOffset() {
475
return standardOffsets[0].equals(wallOffsets[0]) &&
476
standardTransitions.length == 0 &&
477
savingsInstantTransitions.length == 0 &&
478
lastRules.length == 0;
479
}
480
481
/**
482
* Gets the offset applicable at the specified instant in these rules.
483
* <p>
484
* The mapping from an instant to an offset is simple, there is only
485
* one valid offset for each instant.
486
* This method returns that offset.
487
*
488
* @param instant the instant to find the offset for, not null, but null
489
* may be ignored if the rules have a single offset for all instants
490
* @return the offset, not null
491
*/
492
public ZoneOffset getOffset(Instant instant) {
493
if (savingsInstantTransitions.length == 0) {
494
return wallOffsets[0];
495
}
496
long epochSec = instant.getEpochSecond();
497
// check if using last rules
498
if (lastRules.length > 0 &&
499
epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
500
int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
501
ZoneOffsetTransition[] transArray = findTransitionArray(year);
502
ZoneOffsetTransition trans = null;
503
for (int i = 0; i < transArray.length; i++) {
504
trans = transArray[i];
505
if (epochSec < trans.toEpochSecond()) {
506
return trans.getOffsetBefore();
507
}
508
}
509
return trans.getOffsetAfter();
510
}
511
512
// using historic rules
513
int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
514
if (index < 0) {
515
// switch negative insert position to start of matched range
516
index = -index - 2;
517
}
518
return wallOffsets[index + 1];
519
}
520
521
/**
522
* Gets a suitable offset for the specified local date-time in these rules.
523
* <p>
524
* The mapping from a local date-time to an offset is not straightforward.
525
* There are three cases:
526
* <ul>
527
* <li>Normal, with one valid offset. For the vast majority of the year, the normal
528
* case applies, where there is a single valid offset for the local date-time.</li>
529
* <li>Gap, with zero valid offsets. This is when clocks jump forward typically
530
* due to the spring daylight savings change from "winter" to "summer".
531
* In a gap there are local date-time values with no valid offset.</li>
532
* <li>Overlap, with two valid offsets. This is when clocks are set back typically
533
* due to the autumn daylight savings change from "summer" to "winter".
534
* In an overlap there are local date-time values with two valid offsets.</li>
535
* </ul>
536
* Thus, for any given local date-time there can be zero, one or two valid offsets.
537
* This method returns the single offset in the Normal case, and in the Gap or Overlap
538
* case it returns the offset before the transition.
539
* <p>
540
* Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather
541
* than the "correct" value, it should be treated with care. Applications that care
542
* about the correct offset should use a combination of this method,
543
* {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}.
544
*
545
* @param localDateTime the local date-time to query, not null, but null
546
* may be ignored if the rules have a single offset for all instants
547
* @return the best available offset for the local date-time, not null
548
*/
549
public ZoneOffset getOffset(LocalDateTime localDateTime) {
550
Object info = getOffsetInfo(localDateTime);
551
if (info instanceof ZoneOffsetTransition) {
552
return ((ZoneOffsetTransition) info).getOffsetBefore();
553
}
554
return (ZoneOffset) info;
555
}
556
557
/**
558
* Gets the offset applicable at the specified local date-time in these rules.
559
* <p>
560
* The mapping from a local date-time to an offset is not straightforward.
561
* There are three cases:
562
* <ul>
563
* <li>Normal, with one valid offset. For the vast majority of the year, the normal
564
* case applies, where there is a single valid offset for the local date-time.</li>
565
* <li>Gap, with zero valid offsets. This is when clocks jump forward typically
566
* due to the spring daylight savings change from "winter" to "summer".
567
* In a gap there are local date-time values with no valid offset.</li>
568
* <li>Overlap, with two valid offsets. This is when clocks are set back typically
569
* due to the autumn daylight savings change from "summer" to "winter".
570
* In an overlap there are local date-time values with two valid offsets.</li>
571
* </ul>
572
* Thus, for any given local date-time there can be zero, one or two valid offsets.
573
* This method returns that list of valid offsets, which is a list of size 0, 1 or 2.
574
* In the case where there are two offsets, the earlier offset is returned at index 0
575
* and the later offset at index 1.
576
* <p>
577
* There are various ways to handle the conversion from a {@code LocalDateTime}.
578
* One technique, using this method, would be:
579
* <pre>
580
* List&lt;ZoneOffset&gt; validOffsets = rules.getValidOffsets(localDT);
581
* if (validOffsets.size() == 1) {
582
* // Normal case: only one valid offset
583
* zoneOffset = validOffsets.get(0);
584
* } else {
585
* // Gap or Overlap: determine what to do from transition (which will be non-null)
586
* ZoneOffsetTransition trans = rules.getTransition(localDT);
587
* }
588
* </pre>
589
* <p>
590
* In theory, it is possible for there to be more than two valid offsets.
591
* This would happen if clocks to be put back more than once in quick succession.
592
* This has never happened in the history of time-zones and thus has no special handling.
593
* However, if it were to happen, then the list would return more than 2 entries.
594
*
595
* @param localDateTime the local date-time to query for valid offsets, not null, but null
596
* may be ignored if the rules have a single offset for all instants
597
* @return the list of valid offsets, may be immutable, not null
598
*/
599
public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) {
600
// should probably be optimized
601
Object info = getOffsetInfo(localDateTime);
602
if (info instanceof ZoneOffsetTransition) {
603
return ((ZoneOffsetTransition) info).getValidOffsets();
604
}
605
return Collections.singletonList((ZoneOffset) info);
606
}
607
608
/**
609
* Gets the offset transition applicable at the specified local date-time in these rules.
610
* <p>
611
* The mapping from a local date-time to an offset is not straightforward.
612
* There are three cases:
613
* <ul>
614
* <li>Normal, with one valid offset. For the vast majority of the year, the normal
615
* case applies, where there is a single valid offset for the local date-time.</li>
616
* <li>Gap, with zero valid offsets. This is when clocks jump forward typically
617
* due to the spring daylight savings change from "winter" to "summer".
618
* In a gap there are local date-time values with no valid offset.</li>
619
* <li>Overlap, with two valid offsets. This is when clocks are set back typically
620
* due to the autumn daylight savings change from "summer" to "winter".
621
* In an overlap there are local date-time values with two valid offsets.</li>
622
* </ul>
623
* A transition is used to model the cases of a Gap or Overlap.
624
* The Normal case will return null.
625
* <p>
626
* There are various ways to handle the conversion from a {@code LocalDateTime}.
627
* One technique, using this method, would be:
628
* <pre>
629
* ZoneOffsetTransition trans = rules.getTransition(localDT);
630
* if (trans != null) {
631
* // Gap or Overlap: determine what to do from transition
632
* } else {
633
* // Normal case: only one valid offset
634
* zoneOffset = rule.getOffset(localDT);
635
* }
636
* </pre>
637
*
638
* @param localDateTime the local date-time to query for offset transition, not null, but null
639
* may be ignored if the rules have a single offset for all instants
640
* @return the offset transition, null if the local date-time is not in transition
641
*/
642
public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {
643
Object info = getOffsetInfo(localDateTime);
644
return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null);
645
}
646
647
private Object getOffsetInfo(LocalDateTime dt) {
648
if (savingsLocalTransitions.length == 0) {
649
return wallOffsets[0];
650
}
651
// check if using last rules
652
if (lastRules.length > 0 &&
653
dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) {
654
ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear());
655
Object info = null;
656
for (ZoneOffsetTransition trans : transArray) {
657
info = findOffsetInfo(dt, trans);
658
if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) {
659
return info;
660
}
661
}
662
return info;
663
}
664
665
// using historic rules
666
int index = Arrays.binarySearch(savingsLocalTransitions, dt);
667
if (index == -1) {
668
// before first transition
669
return wallOffsets[0];
670
}
671
if (index < 0) {
672
// switch negative insert position to start of matched range
673
index = -index - 2;
674
} else if (index < savingsLocalTransitions.length - 1 &&
675
savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) {
676
// handle overlap immediately following gap
677
index++;
678
}
679
if ((index & 1) == 0) {
680
// gap or overlap
681
LocalDateTime dtBefore = savingsLocalTransitions[index];
682
LocalDateTime dtAfter = savingsLocalTransitions[index + 1];
683
ZoneOffset offsetBefore = wallOffsets[index / 2];
684
ZoneOffset offsetAfter = wallOffsets[index / 2 + 1];
685
if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) {
686
// gap
687
return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter);
688
} else {
689
// overlap
690
return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter);
691
}
692
} else {
693
// normal (neither gap or overlap)
694
return wallOffsets[index / 2 + 1];
695
}
696
}
697
698
/**
699
* Finds the offset info for a local date-time and transition.
700
*
701
* @param dt the date-time, not null
702
* @param trans the transition, not null
703
* @return the offset info, not null
704
*/
705
private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) {
706
LocalDateTime localTransition = trans.getDateTimeBefore();
707
if (trans.isGap()) {
708
if (dt.isBefore(localTransition)) {
709
return trans.getOffsetBefore();
710
}
711
if (dt.isBefore(trans.getDateTimeAfter())) {
712
return trans;
713
} else {
714
return trans.getOffsetAfter();
715
}
716
} else {
717
if (dt.isBefore(localTransition) == false) {
718
return trans.getOffsetAfter();
719
}
720
if (dt.isBefore(trans.getDateTimeAfter())) {
721
return trans.getOffsetBefore();
722
} else {
723
return trans;
724
}
725
}
726
}
727
728
/**
729
* Finds the appropriate transition array for the given year.
730
*
731
* @param year the year, not null
732
* @return the transition array, not null
733
*/
734
private ZoneOffsetTransition[] findTransitionArray(int year) {
735
Integer yearObj = year; // should use Year class, but this saves a class load
736
ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj);
737
if (transArray != null) {
738
return transArray;
739
}
740
ZoneOffsetTransitionRule[] ruleArray = lastRules;
741
transArray = new ZoneOffsetTransition[ruleArray.length];
742
for (int i = 0; i < ruleArray.length; i++) {
743
transArray[i] = ruleArray[i].createTransition(year);
744
}
745
if (year < LAST_CACHED_YEAR) {
746
lastRulesCache.putIfAbsent(yearObj, transArray);
747
}
748
return transArray;
749
}
750
751
/**
752
* Gets the standard offset for the specified instant in this zone.
753
* <p>
754
* This provides access to historic information on how the standard offset
755
* has changed over time.
756
* The standard offset is the offset before any daylight saving time is applied.
757
* This is typically the offset applicable during winter.
758
*
759
* @param instant the instant to find the offset information for, not null, but null
760
* may be ignored if the rules have a single offset for all instants
761
* @return the standard offset, not null
762
*/
763
public ZoneOffset getStandardOffset(Instant instant) {
764
if (standardTransitions.length == 0) {
765
return standardOffsets[0];
766
}
767
long epochSec = instant.getEpochSecond();
768
int index = Arrays.binarySearch(standardTransitions, epochSec);
769
if (index < 0) {
770
// switch negative insert position to start of matched range
771
index = -index - 2;
772
}
773
return standardOffsets[index + 1];
774
}
775
776
/**
777
* Gets the amount of daylight savings in use for the specified instant in this zone.
778
* <p>
779
* This provides access to historic information on how the amount of daylight
780
* savings has changed over time.
781
* This is the difference between the standard offset and the actual offset.
782
* Typically the amount is zero during winter and one hour during summer.
783
* Time-zones are second-based, so the nanosecond part of the duration will be zero.
784
* <p>
785
* This default implementation calculates the duration from the
786
* {@link #getOffset(java.time.Instant) actual} and
787
* {@link #getStandardOffset(java.time.Instant) standard} offsets.
788
*
789
* @param instant the instant to find the daylight savings for, not null, but null
790
* may be ignored if the rules have a single offset for all instants
791
* @return the difference between the standard and actual offset, not null
792
*/
793
public Duration getDaylightSavings(Instant instant) {
794
if (isFixedOffset()) {
795
return Duration.ZERO;
796
}
797
ZoneOffset standardOffset = getStandardOffset(instant);
798
ZoneOffset actualOffset = getOffset(instant);
799
return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds());
800
}
801
802
/**
803
* Checks if the specified instant is in daylight savings.
804
* <p>
805
* This checks if the standard offset and the actual offset are the same
806
* for the specified instant.
807
* If they are not, it is assumed that daylight savings is in operation.
808
* <p>
809
* This default implementation compares the {@link #getOffset(java.time.Instant) actual}
810
* and {@link #getStandardOffset(java.time.Instant) standard} offsets.
811
*
812
* @param instant the instant to find the offset information for, not null, but null
813
* may be ignored if the rules have a single offset for all instants
814
* @return the standard offset, not null
815
*/
816
public boolean isDaylightSavings(Instant instant) {
817
return (getStandardOffset(instant).equals(getOffset(instant)) == false);
818
}
819
820
/**
821
* Checks if the offset date-time is valid for these rules.
822
* <p>
823
* To be valid, the local date-time must not be in a gap and the offset
824
* must match one of the valid offsets.
825
* <p>
826
* This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)}
827
* contains the specified offset.
828
*
829
* @param localDateTime the date-time to check, not null, but null
830
* may be ignored if the rules have a single offset for all instants
831
* @param offset the offset to check, null returns false
832
* @return true if the offset date-time is valid for these rules
833
*/
834
public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) {
835
return getValidOffsets(localDateTime).contains(offset);
836
}
837
838
/**
839
* Gets the next transition after the specified instant.
840
* <p>
841
* This returns details of the next transition after the specified instant.
842
* For example, if the instant represents a point where "Summer" daylight savings time
843
* applies, then the method will return the transition to the next "Winter" time.
844
*
845
* @param instant the instant to get the next transition after, not null, but null
846
* may be ignored if the rules have a single offset for all instants
847
* @return the next transition after the specified instant, null if this is after the last transition
848
*/
849
public ZoneOffsetTransition nextTransition(Instant instant) {
850
if (savingsInstantTransitions.length == 0) {
851
return null;
852
}
853
long epochSec = instant.getEpochSecond();
854
// check if using last rules
855
if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
856
if (lastRules.length == 0) {
857
return null;
858
}
859
// search year the instant is in
860
int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
861
ZoneOffsetTransition[] transArray = findTransitionArray(year);
862
for (ZoneOffsetTransition trans : transArray) {
863
if (epochSec < trans.toEpochSecond()) {
864
return trans;
865
}
866
}
867
// use first from following year
868
if (year < Year.MAX_VALUE) {
869
transArray = findTransitionArray(year + 1);
870
return transArray[0];
871
}
872
return null;
873
}
874
875
// using historic rules
876
int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
877
if (index < 0) {
878
index = -index - 1; // switched value is the next transition
879
} else {
880
index += 1; // exact match, so need to add one to get the next
881
}
882
return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]);
883
}
884
885
/**
886
* Gets the previous transition before the specified instant.
887
* <p>
888
* This returns details of the previous transition before the specified instant.
889
* For example, if the instant represents a point where "summer" daylight saving time
890
* applies, then the method will return the transition from the previous "winter" time.
891
*
892
* @param instant the instant to get the previous transition after, not null, but null
893
* may be ignored if the rules have a single offset for all instants
894
* @return the previous transition before the specified instant, null if this is before the first transition
895
*/
896
public ZoneOffsetTransition previousTransition(Instant instant) {
897
if (savingsInstantTransitions.length == 0) {
898
return null;
899
}
900
long epochSec = instant.getEpochSecond();
901
if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) {
902
epochSec += 1; // allow rest of method to only use seconds
903
}
904
905
// check if using last rules
906
long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1];
907
if (lastRules.length > 0 && epochSec > lastHistoric) {
908
// search year the instant is in
909
ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1];
910
int year = findYear(epochSec, lastHistoricOffset);
911
ZoneOffsetTransition[] transArray = findTransitionArray(year);
912
for (int i = transArray.length - 1; i >= 0; i--) {
913
if (epochSec > transArray[i].toEpochSecond()) {
914
return transArray[i];
915
}
916
}
917
// use last from preceding year
918
int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset);
919
if (--year > lastHistoricYear) {
920
transArray = findTransitionArray(year);
921
return transArray[transArray.length - 1];
922
}
923
// drop through
924
}
925
926
// using historic rules
927
int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
928
if (index < 0) {
929
index = -index - 1;
930
}
931
if (index <= 0) {
932
return null;
933
}
934
return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]);
935
}
936
937
private int findYear(long epochSecond, ZoneOffset offset) {
938
long localSecond = epochSecond + offset.getTotalSeconds();
939
long zeroDay = Math.floorDiv(localSecond, 86400) + DAYS_0000_TO_1970;
940
941
// find the march-based year
942
zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle
943
long adjust = 0;
944
if (zeroDay < 0) {
945
// adjust negative years to positive for calculation
946
long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
947
adjust = adjustCycles * 400;
948
zeroDay += -adjustCycles * DAYS_PER_CYCLE;
949
}
950
long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
951
long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
952
if (doyEst < 0) {
953
// fix estimate
954
yearEst--;
955
doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
956
}
957
yearEst += adjust; // reset any negative year
958
int marchDoy0 = (int) doyEst;
959
960
// convert march-based values back to january-based
961
int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
962
yearEst += marchMonth0 / 10;
963
964
// Cap to the max value
965
return (int)Math.min(yearEst, Year.MAX_VALUE);
966
}
967
968
/**
969
* Gets the complete list of fully defined transitions.
970
* <p>
971
* The complete set of transitions for this rules instance is defined by this method
972
* and {@link #getTransitionRules()}. This method returns those transitions that have
973
* been fully defined. These are typically historical, but may be in the future.
974
* <p>
975
* The list will be empty for fixed offset rules and for any time-zone where there has
976
* only ever been a single offset. The list will also be empty if the transition rules are unknown.
977
*
978
* @return an immutable list of fully defined transitions, not null
979
*/
980
public List<ZoneOffsetTransition> getTransitions() {
981
List<ZoneOffsetTransition> list = new ArrayList<>();
982
for (int i = 0; i < savingsInstantTransitions.length; i++) {
983
list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1]));
984
}
985
return Collections.unmodifiableList(list);
986
}
987
988
/**
989
* Gets the list of transition rules for years beyond those defined in the transition list.
990
* <p>
991
* The complete set of transitions for this rules instance is defined by this method
992
* and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule}
993
* that define an algorithm for when transitions will occur.
994
* <p>
995
* For any given {@code ZoneRules}, this list contains the transition rules for years
996
* beyond those years that have been fully defined. These rules typically refer to future
997
* daylight saving time rule changes.
998
* <p>
999
* If the zone defines daylight savings into the future, then the list will normally
1000
* be of size two and hold information about entering and exiting daylight savings.
1001
* If the zone does not have daylight savings, or information about future changes
1002
* is uncertain, then the list will be empty.
1003
* <p>
1004
* The list will be empty for fixed offset rules and for any time-zone where there is no
1005
* daylight saving time. The list will also be empty if the transition rules are unknown.
1006
*
1007
* @return an immutable list of transition rules, not null
1008
*/
1009
public List<ZoneOffsetTransitionRule> getTransitionRules() {
1010
return List.of(lastRules);
1011
}
1012
1013
/**
1014
* Checks if this set of rules equals another.
1015
* <p>
1016
* Two rule sets are equal if they will always result in the same output
1017
* for any given input instant or local date-time.
1018
* Rules from two different groups may return false even if they are in fact the same.
1019
* <p>
1020
* This definition should result in implementations comparing their entire state.
1021
*
1022
* @param otherRules the other rules, null returns false
1023
* @return true if this rules is the same as that specified
1024
*/
1025
@Override
1026
public boolean equals(Object otherRules) {
1027
if (this == otherRules) {
1028
return true;
1029
}
1030
return (otherRules instanceof ZoneRules other)
1031
&& Arrays.equals(standardTransitions, other.standardTransitions)
1032
&& Arrays.equals(standardOffsets, other.standardOffsets)
1033
&& Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions)
1034
&& Arrays.equals(wallOffsets, other.wallOffsets)
1035
&& Arrays.equals(lastRules, other.lastRules);
1036
}
1037
1038
/**
1039
* Returns a suitable hash code given the definition of {@code #equals}.
1040
*
1041
* @return the hash code
1042
*/
1043
@Override
1044
public int hashCode() {
1045
return Arrays.hashCode(standardTransitions) ^
1046
Arrays.hashCode(standardOffsets) ^
1047
Arrays.hashCode(savingsInstantTransitions) ^
1048
Arrays.hashCode(wallOffsets) ^
1049
Arrays.hashCode(lastRules);
1050
}
1051
1052
/**
1053
* Returns a string describing this object.
1054
*
1055
* @return a string for debugging, not null
1056
*/
1057
@Override
1058
public String toString() {
1059
return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]";
1060
}
1061
1062
}
1063
1064