wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/*
 * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
 * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
 * @license BSD-3-Clause (see LICENSE in the root directory of this source tree)
 */
 
import { requireNonNull } from '../assert';
import { IllegalArgumentException } from '../errors';
 
import { Duration } from '../Duration';
import { LocalDateTime } from '../LocalDateTime';
 
/**
 * A transition between two offsets caused by a discontinuity in the local time-line.
 *
 * A transition between two offsets is normally the result of a daylight savings cutover.
 * The discontinuity is normally a gap in spring and an overlap in autumn.
 * {@link ZoneOffsetTransition} models the transition between the two offsets.
 *
 * Gaps occur where there are local date-times that simply do not not exist.
 * An example would be when the offset changes from `+03:00` to `+04:00`.
 * This might be described as 'the clocks will move forward one hour tonight at 1am'.
 *
 * Overlaps occur where there are local date-times that exist twice.
 * An example would be when the offset changes from `+04:00` to `+03:00`.
 * This might be described as 'the clocks will move back one hour tonight at 2am'.
 *
 */
export class ZoneOffsetTransition {
 
    //-----------------------------------------------------------------------
    /**
     * Obtains an instance defining a transition between two offsets.
     *
     * Applications should normally obtain an instance from {@link ZoneRules}.
     * This factory is only intended for use when creating {@link ZoneRules}.
     *
     * @param {LocalDateTime} transition - the transition date-time at the transition, which never
     *  actually occurs, expressed local to the before offset, not null
     * @param {ZoneOffset} offsetBefore - the offset before the transition, not null
     * @param {ZoneOffset} offsetAfter - the offset at and after the transition, not null
     * @return {ZoneOffsetTransition} the transition, not null
     * @throws IllegalArgumentException if {@link offsetBefore} and {@link offsetAfter}
     *         are equal, or {@link transition.getNano} returns non-zero value
     */
    static of(transition, offsetBefore, offsetAfter) {
        return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter);
    }
 
    /**
     * Creates an instance defining a transition between two offsets.
     * Creates an instance from epoch-second if transition is not a LocalDateTimeInstance
     *
     * @param {(LocalDateTime \ number)} transition - the transition date-time with the offset before the transition, not null
     * @param {ZoneOffset} offsetBefore - the offset before the transition, not null
     * @param {ZoneOffset} offsetAfter - the offset at and after the transition, not null
     * @private
     */
    constructor(transition, offsetBefore, offsetAfter) {
        requireNonNull(transition, 'transition');
        requireNonNull(offsetBefore, 'offsetBefore');
        requireNonNull(offsetAfter, 'offsetAfter');
        if (offsetBefore.equals(offsetAfter)) {
            throw new IllegalArgumentException('Offsets must not be equal');
        }
        if (transition.nano() !== 0) {
            throw new IllegalArgumentException('Nano-of-second must be zero');
        }
        if(transition instanceof LocalDateTime) {
            this._transition = transition;
        } else {
            this._transition = LocalDateTime.ofEpochSecond(transition, 0, offsetBefore);
        }
        this._offsetBefore = offsetBefore;
        this._offsetAfter = offsetAfter;
    }
 
    //-----------------------------------------------------------------------
    /**
     * Gets the transition instant.
     *
     * This is the instant of the discontinuity, which is defined as the first
     * instant that the 'after' offset applies.
     *
     * The methods {@link getInstant}, {@link getDateTimeBefore} and {@link getDateTimeAfter}
     * all represent the same instant.
     *
     * @return {Instant} the transition instant, not null
     */
    instant() {
        return this._transition.toInstant(this._offsetBefore);
    }
 
    /**
     * Gets the transition instant as an epoch second.
     *
     * @return {number} the transition epoch second
     */
    toEpochSecond() {
        return this._transition.toEpochSecond(this._offsetBefore);
    }
 
    //-------------------------------------------------------------------------
    /**
     * Gets the local transition date-time, as would be expressed with the 'before' offset.
     *
     * This is the date-time where the discontinuity begins expressed with the 'before' offset.
     * At this instant, the 'after' offset is actually used, therefore the combination of this
     * date-time and the 'before' offset will never occur.
     *
     * The combination of the 'before' date-time and offset represents the same instant
     * as the 'after' date-time and offset.
     *
     * @return {LocalDateTime} the transition date-time expressed with the before offset, not null
     */
    dateTimeBefore(){
        return this._transition;
    }
 
    /**
     * Gets the local transition date-time, as would be expressed with the 'after' offset.
     *
     * This is the first date-time after the discontinuity, when the new offset applies.
     *
     * The combination of the 'before' date-time and offset represents the same instant
     * as the 'after' date-time and offset.
     *
     * @return {LocalDateTime} the transition date-time expressed with the after offset, not null
     */
    dateTimeAfter() {
        return this._transition.plusSeconds(this.durationSeconds());
    }
 
    /**
     * Gets the offset before the transition.
     *
     * This is the offset in use before the instant of the transition.
     *
     * @return {ZoneOffset} the offset before the transition, not null
     */
    offsetBefore() {
        return this._offsetBefore;
    }
 
    /**
     * Gets the offset after the transition.
     *
     * This is the offset in use on and after the instant of the transition.
     *
     * @return {ZoneOffset} the offset after the transition, not null
     */
    offsetAfter() {
        return this._offsetAfter;
    }
 
    /**
     * Gets the duration of the transition.
     *
     * In most cases, the transition duration is one hour, however this is not always the case.
     * The duration will be positive for a gap and negative for an overlap.
     * Time-zones are second-based, so the nanosecond part of the duration will be zero.
     *
     * @return {Duration} the duration of the transition, positive for gaps, negative for overlaps
     */
    duration() {
        return Duration.ofSeconds(this.durationSeconds());
    }
 
    /**
     * Gets the duration of the transition in seconds.
     *
     * @return {number} the duration in seconds
     */
    durationSeconds() {
        return this._offsetAfter.totalSeconds() - this._offsetBefore.totalSeconds();
    }
 
    /**
     * Does this transition represent a gap in the local time-line.
     *
     * Gaps occur where there are local date-times that simply do not not exist.
     * An example would be when the offset changes from `+01:00` to `+02:00`.
     * This might be described as 'the clocks will move forward one hour tonight at 1am'.
     *
     * @return {boolean} true if this transition is a gap, false if it is an overlap
     */
    isGap() {
        return this._offsetAfter.totalSeconds() > this._offsetBefore.totalSeconds();
    }
 
    /**
     * Does this transition represent a gap in the local time-line.
     *
     * Overlaps occur where there are local date-times that exist twice.
     * An example would be when the offset changes from `+02:00` to `+01:00`.
     * This might be described as 'the clocks will move back one hour tonight at 2am'.
     *
     * @return {boolean} true if this transition is an overlap, false if it is a gap
     */
    isOverlap() {
        return this._offsetAfter.totalSeconds() < this._offsetBefore.totalSeconds();
    }
 
    /**
     * Checks if the specified offset is valid during this transition.
     *
     * This checks to see if the given offset will be valid at some point in the transition.
     * A gap will always return false.
     * An overlap will return true if the offset is either the before or after offset.
     *
     * @param {ZoneOffset} offset - the offset to check, null returns false
     * @return {boolean} true if the offset is valid during the transition
     */
    isValidOffset(offset) {
        return this.isGap() ? false : (this._offsetBefore.equals(offset) || this._offsetAfter.equals(offset));
    }
 
    /**
     * Gets the valid offsets during this transition.
     *
     * A gap will return an empty list, while an overlap will return both offsets.
     *
     * @return {ZoneOffset[]} the list of valid offsets
     */
    validOffsets() {
        if (this.isGap()){
            return [];
        } else {
            return [this._offsetBefore, this._offsetAfter];
        }
    }
 
    //-----------------------------------------------------------------------
    /**
     * Compares this transition to another based on the transition instant.
     *
     * This compares the instants of each transition.
     * The offsets are ignored, making this order inconsistent with equals.
     *
     * @param {ZoneOffsetTransition} transition - the transition to compare to, not null
     * @return {number} the comparator value, negative if less, positive if greater
     */
    compareTo(transition) {
        return this.instant().compareTo(transition.instant());
    }
 
    //-----------------------------------------------------------------------
    /**
     * Checks if this object equals another.
     *
     * The entire state of the object is compared.
     *
     * @param {*} other - the other object to compare to, null returns false
     * @return true if equal
     */
    equals(other) {
        if (other === this) {
            return true;
        }
        if (other instanceof ZoneOffsetTransition) {
            const d = other;
            return this._transition.equals(d._transition) &&
                this._offsetBefore.equals(d.offsetBefore()) && this._offsetAfter.equals(d.offsetAfter());
        }
        return false;
    }
 
    /**
     * Returns a suitable hash code.
     *
     * @return {number} the hash code
     */
    hashCode() {
        return this._transition.hashCode() ^ this._offsetBefore.hashCode() ^ (this._offsetAfter.hashCode()>>>16);
    }
 
    //-----------------------------------------------------------------------
    /**
     * Returns a string describing this object.
     *
     * @return {string} a string for debugging, not null
     */
    toString() {
        return `Transition[${this.isGap() ? 'Gap' : 'Overlap' 
        } at ${this._transition.toString()}${this._offsetBefore.toString() 
        } to ${this._offsetAfter}]`;
    }
 
}