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/ZoneOffsetTransition.java
41159 views
1
/*
2
* Copyright (c) 2012, 2018, 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.ZoneOffset;
74
import java.util.Arrays;
75
import java.util.Collections;
76
import java.util.List;
77
import java.util.Objects;
78
79
/**
80
* A transition between two offsets caused by a discontinuity in the local time-line.
81
* <p>
82
* A transition between two offsets is normally the result of a daylight savings cutover.
83
* The discontinuity is normally a gap in spring and an overlap in autumn.
84
* {@code ZoneOffsetTransition} models the transition between the two offsets.
85
* <p>
86
* Gaps occur where there are local date-times that simply do not exist.
87
* An example would be when the offset changes from {@code +03:00} to {@code +04:00}.
88
* This might be described as 'the clocks will move forward one hour tonight at 1am'.
89
* <p>
90
* Overlaps occur where there are local date-times that exist twice.
91
* An example would be when the offset changes from {@code +04:00} to {@code +03:00}.
92
* This might be described as 'the clocks will move back one hour tonight at 2am'.
93
*
94
* @implSpec
95
* This class is immutable and thread-safe.
96
*
97
* @since 1.8
98
*/
99
public final class ZoneOffsetTransition
100
implements Comparable<ZoneOffsetTransition>, Serializable {
101
102
/**
103
* Serialization version.
104
*/
105
private static final long serialVersionUID = -6946044323557704546L;
106
/**
107
* The transition epoch-second.
108
*/
109
private final long epochSecond;
110
/**
111
* The local transition date-time at the transition.
112
*/
113
private final LocalDateTime transition;
114
/**
115
* The offset before transition.
116
*/
117
private final ZoneOffset offsetBefore;
118
/**
119
* The offset after transition.
120
*/
121
private final ZoneOffset offsetAfter;
122
123
//-----------------------------------------------------------------------
124
/**
125
* Obtains an instance defining a transition between two offsets.
126
* <p>
127
* Applications should normally obtain an instance from {@link ZoneRules}.
128
* This factory is only intended for use when creating {@link ZoneRules}.
129
*
130
* @param transition the transition date-time at the transition, which never
131
* actually occurs, expressed local to the before offset, not null
132
* @param offsetBefore the offset before the transition, not null
133
* @param offsetAfter the offset at and after the transition, not null
134
* @return the transition, not null
135
* @throws IllegalArgumentException if {@code offsetBefore} and {@code offsetAfter}
136
* are equal, or {@code transition.getNano()} returns non-zero value
137
*/
138
public static ZoneOffsetTransition of(LocalDateTime transition, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {
139
Objects.requireNonNull(transition, "transition");
140
Objects.requireNonNull(offsetBefore, "offsetBefore");
141
Objects.requireNonNull(offsetAfter, "offsetAfter");
142
if (offsetBefore.equals(offsetAfter)) {
143
throw new IllegalArgumentException("Offsets must not be equal");
144
}
145
if (transition.getNano() != 0) {
146
throw new IllegalArgumentException("Nano-of-second must be zero");
147
}
148
return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter);
149
}
150
151
/**
152
* Creates an instance defining a transition between two offsets.
153
*
154
* @param transition the transition date-time with the offset before the transition, not null
155
* @param offsetBefore the offset before the transition, not null
156
* @param offsetAfter the offset at and after the transition, not null
157
*/
158
ZoneOffsetTransition(LocalDateTime transition, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {
159
assert transition.getNano() == 0;
160
this.epochSecond = transition.toEpochSecond(offsetBefore);
161
this.transition = transition;
162
this.offsetBefore = offsetBefore;
163
this.offsetAfter = offsetAfter;
164
}
165
166
/**
167
* Creates an instance from epoch-second and offsets.
168
*
169
* @param epochSecond the transition epoch-second
170
* @param offsetBefore the offset before the transition, not null
171
* @param offsetAfter the offset at and after the transition, not null
172
*/
173
ZoneOffsetTransition(long epochSecond, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {
174
this.epochSecond = epochSecond;
175
this.transition = LocalDateTime.ofEpochSecond(epochSecond, 0, offsetBefore);
176
this.offsetBefore = offsetBefore;
177
this.offsetAfter = offsetAfter;
178
}
179
180
//-----------------------------------------------------------------------
181
/**
182
* Defend against malicious streams.
183
*
184
* @param s the stream to read
185
* @throws InvalidObjectException always
186
*/
187
private void readObject(ObjectInputStream s) throws InvalidObjectException {
188
throw new InvalidObjectException("Deserialization via serialization delegate");
189
}
190
191
/**
192
* Writes the object using a
193
* <a href="{@docRoot}/serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
194
* @serialData
195
* Refer to the serialized form of
196
* <a href="{@docRoot}/serialized-form.html#java.time.zone.ZoneRules">ZoneRules.writeReplace</a>
197
* for the encoding of epoch seconds and offsets.
198
* <pre style="font-size:1.0em">{@code
199
*
200
* out.writeByte(2); // identifies a ZoneOffsetTransition
201
* out.writeEpochSec(toEpochSecond);
202
* out.writeOffset(offsetBefore);
203
* out.writeOffset(offsetAfter);
204
* }
205
* </pre>
206
* @return the replacing object, not null
207
*/
208
private Object writeReplace() {
209
return new Ser(Ser.ZOT, this);
210
}
211
212
/**
213
* Writes the state to the stream.
214
*
215
* @param out the output stream, not null
216
* @throws IOException if an error occurs
217
*/
218
void writeExternal(DataOutput out) throws IOException {
219
Ser.writeEpochSec(epochSecond, out);
220
Ser.writeOffset(offsetBefore, out);
221
Ser.writeOffset(offsetAfter, out);
222
}
223
224
/**
225
* Reads the state from the stream.
226
*
227
* @param in the input stream, not null
228
* @return the created object, not null
229
* @throws IOException if an error occurs
230
*/
231
static ZoneOffsetTransition readExternal(DataInput in) throws IOException {
232
long epochSecond = Ser.readEpochSec(in);
233
ZoneOffset before = Ser.readOffset(in);
234
ZoneOffset after = Ser.readOffset(in);
235
if (before.equals(after)) {
236
throw new IllegalArgumentException("Offsets must not be equal");
237
}
238
return new ZoneOffsetTransition(epochSecond, before, after);
239
}
240
241
//-----------------------------------------------------------------------
242
/**
243
* Gets the transition instant.
244
* <p>
245
* This is the instant of the discontinuity, which is defined as the first
246
* instant that the 'after' offset applies.
247
* <p>
248
* The methods {@link #getInstant()}, {@link #getDateTimeBefore()} and {@link #getDateTimeAfter()}
249
* all represent the same instant.
250
*
251
* @return the transition instant, not null
252
*/
253
public Instant getInstant() {
254
return Instant.ofEpochSecond(epochSecond);
255
}
256
257
/**
258
* Gets the transition instant as an epoch second.
259
*
260
* @return the transition epoch second
261
*/
262
public long toEpochSecond() {
263
return epochSecond;
264
}
265
266
//-------------------------------------------------------------------------
267
/**
268
* Gets the local transition date-time, as would be expressed with the 'before' offset.
269
* <p>
270
* This is the date-time where the discontinuity begins expressed with the 'before' offset.
271
* At this instant, the 'after' offset is actually used, therefore the combination of this
272
* date-time and the 'before' offset will never occur.
273
* <p>
274
* The combination of the 'before' date-time and offset represents the same instant
275
* as the 'after' date-time and offset.
276
*
277
* @return the transition date-time expressed with the before offset, not null
278
*/
279
public LocalDateTime getDateTimeBefore() {
280
return transition;
281
}
282
283
/**
284
* Gets the local transition date-time, as would be expressed with the 'after' offset.
285
* <p>
286
* This is the first date-time after the discontinuity, when the new offset applies.
287
* <p>
288
* The combination of the 'before' date-time and offset represents the same instant
289
* as the 'after' date-time and offset.
290
*
291
* @return the transition date-time expressed with the after offset, not null
292
*/
293
public LocalDateTime getDateTimeAfter() {
294
return transition.plusSeconds(getDurationSeconds());
295
}
296
297
/**
298
* Gets the offset before the transition.
299
* <p>
300
* This is the offset in use before the instant of the transition.
301
*
302
* @return the offset before the transition, not null
303
*/
304
public ZoneOffset getOffsetBefore() {
305
return offsetBefore;
306
}
307
308
/**
309
* Gets the offset after the transition.
310
* <p>
311
* This is the offset in use on and after the instant of the transition.
312
*
313
* @return the offset after the transition, not null
314
*/
315
public ZoneOffset getOffsetAfter() {
316
return offsetAfter;
317
}
318
319
/**
320
* Gets the duration of the transition.
321
* <p>
322
* In most cases, the transition duration is one hour, however this is not always the case.
323
* The duration will be positive for a gap and negative for an overlap.
324
* Time-zones are second-based, so the nanosecond part of the duration will be zero.
325
*
326
* @return the duration of the transition, positive for gaps, negative for overlaps
327
*/
328
public Duration getDuration() {
329
return Duration.ofSeconds(getDurationSeconds());
330
}
331
332
/**
333
* Gets the duration of the transition in seconds.
334
*
335
* @return the duration in seconds
336
*/
337
private int getDurationSeconds() {
338
return getOffsetAfter().getTotalSeconds() - getOffsetBefore().getTotalSeconds();
339
}
340
341
/**
342
* Does this transition represent a gap in the local time-line.
343
* <p>
344
* Gaps occur where there are local date-times that simply do not exist.
345
* An example would be when the offset changes from {@code +01:00} to {@code +02:00}.
346
* This might be described as 'the clocks will move forward one hour tonight at 1am'.
347
*
348
* @return true if this transition is a gap, false if it is an overlap
349
*/
350
public boolean isGap() {
351
return getOffsetAfter().getTotalSeconds() > getOffsetBefore().getTotalSeconds();
352
}
353
354
/**
355
* Does this transition represent an overlap in the local time-line.
356
* <p>
357
* Overlaps occur where there are local date-times that exist twice.
358
* An example would be when the offset changes from {@code +02:00} to {@code +01:00}.
359
* This might be described as 'the clocks will move back one hour tonight at 2am'.
360
*
361
* @return true if this transition is an overlap, false if it is a gap
362
*/
363
public boolean isOverlap() {
364
return getOffsetAfter().getTotalSeconds() < getOffsetBefore().getTotalSeconds();
365
}
366
367
/**
368
* Checks if the specified offset is valid during this transition.
369
* <p>
370
* This checks to see if the given offset will be valid at some point in the transition.
371
* A gap will always return false.
372
* An overlap will return true if the offset is either the before or after offset.
373
*
374
* @param offset the offset to check, null returns false
375
* @return true if the offset is valid during the transition
376
*/
377
public boolean isValidOffset(ZoneOffset offset) {
378
return isGap() ? false : (getOffsetBefore().equals(offset) || getOffsetAfter().equals(offset));
379
}
380
381
/**
382
* Gets the valid offsets during this transition.
383
* <p>
384
* A gap will return an empty list, while an overlap will return both offsets.
385
*
386
* @return the list of valid offsets
387
*/
388
List<ZoneOffset> getValidOffsets() {
389
if (isGap()) {
390
return List.of();
391
}
392
return List.of(getOffsetBefore(), getOffsetAfter());
393
}
394
395
//-----------------------------------------------------------------------
396
/**
397
* Compares this transition to another based on the transition instant.
398
* <p>
399
* This compares the instants of each transition.
400
* The offsets are ignored, making this order inconsistent with equals.
401
*
402
* @param transition the transition to compare to, not null
403
* @return the comparator value, negative if less, positive if greater
404
*/
405
@Override
406
public int compareTo(ZoneOffsetTransition transition) {
407
return Long.compare(epochSecond, transition.epochSecond);
408
}
409
410
//-----------------------------------------------------------------------
411
/**
412
* Checks if this object equals another.
413
* <p>
414
* The entire state of the object is compared.
415
*
416
* @param other the other object to compare to, null returns false
417
* @return true if equal
418
*/
419
@Override
420
public boolean equals(Object other) {
421
if (other == this) {
422
return true;
423
}
424
return (other instanceof ZoneOffsetTransition d)
425
&& epochSecond == d.epochSecond
426
&& offsetBefore.equals(d.offsetBefore)
427
&& offsetAfter.equals(d.offsetAfter);
428
}
429
430
/**
431
* Returns a suitable hash code.
432
*
433
* @return the hash code
434
*/
435
@Override
436
public int hashCode() {
437
return transition.hashCode() ^ offsetBefore.hashCode() ^ Integer.rotateLeft(offsetAfter.hashCode(), 16);
438
}
439
440
//-----------------------------------------------------------------------
441
/**
442
* Returns a string describing this object.
443
*
444
* @return a string for debugging, not null
445
*/
446
@Override
447
public String toString() {
448
StringBuilder buf = new StringBuilder();
449
buf.append("Transition[")
450
.append(isGap() ? "Gap" : "Overlap")
451
.append(" at ")
452
.append(transition)
453
.append(offsetBefore)
454
.append(" to ")
455
.append(offsetAfter)
456
.append(']');
457
return buf.toString();
458
}
459
460
}
461
462