15289
|
1 |
/*
|
|
2 |
* Copyright (c) 2012, 2013, 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.Serializable;
|
|
68 |
import java.time.Duration;
|
|
69 |
import java.time.Instant;
|
|
70 |
import java.time.LocalDate;
|
|
71 |
import java.time.LocalDateTime;
|
|
72 |
import java.time.ZoneId;
|
|
73 |
import java.time.ZoneOffset;
|
15658
|
74 |
import java.time.Year;
|
15289
|
75 |
import java.util.ArrayList;
|
|
76 |
import java.util.Arrays;
|
|
77 |
import java.util.Collections;
|
|
78 |
import java.util.List;
|
|
79 |
import java.util.Objects;
|
|
80 |
import java.util.concurrent.ConcurrentHashMap;
|
|
81 |
import java.util.concurrent.ConcurrentMap;
|
|
82 |
|
|
83 |
/**
|
|
84 |
* The rules defining how the zone offset varies for a single time-zone.
|
|
85 |
* <p>
|
|
86 |
* The rules model all the historic and future transitions for a time-zone.
|
|
87 |
* {@link ZoneOffsetTransition} is used for known transitions, typically historic.
|
|
88 |
* {@link ZoneOffsetTransitionRule} is used for future transitions that are based
|
|
89 |
* on the result of an algorithm.
|
|
90 |
* <p>
|
|
91 |
* The rules are loaded via {@link ZoneRulesProvider} using a {@link ZoneId}.
|
|
92 |
* The same rules may be shared internally between multiple zone IDs.
|
|
93 |
* <p>
|
|
94 |
* Serializing an instance of {@code ZoneRules} will store the entire set of rules.
|
|
95 |
* It does not store the zone ID as it is not part of the state of this object.
|
|
96 |
* <p>
|
|
97 |
* A rule implementation may or may not store full information about historic
|
|
98 |
* and future transitions, and the information stored is only as accurate as
|
|
99 |
* that supplied to the implementation by the rules provider.
|
|
100 |
* Applications should treat the data provided as representing the best information
|
|
101 |
* available to the implementation of this rule.
|
|
102 |
*
|
17474
|
103 |
* @implSpec
|
15289
|
104 |
* This class is immutable and thread-safe.
|
|
105 |
*
|
|
106 |
* @since 1.8
|
|
107 |
*/
|
|
108 |
public final class ZoneRules implements Serializable {
|
|
109 |
|
|
110 |
/**
|
|
111 |
* Serialization version.
|
|
112 |
*/
|
|
113 |
private static final long serialVersionUID = 3044319355680032515L;
|
|
114 |
/**
|
|
115 |
* The last year to have its transitions cached.
|
|
116 |
*/
|
|
117 |
private static final int LAST_CACHED_YEAR = 2100;
|
|
118 |
|
|
119 |
/**
|
|
120 |
* The transitions between standard offsets (epoch seconds), sorted.
|
|
121 |
*/
|
|
122 |
private final long[] standardTransitions;
|
|
123 |
/**
|
|
124 |
* The standard offsets.
|
|
125 |
*/
|
|
126 |
private final ZoneOffset[] standardOffsets;
|
|
127 |
/**
|
|
128 |
* The transitions between instants (epoch seconds), sorted.
|
|
129 |
*/
|
|
130 |
private final long[] savingsInstantTransitions;
|
|
131 |
/**
|
|
132 |
* The transitions between local date-times, sorted.
|
|
133 |
* This is a paired array, where the first entry is the start of the transition
|
|
134 |
* and the second entry is the end of the transition.
|
|
135 |
*/
|
|
136 |
private final LocalDateTime[] savingsLocalTransitions;
|
|
137 |
/**
|
|
138 |
* The wall offsets.
|
|
139 |
*/
|
|
140 |
private final ZoneOffset[] wallOffsets;
|
|
141 |
/**
|
|
142 |
* The last rule.
|
|
143 |
*/
|
|
144 |
private final ZoneOffsetTransitionRule[] lastRules;
|
|
145 |
/**
|
|
146 |
* The map of recent transitions.
|
|
147 |
*/
|
|
148 |
private final ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache =
|
|
149 |
new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>();
|
|
150 |
/**
|
|
151 |
* The zero-length long array.
|
|
152 |
*/
|
|
153 |
private static final long[] EMPTY_LONG_ARRAY = new long[0];
|
|
154 |
/**
|
|
155 |
* The zero-length lastrules array.
|
|
156 |
*/
|
|
157 |
private static final ZoneOffsetTransitionRule[] EMPTY_LASTRULES =
|
|
158 |
new ZoneOffsetTransitionRule[0];
|
|
159 |
/**
|
|
160 |
* The zero-length ldt array.
|
|
161 |
*/
|
|
162 |
private static final LocalDateTime[] EMPTY_LDT_ARRAY = new LocalDateTime[0];
|
|
163 |
|
|
164 |
/**
|
|
165 |
* Obtains an instance of a ZoneRules.
|
|
166 |
*
|
|
167 |
* @param baseStandardOffset the standard offset to use before legal rules were set, not null
|
|
168 |
* @param baseWallOffset the wall offset to use before legal rules were set, not null
|
|
169 |
* @param standardOffsetTransitionList the list of changes to the standard offset, not null
|
|
170 |
* @param transitionList the list of transitions, not null
|
|
171 |
* @param lastRules the recurring last rules, size 16 or less, not null
|
|
172 |
* @return the zone rules, not null
|
|
173 |
*/
|
|
174 |
public static ZoneRules of(ZoneOffset baseStandardOffset,
|
|
175 |
ZoneOffset baseWallOffset,
|
|
176 |
List<ZoneOffsetTransition> standardOffsetTransitionList,
|
|
177 |
List<ZoneOffsetTransition> transitionList,
|
|
178 |
List<ZoneOffsetTransitionRule> lastRules) {
|
|
179 |
Objects.requireNonNull(baseStandardOffset, "baseStandardOffset");
|
|
180 |
Objects.requireNonNull(baseWallOffset, "baseWallOffset");
|
|
181 |
Objects.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList");
|
|
182 |
Objects.requireNonNull(transitionList, "transitionList");
|
|
183 |
Objects.requireNonNull(lastRules, "lastRules");
|
|
184 |
return new ZoneRules(baseStandardOffset, baseWallOffset,
|
|
185 |
standardOffsetTransitionList, transitionList, lastRules);
|
|
186 |
}
|
|
187 |
|
|
188 |
/**
|
|
189 |
* Obtains an instance of ZoneRules that has fixed zone rules.
|
|
190 |
*
|
|
191 |
* @param offset the offset this fixed zone rules is based on, not null
|
|
192 |
* @return the zone rules, not null
|
|
193 |
* @see #isFixedOffset()
|
|
194 |
*/
|
|
195 |
public static ZoneRules of(ZoneOffset offset) {
|
|
196 |
Objects.requireNonNull(offset, "offset");
|
|
197 |
return new ZoneRules(offset);
|
|
198 |
}
|
|
199 |
|
|
200 |
/**
|
|
201 |
* Creates an instance.
|
|
202 |
*
|
|
203 |
* @param baseStandardOffset the standard offset to use before legal rules were set, not null
|
|
204 |
* @param baseWallOffset the wall offset to use before legal rules were set, not null
|
|
205 |
* @param standardOffsetTransitionList the list of changes to the standard offset, not null
|
|
206 |
* @param transitionList the list of transitions, not null
|
|
207 |
* @param lastRules the recurring last rules, size 16 or less, not null
|
|
208 |
*/
|
|
209 |
ZoneRules(ZoneOffset baseStandardOffset,
|
|
210 |
ZoneOffset baseWallOffset,
|
|
211 |
List<ZoneOffsetTransition> standardOffsetTransitionList,
|
|
212 |
List<ZoneOffsetTransition> transitionList,
|
|
213 |
List<ZoneOffsetTransitionRule> lastRules) {
|
|
214 |
super();
|
|
215 |
|
|
216 |
// convert standard transitions
|
|
217 |
|
|
218 |
this.standardTransitions = new long[standardOffsetTransitionList.size()];
|
|
219 |
|
|
220 |
this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1];
|
|
221 |
this.standardOffsets[0] = baseStandardOffset;
|
|
222 |
for (int i = 0; i < standardOffsetTransitionList.size(); i++) {
|
|
223 |
this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond();
|
|
224 |
this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter();
|
|
225 |
}
|
|
226 |
|
|
227 |
// convert savings transitions to locals
|
|
228 |
List<LocalDateTime> localTransitionList = new ArrayList<>();
|
|
229 |
List<ZoneOffset> localTransitionOffsetList = new ArrayList<>();
|
|
230 |
localTransitionOffsetList.add(baseWallOffset);
|
|
231 |
for (ZoneOffsetTransition trans : transitionList) {
|
|
232 |
if (trans.isGap()) {
|
|
233 |
localTransitionList.add(trans.getDateTimeBefore());
|
|
234 |
localTransitionList.add(trans.getDateTimeAfter());
|
|
235 |
} else {
|
|
236 |
localTransitionList.add(trans.getDateTimeAfter());
|
|
237 |
localTransitionList.add(trans.getDateTimeBefore());
|
|
238 |
}
|
|
239 |
localTransitionOffsetList.add(trans.getOffsetAfter());
|
|
240 |
}
|
|
241 |
this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
|
|
242 |
this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]);
|
|
243 |
|
|
244 |
// convert savings transitions to instants
|
|
245 |
this.savingsInstantTransitions = new long[transitionList.size()];
|
|
246 |
for (int i = 0; i < transitionList.size(); i++) {
|
|
247 |
this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond();
|
|
248 |
}
|
|
249 |
|
|
250 |
// last rules
|
|
251 |
if (lastRules.size() > 16) {
|
|
252 |
throw new IllegalArgumentException("Too many transition rules");
|
|
253 |
}
|
|
254 |
this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]);
|
|
255 |
}
|
|
256 |
|
|
257 |
/**
|
|
258 |
* Constructor.
|
|
259 |
*
|
|
260 |
* @param standardTransitions the standard transitions, not null
|
|
261 |
* @param standardOffsets the standard offsets, not null
|
|
262 |
* @param savingsInstantTransitions the standard transitions, not null
|
|
263 |
* @param wallOffsets the wall offsets, not null
|
|
264 |
* @param lastRules the recurring last rules, size 15 or less, not null
|
|
265 |
*/
|
|
266 |
private ZoneRules(long[] standardTransitions,
|
|
267 |
ZoneOffset[] standardOffsets,
|
|
268 |
long[] savingsInstantTransitions,
|
|
269 |
ZoneOffset[] wallOffsets,
|
|
270 |
ZoneOffsetTransitionRule[] lastRules) {
|
|
271 |
super();
|
|
272 |
|
|
273 |
this.standardTransitions = standardTransitions;
|
|
274 |
this.standardOffsets = standardOffsets;
|
|
275 |
this.savingsInstantTransitions = savingsInstantTransitions;
|
|
276 |
this.wallOffsets = wallOffsets;
|
|
277 |
this.lastRules = lastRules;
|
|
278 |
|
|
279 |
if (savingsInstantTransitions.length == 0) {
|
|
280 |
this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
|
|
281 |
} else {
|
|
282 |
// convert savings transitions to locals
|
|
283 |
List<LocalDateTime> localTransitionList = new ArrayList<>();
|
|
284 |
for (int i = 0; i < savingsInstantTransitions.length; i++) {
|
|
285 |
ZoneOffset before = wallOffsets[i];
|
|
286 |
ZoneOffset after = wallOffsets[i + 1];
|
|
287 |
ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after);
|
|
288 |
if (trans.isGap()) {
|
|
289 |
localTransitionList.add(trans.getDateTimeBefore());
|
|
290 |
localTransitionList.add(trans.getDateTimeAfter());
|
|
291 |
} else {
|
|
292 |
localTransitionList.add(trans.getDateTimeAfter());
|
|
293 |
localTransitionList.add(trans.getDateTimeBefore());
|
|
294 |
}
|
|
295 |
}
|
|
296 |
this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
|
|
297 |
}
|
|
298 |
}
|
|
299 |
|
|
300 |
/**
|
|
301 |
* Creates an instance of ZoneRules that has fixed zone rules.
|
|
302 |
*
|
|
303 |
* @param offset the offset this fixed zone rules is based on, not null
|
|
304 |
* @return the zone rules, not null
|
|
305 |
* @see #isFixedOffset()
|
|
306 |
*/
|
|
307 |
private ZoneRules(ZoneOffset offset) {
|
|
308 |
this.standardOffsets = new ZoneOffset[1];
|
|
309 |
this.standardOffsets[0] = offset;
|
|
310 |
this.standardTransitions = EMPTY_LONG_ARRAY;
|
|
311 |
this.savingsInstantTransitions = EMPTY_LONG_ARRAY;
|
|
312 |
this.savingsLocalTransitions = EMPTY_LDT_ARRAY;
|
|
313 |
this.wallOffsets = standardOffsets;
|
|
314 |
this.lastRules = EMPTY_LASTRULES;
|
|
315 |
}
|
|
316 |
|
|
317 |
/**
|
|
318 |
* Uses a serialization delegate.
|
|
319 |
*
|
|
320 |
* @return the replacing object, not null
|
|
321 |
*/
|
|
322 |
private Object writeReplace() {
|
|
323 |
return new Ser(Ser.ZRULES, this);
|
|
324 |
}
|
|
325 |
|
|
326 |
/**
|
|
327 |
* Writes the state to the stream.
|
|
328 |
*
|
|
329 |
* @param out the output stream, not null
|
|
330 |
* @throws IOException if an error occurs
|
|
331 |
*/
|
|
332 |
void writeExternal(DataOutput out) throws IOException {
|
|
333 |
out.writeInt(standardTransitions.length);
|
|
334 |
for (long trans : standardTransitions) {
|
|
335 |
Ser.writeEpochSec(trans, out);
|
|
336 |
}
|
|
337 |
for (ZoneOffset offset : standardOffsets) {
|
|
338 |
Ser.writeOffset(offset, out);
|
|
339 |
}
|
|
340 |
out.writeInt(savingsInstantTransitions.length);
|
|
341 |
for (long trans : savingsInstantTransitions) {
|
|
342 |
Ser.writeEpochSec(trans, out);
|
|
343 |
}
|
|
344 |
for (ZoneOffset offset : wallOffsets) {
|
|
345 |
Ser.writeOffset(offset, out);
|
|
346 |
}
|
|
347 |
out.writeByte(lastRules.length);
|
|
348 |
for (ZoneOffsetTransitionRule rule : lastRules) {
|
|
349 |
rule.writeExternal(out);
|
|
350 |
}
|
|
351 |
}
|
|
352 |
|
|
353 |
/**
|
|
354 |
* Reads the state from the stream.
|
|
355 |
*
|
|
356 |
* @param in the input stream, not null
|
|
357 |
* @return the created object, not null
|
|
358 |
* @throws IOException if an error occurs
|
|
359 |
*/
|
|
360 |
static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException {
|
|
361 |
int stdSize = in.readInt();
|
|
362 |
long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY
|
|
363 |
: new long[stdSize];
|
|
364 |
for (int i = 0; i < stdSize; i++) {
|
|
365 |
stdTrans[i] = Ser.readEpochSec(in);
|
|
366 |
}
|
|
367 |
ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1];
|
|
368 |
for (int i = 0; i < stdOffsets.length; i++) {
|
|
369 |
stdOffsets[i] = Ser.readOffset(in);
|
|
370 |
}
|
|
371 |
int savSize = in.readInt();
|
|
372 |
long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY
|
|
373 |
: new long[savSize];
|
|
374 |
for (int i = 0; i < savSize; i++) {
|
|
375 |
savTrans[i] = Ser.readEpochSec(in);
|
|
376 |
}
|
|
377 |
ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1];
|
|
378 |
for (int i = 0; i < savOffsets.length; i++) {
|
|
379 |
savOffsets[i] = Ser.readOffset(in);
|
|
380 |
}
|
|
381 |
int ruleSize = in.readByte();
|
|
382 |
ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ?
|
|
383 |
EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize];
|
|
384 |
for (int i = 0; i < ruleSize; i++) {
|
|
385 |
rules[i] = ZoneOffsetTransitionRule.readExternal(in);
|
|
386 |
}
|
|
387 |
return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules);
|
|
388 |
}
|
|
389 |
|
|
390 |
/**
|
|
391 |
* Checks of the zone rules are fixed, such that the offset never varies.
|
|
392 |
*
|
|
393 |
* @return true if the time-zone is fixed and the offset never changes
|
|
394 |
*/
|
|
395 |
public boolean isFixedOffset() {
|
|
396 |
return savingsInstantTransitions.length == 0;
|
|
397 |
}
|
|
398 |
|
|
399 |
/**
|
|
400 |
* Gets the offset applicable at the specified instant in these rules.
|
|
401 |
* <p>
|
|
402 |
* The mapping from an instant to an offset is simple, there is only
|
|
403 |
* one valid offset for each instant.
|
|
404 |
* This method returns that offset.
|
|
405 |
*
|
|
406 |
* @param instant the instant to find the offset for, not null, but null
|
|
407 |
* may be ignored if the rules have a single offset for all instants
|
|
408 |
* @return the offset, not null
|
|
409 |
*/
|
|
410 |
public ZoneOffset getOffset(Instant instant) {
|
|
411 |
if (savingsInstantTransitions.length == 0) {
|
|
412 |
return standardOffsets[0];
|
|
413 |
}
|
|
414 |
long epochSec = instant.getEpochSecond();
|
|
415 |
// check if using last rules
|
|
416 |
if (lastRules.length > 0 &&
|
|
417 |
epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
|
|
418 |
int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
|
|
419 |
ZoneOffsetTransition[] transArray = findTransitionArray(year);
|
|
420 |
ZoneOffsetTransition trans = null;
|
|
421 |
for (int i = 0; i < transArray.length; i++) {
|
|
422 |
trans = transArray[i];
|
|
423 |
if (epochSec < trans.toEpochSecond()) {
|
|
424 |
return trans.getOffsetBefore();
|
|
425 |
}
|
|
426 |
}
|
|
427 |
return trans.getOffsetAfter();
|
|
428 |
}
|
|
429 |
|
|
430 |
// using historic rules
|
|
431 |
int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
|
|
432 |
if (index < 0) {
|
|
433 |
// switch negative insert position to start of matched range
|
|
434 |
index = -index - 2;
|
|
435 |
}
|
|
436 |
return wallOffsets[index + 1];
|
|
437 |
}
|
|
438 |
|
|
439 |
/**
|
|
440 |
* Gets a suitable offset for the specified local date-time in these rules.
|
|
441 |
* <p>
|
|
442 |
* The mapping from a local date-time to an offset is not straightforward.
|
|
443 |
* There are three cases:
|
|
444 |
* <p><ul>
|
|
445 |
* <li>Normal, with one valid offset. For the vast majority of the year, the normal
|
|
446 |
* case applies, where there is a single valid offset for the local date-time.</li>
|
|
447 |
* <li>Gap, with zero valid offsets. This is when clocks jump forward typically
|
|
448 |
* due to the spring daylight savings change from "winter" to "summer".
|
|
449 |
* In a gap there are local date-time values with no valid offset.</li>
|
|
450 |
* <li>Overlap, with two valid offsets. This is when clocks are set back typically
|
|
451 |
* due to the autumn daylight savings change from "summer" to "winter".
|
|
452 |
* In an overlap there are local date-time values with two valid offsets.</li>
|
|
453 |
* </ul><p>
|
|
454 |
* Thus, for any given local date-time there can be zero, one or two valid offsets.
|
|
455 |
* This method returns the single offset in the Normal case, and in the Gap or Overlap
|
|
456 |
* case it returns the offset before the transition.
|
|
457 |
* <p>
|
|
458 |
* Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather
|
|
459 |
* than the "correct" value, it should be treated with care. Applications that care
|
|
460 |
* about the correct offset should use a combination of this method,
|
|
461 |
* {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}.
|
|
462 |
*
|
|
463 |
* @param localDateTime the local date-time to query, not null, but null
|
|
464 |
* may be ignored if the rules have a single offset for all instants
|
|
465 |
* @return the best available offset for the local date-time, not null
|
|
466 |
*/
|
|
467 |
public ZoneOffset getOffset(LocalDateTime localDateTime) {
|
|
468 |
Object info = getOffsetInfo(localDateTime);
|
|
469 |
if (info instanceof ZoneOffsetTransition) {
|
|
470 |
return ((ZoneOffsetTransition) info).getOffsetBefore();
|
|
471 |
}
|
|
472 |
return (ZoneOffset) info;
|
|
473 |
}
|
|
474 |
|
|
475 |
/**
|
|
476 |
* Gets the offset applicable at the specified local date-time in these rules.
|
|
477 |
* <p>
|
|
478 |
* The mapping from a local date-time to an offset is not straightforward.
|
|
479 |
* There are three cases:
|
|
480 |
* <p><ul>
|
|
481 |
* <li>Normal, with one valid offset. For the vast majority of the year, the normal
|
|
482 |
* case applies, where there is a single valid offset for the local date-time.</li>
|
|
483 |
* <li>Gap, with zero valid offsets. This is when clocks jump forward typically
|
|
484 |
* due to the spring daylight savings change from "winter" to "summer".
|
|
485 |
* In a gap there are local date-time values with no valid offset.</li>
|
|
486 |
* <li>Overlap, with two valid offsets. This is when clocks are set back typically
|
|
487 |
* due to the autumn daylight savings change from "summer" to "winter".
|
|
488 |
* In an overlap there are local date-time values with two valid offsets.</li>
|
|
489 |
* </ul><p>
|
|
490 |
* Thus, for any given local date-time there can be zero, one or two valid offsets.
|
|
491 |
* This method returns that list of valid offsets, which is a list of size 0, 1 or 2.
|
|
492 |
* In the case where there are two offsets, the earlier offset is returned at index 0
|
|
493 |
* and the later offset at index 1.
|
|
494 |
* <p>
|
|
495 |
* There are various ways to handle the conversion from a {@code LocalDateTime}.
|
|
496 |
* One technique, using this method, would be:
|
|
497 |
* <pre>
|
|
498 |
* List<ZoneOffset> validOffsets = rules.getOffset(localDT);
|
|
499 |
* if (validOffsets.size() == 1) {
|
|
500 |
* // Normal case: only one valid offset
|
|
501 |
* zoneOffset = validOffsets.get(0);
|
|
502 |
* } else {
|
|
503 |
* // Gap or Overlap: determine what to do from transition (which will be non-null)
|
|
504 |
* ZoneOffsetTransition trans = rules.getTransition(localDT);
|
|
505 |
* }
|
|
506 |
* </pre>
|
|
507 |
* <p>
|
|
508 |
* In theory, it is possible for there to be more than two valid offsets.
|
|
509 |
* This would happen if clocks to be put back more than once in quick succession.
|
|
510 |
* This has never happened in the history of time-zones and thus has no special handling.
|
|
511 |
* However, if it were to happen, then the list would return more than 2 entries.
|
|
512 |
*
|
|
513 |
* @param localDateTime the local date-time to query for valid offsets, not null, but null
|
|
514 |
* may be ignored if the rules have a single offset for all instants
|
|
515 |
* @return the list of valid offsets, may be immutable, not null
|
|
516 |
*/
|
|
517 |
public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) {
|
|
518 |
// should probably be optimized
|
|
519 |
Object info = getOffsetInfo(localDateTime);
|
|
520 |
if (info instanceof ZoneOffsetTransition) {
|
|
521 |
return ((ZoneOffsetTransition) info).getValidOffsets();
|
|
522 |
}
|
|
523 |
return Collections.singletonList((ZoneOffset) info);
|
|
524 |
}
|
|
525 |
|
|
526 |
/**
|
|
527 |
* Gets the offset transition applicable at the specified local date-time in these rules.
|
|
528 |
* <p>
|
|
529 |
* The mapping from a local date-time to an offset is not straightforward.
|
|
530 |
* There are three cases:
|
|
531 |
* <p><ul>
|
|
532 |
* <li>Normal, with one valid offset. For the vast majority of the year, the normal
|
|
533 |
* case applies, where there is a single valid offset for the local date-time.</li>
|
|
534 |
* <li>Gap, with zero valid offsets. This is when clocks jump forward typically
|
|
535 |
* due to the spring daylight savings change from "winter" to "summer".
|
|
536 |
* In a gap there are local date-time values with no valid offset.</li>
|
|
537 |
* <li>Overlap, with two valid offsets. This is when clocks are set back typically
|
|
538 |
* due to the autumn daylight savings change from "summer" to "winter".
|
|
539 |
* In an overlap there are local date-time values with two valid offsets.</li>
|
|
540 |
* </ul><p>
|
|
541 |
* A transition is used to model the cases of a Gap or Overlap.
|
|
542 |
* The Normal case will return null.
|
|
543 |
* <p>
|
|
544 |
* There are various ways to handle the conversion from a {@code LocalDateTime}.
|
|
545 |
* One technique, using this method, would be:
|
|
546 |
* <pre>
|
|
547 |
* ZoneOffsetTransition trans = rules.getTransition(localDT);
|
|
548 |
* if (trans == null) {
|
|
549 |
* // Gap or Overlap: determine what to do from transition
|
|
550 |
* } else {
|
|
551 |
* // Normal case: only one valid offset
|
|
552 |
* zoneOffset = rule.getOffset(localDT);
|
|
553 |
* }
|
|
554 |
* </pre>
|
|
555 |
*
|
|
556 |
* @param localDateTime the local date-time to query for offset transition, not null, but null
|
|
557 |
* may be ignored if the rules have a single offset for all instants
|
|
558 |
* @return the offset transition, null if the local date-time is not in transition
|
|
559 |
*/
|
|
560 |
public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {
|
|
561 |
Object info = getOffsetInfo(localDateTime);
|
|
562 |
return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null);
|
|
563 |
}
|
|
564 |
|
|
565 |
private Object getOffsetInfo(LocalDateTime dt) {
|
|
566 |
if (savingsInstantTransitions.length == 0) {
|
|
567 |
return standardOffsets[0];
|
|
568 |
}
|
|
569 |
// check if using last rules
|
|
570 |
if (lastRules.length > 0 &&
|
|
571 |
dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) {
|
|
572 |
ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear());
|
|
573 |
Object info = null;
|
|
574 |
for (ZoneOffsetTransition trans : transArray) {
|
|
575 |
info = findOffsetInfo(dt, trans);
|
|
576 |
if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) {
|
|
577 |
return info;
|
|
578 |
}
|
|
579 |
}
|
|
580 |
return info;
|
|
581 |
}
|
|
582 |
|
|
583 |
// using historic rules
|
|
584 |
int index = Arrays.binarySearch(savingsLocalTransitions, dt);
|
|
585 |
if (index == -1) {
|
|
586 |
// before first transition
|
|
587 |
return wallOffsets[0];
|
|
588 |
}
|
|
589 |
if (index < 0) {
|
|
590 |
// switch negative insert position to start of matched range
|
|
591 |
index = -index - 2;
|
|
592 |
} else if (index < savingsLocalTransitions.length - 1 &&
|
|
593 |
savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) {
|
|
594 |
// handle overlap immediately following gap
|
|
595 |
index++;
|
|
596 |
}
|
|
597 |
if ((index & 1) == 0) {
|
|
598 |
// gap or overlap
|
|
599 |
LocalDateTime dtBefore = savingsLocalTransitions[index];
|
|
600 |
LocalDateTime dtAfter = savingsLocalTransitions[index + 1];
|
|
601 |
ZoneOffset offsetBefore = wallOffsets[index / 2];
|
|
602 |
ZoneOffset offsetAfter = wallOffsets[index / 2 + 1];
|
|
603 |
if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) {
|
|
604 |
// gap
|
|
605 |
return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter);
|
|
606 |
} else {
|
|
607 |
// overlap
|
|
608 |
return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter);
|
|
609 |
}
|
|
610 |
} else {
|
|
611 |
// normal (neither gap or overlap)
|
|
612 |
return wallOffsets[index / 2 + 1];
|
|
613 |
}
|
|
614 |
}
|
|
615 |
|
|
616 |
/**
|
|
617 |
* Finds the offset info for a local date-time and transition.
|
|
618 |
*
|
|
619 |
* @param dt the date-time, not null
|
|
620 |
* @param trans the transition, not null
|
|
621 |
* @return the offset info, not null
|
|
622 |
*/
|
|
623 |
private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) {
|
|
624 |
LocalDateTime localTransition = trans.getDateTimeBefore();
|
|
625 |
if (trans.isGap()) {
|
|
626 |
if (dt.isBefore(localTransition)) {
|
|
627 |
return trans.getOffsetBefore();
|
|
628 |
}
|
|
629 |
if (dt.isBefore(trans.getDateTimeAfter())) {
|
|
630 |
return trans;
|
|
631 |
} else {
|
|
632 |
return trans.getOffsetAfter();
|
|
633 |
}
|
|
634 |
} else {
|
|
635 |
if (dt.isBefore(localTransition) == false) {
|
|
636 |
return trans.getOffsetAfter();
|
|
637 |
}
|
|
638 |
if (dt.isBefore(trans.getDateTimeAfter())) {
|
|
639 |
return trans.getOffsetBefore();
|
|
640 |
} else {
|
|
641 |
return trans;
|
|
642 |
}
|
|
643 |
}
|
|
644 |
}
|
|
645 |
|
|
646 |
/**
|
|
647 |
* Finds the appropriate transition array for the given year.
|
|
648 |
*
|
|
649 |
* @param year the year, not null
|
|
650 |
* @return the transition array, not null
|
|
651 |
*/
|
|
652 |
private ZoneOffsetTransition[] findTransitionArray(int year) {
|
|
653 |
Integer yearObj = year; // should use Year class, but this saves a class load
|
|
654 |
ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj);
|
|
655 |
if (transArray != null) {
|
|
656 |
return transArray;
|
|
657 |
}
|
|
658 |
ZoneOffsetTransitionRule[] ruleArray = lastRules;
|
|
659 |
transArray = new ZoneOffsetTransition[ruleArray.length];
|
|
660 |
for (int i = 0; i < ruleArray.length; i++) {
|
|
661 |
transArray[i] = ruleArray[i].createTransition(year);
|
|
662 |
}
|
|
663 |
if (year < LAST_CACHED_YEAR) {
|
|
664 |
lastRulesCache.putIfAbsent(yearObj, transArray);
|
|
665 |
}
|
|
666 |
return transArray;
|
|
667 |
}
|
|
668 |
|
|
669 |
/**
|
|
670 |
* Gets the standard offset for the specified instant in this zone.
|
|
671 |
* <p>
|
|
672 |
* This provides access to historic information on how the standard offset
|
|
673 |
* has changed over time.
|
|
674 |
* The standard offset is the offset before any daylight saving time is applied.
|
|
675 |
* This is typically the offset applicable during winter.
|
|
676 |
*
|
|
677 |
* @param instant the instant to find the offset information for, not null, but null
|
|
678 |
* may be ignored if the rules have a single offset for all instants
|
|
679 |
* @return the standard offset, not null
|
|
680 |
*/
|
|
681 |
public ZoneOffset getStandardOffset(Instant instant) {
|
|
682 |
if (savingsInstantTransitions.length == 0) {
|
|
683 |
return standardOffsets[0];
|
|
684 |
}
|
|
685 |
long epochSec = instant.getEpochSecond();
|
|
686 |
int index = Arrays.binarySearch(standardTransitions, epochSec);
|
|
687 |
if (index < 0) {
|
|
688 |
// switch negative insert position to start of matched range
|
|
689 |
index = -index - 2;
|
|
690 |
}
|
|
691 |
return standardOffsets[index + 1];
|
|
692 |
}
|
|
693 |
|
|
694 |
/**
|
|
695 |
* Gets the amount of daylight savings in use for the specified instant in this zone.
|
|
696 |
* <p>
|
|
697 |
* This provides access to historic information on how the amount of daylight
|
|
698 |
* savings has changed over time.
|
|
699 |
* This is the difference between the standard offset and the actual offset.
|
|
700 |
* Typically the amount is zero during winter and one hour during summer.
|
|
701 |
* Time-zones are second-based, so the nanosecond part of the duration will be zero.
|
|
702 |
* <p>
|
|
703 |
* This default implementation calculates the duration from the
|
|
704 |
* {@link #getOffset(java.time.Instant) actual} and
|
|
705 |
* {@link #getStandardOffset(java.time.Instant) standard} offsets.
|
|
706 |
*
|
|
707 |
* @param instant the instant to find the daylight savings for, not null, but null
|
|
708 |
* may be ignored if the rules have a single offset for all instants
|
|
709 |
* @return the difference between the standard and actual offset, not null
|
|
710 |
*/
|
|
711 |
public Duration getDaylightSavings(Instant instant) {
|
|
712 |
if (savingsInstantTransitions.length == 0) {
|
|
713 |
return Duration.ZERO;
|
|
714 |
}
|
|
715 |
ZoneOffset standardOffset = getStandardOffset(instant);
|
|
716 |
ZoneOffset actualOffset = getOffset(instant);
|
|
717 |
return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds());
|
|
718 |
}
|
|
719 |
|
|
720 |
/**
|
|
721 |
* Checks if the specified instant is in daylight savings.
|
|
722 |
* <p>
|
|
723 |
* This checks if the standard offset and the actual offset are the same
|
|
724 |
* for the specified instant.
|
|
725 |
* If they are not, it is assumed that daylight savings is in operation.
|
|
726 |
* <p>
|
|
727 |
* This default implementation compares the {@link #getOffset(java.time.Instant) actual}
|
|
728 |
* and {@link #getStandardOffset(java.time.Instant) standard} offsets.
|
|
729 |
*
|
|
730 |
* @param instant the instant to find the offset information for, not null, but null
|
|
731 |
* may be ignored if the rules have a single offset for all instants
|
|
732 |
* @return the standard offset, not null
|
|
733 |
*/
|
|
734 |
public boolean isDaylightSavings(Instant instant) {
|
|
735 |
return (getStandardOffset(instant).equals(getOffset(instant)) == false);
|
|
736 |
}
|
|
737 |
|
|
738 |
/**
|
|
739 |
* Checks if the offset date-time is valid for these rules.
|
|
740 |
* <p>
|
|
741 |
* To be valid, the local date-time must not be in a gap and the offset
|
|
742 |
* must match one of the valid offsets.
|
|
743 |
* <p>
|
|
744 |
* This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)}
|
|
745 |
* contains the specified offset.
|
|
746 |
*
|
|
747 |
* @param localDateTime the date-time to check, not null, but null
|
|
748 |
* may be ignored if the rules have a single offset for all instants
|
|
749 |
* @param offset the offset to check, null returns false
|
|
750 |
* @return true if the offset date-time is valid for these rules
|
|
751 |
*/
|
|
752 |
public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) {
|
|
753 |
return getValidOffsets(localDateTime).contains(offset);
|
|
754 |
}
|
|
755 |
|
|
756 |
/**
|
|
757 |
* Gets the next transition after the specified instant.
|
|
758 |
* <p>
|
|
759 |
* This returns details of the next transition after the specified instant.
|
|
760 |
* For example, if the instant represents a point where "Summer" daylight savings time
|
|
761 |
* applies, then the method will return the transition to the next "Winter" time.
|
|
762 |
*
|
|
763 |
* @param instant the instant to get the next transition after, not null, but null
|
|
764 |
* may be ignored if the rules have a single offset for all instants
|
|
765 |
* @return the next transition after the specified instant, null if this is after the last transition
|
|
766 |
*/
|
|
767 |
public ZoneOffsetTransition nextTransition(Instant instant) {
|
|
768 |
if (savingsInstantTransitions.length == 0) {
|
|
769 |
return null;
|
|
770 |
}
|
|
771 |
long epochSec = instant.getEpochSecond();
|
|
772 |
// check if using last rules
|
|
773 |
if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
|
|
774 |
if (lastRules.length == 0) {
|
|
775 |
return null;
|
|
776 |
}
|
|
777 |
// search year the instant is in
|
|
778 |
int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
|
|
779 |
ZoneOffsetTransition[] transArray = findTransitionArray(year);
|
|
780 |
for (ZoneOffsetTransition trans : transArray) {
|
|
781 |
if (epochSec < trans.toEpochSecond()) {
|
|
782 |
return trans;
|
|
783 |
}
|
|
784 |
}
|
|
785 |
// use first from following year
|
|
786 |
if (year < Year.MAX_VALUE) {
|
|
787 |
transArray = findTransitionArray(year + 1);
|
|
788 |
return transArray[0];
|
|
789 |
}
|
|
790 |
return null;
|
|
791 |
}
|
|
792 |
|
|
793 |
// using historic rules
|
|
794 |
int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
|
|
795 |
if (index < 0) {
|
|
796 |
index = -index - 1; // switched value is the next transition
|
|
797 |
} else {
|
|
798 |
index += 1; // exact match, so need to add one to get the next
|
|
799 |
}
|
|
800 |
return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]);
|
|
801 |
}
|
|
802 |
|
|
803 |
/**
|
|
804 |
* Gets the previous transition before the specified instant.
|
|
805 |
* <p>
|
|
806 |
* This returns details of the previous transition after the specified instant.
|
|
807 |
* For example, if the instant represents a point where "summer" daylight saving time
|
|
808 |
* applies, then the method will return the transition from the previous "winter" time.
|
|
809 |
*
|
|
810 |
* @param instant the instant to get the previous transition after, not null, but null
|
|
811 |
* may be ignored if the rules have a single offset for all instants
|
|
812 |
* @return the previous transition after the specified instant, null if this is before the first transition
|
|
813 |
*/
|
|
814 |
public ZoneOffsetTransition previousTransition(Instant instant) {
|
|
815 |
if (savingsInstantTransitions.length == 0) {
|
|
816 |
return null;
|
|
817 |
}
|
|
818 |
long epochSec = instant.getEpochSecond();
|
|
819 |
if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) {
|
|
820 |
epochSec += 1; // allow rest of method to only use seconds
|
|
821 |
}
|
|
822 |
|
|
823 |
// check if using last rules
|
|
824 |
long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1];
|
|
825 |
if (lastRules.length > 0 && epochSec > lastHistoric) {
|
|
826 |
// search year the instant is in
|
|
827 |
ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1];
|
|
828 |
int year = findYear(epochSec, lastHistoricOffset);
|
|
829 |
ZoneOffsetTransition[] transArray = findTransitionArray(year);
|
|
830 |
for (int i = transArray.length - 1; i >= 0; i--) {
|
|
831 |
if (epochSec > transArray[i].toEpochSecond()) {
|
|
832 |
return transArray[i];
|
|
833 |
}
|
|
834 |
}
|
|
835 |
// use last from preceeding year
|
|
836 |
int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset);
|
|
837 |
if (--year > lastHistoricYear) {
|
|
838 |
transArray = findTransitionArray(year);
|
|
839 |
return transArray[transArray.length - 1];
|
|
840 |
}
|
|
841 |
// drop through
|
|
842 |
}
|
|
843 |
|
|
844 |
// using historic rules
|
|
845 |
int index = Arrays.binarySearch(savingsInstantTransitions, epochSec);
|
|
846 |
if (index < 0) {
|
|
847 |
index = -index - 1;
|
|
848 |
}
|
|
849 |
if (index <= 0) {
|
|
850 |
return null;
|
|
851 |
}
|
|
852 |
return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]);
|
|
853 |
}
|
|
854 |
|
|
855 |
private int findYear(long epochSecond, ZoneOffset offset) {
|
|
856 |
// inline for performance
|
|
857 |
long localSecond = epochSecond + offset.getTotalSeconds();
|
|
858 |
long localEpochDay = Math.floorDiv(localSecond, 86400);
|
|
859 |
return LocalDate.ofEpochDay(localEpochDay).getYear();
|
|
860 |
}
|
|
861 |
|
|
862 |
/**
|
|
863 |
* Gets the complete list of fully defined transitions.
|
|
864 |
* <p>
|
|
865 |
* The complete set of transitions for this rules instance is defined by this method
|
|
866 |
* and {@link #getTransitionRules()}. This method returns those transitions that have
|
|
867 |
* been fully defined. These are typically historical, but may be in the future.
|
|
868 |
* <p>
|
|
869 |
* The list will be empty for fixed offset rules and for any time-zone where there has
|
|
870 |
* only ever been a single offset. The list will also be empty if the transition rules are unknown.
|
|
871 |
*
|
|
872 |
* @return an immutable list of fully defined transitions, not null
|
|
873 |
*/
|
|
874 |
public List<ZoneOffsetTransition> getTransitions() {
|
|
875 |
List<ZoneOffsetTransition> list = new ArrayList<>();
|
|
876 |
for (int i = 0; i < savingsInstantTransitions.length; i++) {
|
|
877 |
list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1]));
|
|
878 |
}
|
|
879 |
return Collections.unmodifiableList(list);
|
|
880 |
}
|
|
881 |
|
|
882 |
/**
|
|
883 |
* Gets the list of transition rules for years beyond those defined in the transition list.
|
|
884 |
* <p>
|
|
885 |
* The complete set of transitions for this rules instance is defined by this method
|
|
886 |
* and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule}
|
|
887 |
* that define an algorithm for when transitions will occur.
|
|
888 |
* <p>
|
|
889 |
* For any given {@code ZoneRules}, this list contains the transition rules for years
|
|
890 |
* beyond those years that have been fully defined. These rules typically refer to future
|
|
891 |
* daylight saving time rule changes.
|
|
892 |
* <p>
|
|
893 |
* If the zone defines daylight savings into the future, then the list will normally
|
|
894 |
* be of size two and hold information about entering and exiting daylight savings.
|
|
895 |
* If the zone does not have daylight savings, or information about future changes
|
|
896 |
* is uncertain, then the list will be empty.
|
|
897 |
* <p>
|
|
898 |
* The list will be empty for fixed offset rules and for any time-zone where there is no
|
|
899 |
* daylight saving time. The list will also be empty if the transition rules are unknown.
|
|
900 |
*
|
|
901 |
* @return an immutable list of transition rules, not null
|
|
902 |
*/
|
|
903 |
public List<ZoneOffsetTransitionRule> getTransitionRules() {
|
|
904 |
return Collections.unmodifiableList(Arrays.asList(lastRules));
|
|
905 |
}
|
|
906 |
|
|
907 |
/**
|
|
908 |
* Checks if this set of rules equals another.
|
|
909 |
* <p>
|
|
910 |
* Two rule sets are equal if they will always result in the same output
|
|
911 |
* for any given input instant or local date-time.
|
|
912 |
* Rules from two different groups may return false even if they are in fact the same.
|
|
913 |
* <p>
|
|
914 |
* This definition should result in implementations comparing their entire state.
|
|
915 |
*
|
|
916 |
* @param otherRules the other rules, null returns false
|
|
917 |
* @return true if this rules is the same as that specified
|
|
918 |
*/
|
|
919 |
@Override
|
|
920 |
public boolean equals(Object otherRules) {
|
|
921 |
if (this == otherRules) {
|
|
922 |
return true;
|
|
923 |
}
|
|
924 |
if (otherRules instanceof ZoneRules) {
|
|
925 |
ZoneRules other = (ZoneRules) otherRules;
|
|
926 |
return Arrays.equals(standardTransitions, other.standardTransitions) &&
|
|
927 |
Arrays.equals(standardOffsets, other.standardOffsets) &&
|
|
928 |
Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) &&
|
|
929 |
Arrays.equals(wallOffsets, other.wallOffsets) &&
|
|
930 |
Arrays.equals(lastRules, other.lastRules);
|
|
931 |
}
|
|
932 |
return false;
|
|
933 |
}
|
|
934 |
|
|
935 |
/**
|
|
936 |
* Returns a suitable hash code given the definition of {@code #equals}.
|
|
937 |
*
|
|
938 |
* @return the hash code
|
|
939 |
*/
|
|
940 |
@Override
|
|
941 |
public int hashCode() {
|
|
942 |
return Arrays.hashCode(standardTransitions) ^
|
|
943 |
Arrays.hashCode(standardOffsets) ^
|
|
944 |
Arrays.hashCode(savingsInstantTransitions) ^
|
|
945 |
Arrays.hashCode(wallOffsets) ^
|
|
946 |
Arrays.hashCode(lastRules);
|
|
947 |
}
|
|
948 |
|
|
949 |
/**
|
|
950 |
* Returns a string describing this object.
|
|
951 |
*
|
|
952 |
* @return a string for debugging, not null
|
|
953 |
*/
|
|
954 |
@Override
|
|
955 |
public String toString() {
|
|
956 |
return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]";
|
|
957 |
}
|
|
958 |
|
|
959 |
}
|