Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/make/src/classes/build/tools/tzdb/ZoneRulesBuilder.java
32287 views
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 build.tools.tzdb;
63
64
import static build.tools.tzdb.Utils.*;
65
66
import java.util.ArrayList;
67
import java.util.Collections;
68
import java.util.HashMap;
69
import java.util.List;
70
import java.util.Map;
71
import java.util.Objects;
72
73
/**
74
* A mutable builder used to create all the rules for a historic time-zone.
75
* <p>
76
* The rules of a time-zone describe how the offset changes over time.
77
* The rules are created by building windows on the time-line within which
78
* the different rules apply. The rules may be one of two kinds:
79
* <p><ul>
80
* <li>Fixed savings - A single fixed amount of savings from the standard offset will apply.</li>
81
* <li>Rules - A set of one or more rules describe how daylight savings changes during the window.</li>
82
* </ul><p>
83
*
84
* <h4>Implementation notes</h4>
85
* This class is a mutable builder used to create zone instances.
86
* It must only be used from a single thread.
87
* The created instances are immutable and thread-safe.
88
*
89
* @since 1.8
90
*/
91
public class ZoneRulesBuilder {
92
93
/**
94
* The list of windows.
95
*/
96
private List<TZWindow> windowList = new ArrayList<>();
97
98
//-----------------------------------------------------------------------
99
/**
100
* Constructs an instance of the builder that can be used to create zone rules.
101
* <p>
102
* The builder is used by adding one or more windows representing portions
103
* of the time-line. The standard offset from UTC/Greenwich will be constant
104
* within a window, although two adjacent windows can have the same standard offset.
105
* <p>
106
* Within each window, there can either be a
107
* {@link #setFixedSavingsToWindow fixed savings amount} or a
108
* {@link #addRuleToWindow list of rules}.
109
*/
110
public ZoneRulesBuilder() {
111
}
112
113
//-----------------------------------------------------------------------
114
/**
115
* Adds a window to the builder that can be used to filter a set of rules.
116
* <p>
117
* This method defines and adds a window to the zone where the standard offset is specified.
118
* The window limits the effect of subsequent additions of transition rules
119
* or fixed savings. If neither rules or fixed savings are added to the window
120
* then the window will default to no savings.
121
* <p>
122
* Each window must be added sequentially, as the start instant of the window
123
* is derived from the until instant of the previous window.
124
*
125
* @param standardOffset the standard offset, not null
126
* @param until the date-time that the offset applies until, not null
127
* @param untilDefinition the time type for the until date-time, not null
128
* @return this, for chaining
129
* @throws IllegalStateException if the window order is invalid
130
*/
131
public ZoneRulesBuilder addWindow(
132
ZoneOffset standardOffset,
133
LocalDateTime until,
134
TimeDefinition untilDefinition) {
135
Objects.requireNonNull(standardOffset, "standardOffset");
136
Objects.requireNonNull(until, "until");
137
Objects.requireNonNull(untilDefinition, "untilDefinition");
138
TZWindow window = new TZWindow(standardOffset, until, untilDefinition);
139
if (windowList.size() > 0) {
140
TZWindow previous = windowList.get(windowList.size() - 1);
141
window.validateWindowOrder(previous);
142
}
143
windowList.add(window);
144
return this;
145
}
146
147
/**
148
* Adds a window that applies until the end of time to the builder that can be
149
* used to filter a set of rules.
150
* <p>
151
* This method defines and adds a window to the zone where the standard offset is specified.
152
* The window limits the effect of subsequent additions of transition rules
153
* or fixed savings. If neither rules or fixed savings are added to the window
154
* then the window will default to no savings.
155
* <p>
156
* This must be added after all other windows.
157
* No more windows can be added after this one.
158
*
159
* @param standardOffset the standard offset, not null
160
* @return this, for chaining
161
* @throws IllegalStateException if a forever window has already been added
162
*/
163
public ZoneRulesBuilder addWindowForever(ZoneOffset standardOffset) {
164
return addWindow(standardOffset, LocalDateTime.MAX, TimeDefinition.WALL);
165
}
166
167
//-----------------------------------------------------------------------
168
/**
169
* Sets the previously added window to have fixed savings.
170
* <p>
171
* Setting a window to have fixed savings simply means that a single daylight
172
* savings amount applies throughout the window. The window could be small,
173
* such as a single summer, or large, such as a multi-year daylight savings.
174
* <p>
175
* A window can either have fixed savings or rules but not both.
176
*
177
* @param fixedSavingAmountSecs the amount of saving to use for the whole window, not null
178
* @return this, for chaining
179
* @throws IllegalStateException if no window has yet been added
180
* @throws IllegalStateException if the window already has rules
181
*/
182
public ZoneRulesBuilder setFixedSavingsToWindow(int fixedSavingAmountSecs) {
183
if (windowList.isEmpty()) {
184
throw new IllegalStateException("Must add a window before setting the fixed savings");
185
}
186
TZWindow window = windowList.get(windowList.size() - 1);
187
window.setFixedSavings(fixedSavingAmountSecs);
188
return this;
189
}
190
191
//-----------------------------------------------------------------------
192
/**
193
* Adds a single transition rule to the current window.
194
* <p>
195
* This adds a rule such that the offset, expressed as a daylight savings amount,
196
* changes at the specified date-time.
197
*
198
* @param transitionDateTime the date-time that the transition occurs as defined by timeDefintion, not null
199
* @param timeDefinition the definition of how to convert local to actual time, not null
200
* @param savingAmountSecs the amount of saving from the standard offset after the transition in seconds
201
* @return this, for chaining
202
* @throws IllegalStateException if no window has yet been added
203
* @throws IllegalStateException if the window already has fixed savings
204
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
205
*/
206
public ZoneRulesBuilder addRuleToWindow(
207
LocalDateTime transitionDateTime,
208
TimeDefinition timeDefinition,
209
int savingAmountSecs) {
210
Objects.requireNonNull(transitionDateTime, "transitionDateTime");
211
return addRuleToWindow(
212
transitionDateTime.getYear(), transitionDateTime.getYear(),
213
transitionDateTime.getMonth(), transitionDateTime.getDayOfMonth(),
214
-1, transitionDateTime.getTime(), false, timeDefinition, savingAmountSecs);
215
}
216
217
/**
218
* Adds a single transition rule to the current window.
219
* <p>
220
* This adds a rule such that the offset, expressed as a daylight savings amount,
221
* changes at the specified date-time.
222
*
223
* @param year the year of the transition, from MIN_YEAR to MAX_YEAR
224
* @param month the month of the transition, not null
225
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
226
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
227
* @param time the time that the transition occurs as defined by timeDefintion, not null
228
* @param timeEndOfDay whether midnight is at the end of day
229
* @param timeDefinition the definition of how to convert local to actual time, not null
230
* @param savingAmountSecs the amount of saving from the standard offset after the transition in seconds
231
* @return this, for chaining
232
* @throws DateTimeException if a date-time field is out of range
233
* @throws IllegalStateException if no window has yet been added
234
* @throws IllegalStateException if the window already has fixed savings
235
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
236
*/
237
public ZoneRulesBuilder addRuleToWindow(
238
int year,
239
int month,
240
int dayOfMonthIndicator,
241
LocalTime time,
242
boolean timeEndOfDay,
243
TimeDefinition timeDefinition,
244
int savingAmountSecs) {
245
return addRuleToWindow(year, year, month, dayOfMonthIndicator, -1, time, timeEndOfDay, timeDefinition, savingAmountSecs);
246
}
247
248
/**
249
* Adds a multi-year transition rule to the current window.
250
* <p>
251
* This adds a rule such that the offset, expressed as a daylight savings amount,
252
* changes at the specified date-time for each year in the range.
253
*
254
* @param startYear the start year of the rule, from MIN_YEAR to MAX_YEAR
255
* @param endYear the end year of the rule, from MIN_YEAR to MAX_YEAR
256
* @param month the month of the transition, from 1 to 12
257
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
258
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
259
* @param dayOfWeek the day-of-week to adjust to, -1 if day-of-month should not be adjusted
260
* @param time the time that the transition occurs as defined by timeDefintion, not null
261
* @param timeEndOfDay whether midnight is at the end of day
262
* @param timeDefinition the definition of how to convert local to actual time, not null
263
* @param savingAmountSecs the amount of saving from the standard offset after the transition in seconds
264
* @return this, for chaining
265
* @throws DateTimeException if a date-time field is out of range
266
* @throws IllegalArgumentException if the day of month indicator is invalid
267
* @throws IllegalArgumentException if the end of day midnight flag does not match the time
268
* @throws IllegalStateException if no window has yet been added
269
* @throws IllegalStateException if the window already has fixed savings
270
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
271
*/
272
public ZoneRulesBuilder addRuleToWindow(
273
int startYear,
274
int endYear,
275
int month,
276
int dayOfMonthIndicator,
277
int dayOfWeek,
278
LocalTime time,
279
boolean timeEndOfDay,
280
TimeDefinition timeDefinition,
281
int savingAmountSecs) {
282
Objects.requireNonNull(time, "time");
283
Objects.requireNonNull(timeDefinition, "timeDefinition");
284
if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) {
285
throw new IllegalArgumentException("Day of month indicator must be between -28 and 31 inclusive excluding zero");
286
}
287
if (timeEndOfDay && time.equals(LocalTime.MIDNIGHT) == false) {
288
throw new IllegalArgumentException("Time must be midnight when end of day flag is true");
289
}
290
if (windowList.isEmpty()) {
291
throw new IllegalStateException("Must add a window before adding a rule");
292
}
293
TZWindow window = windowList.get(windowList.size() - 1);
294
window.addRule(startYear, endYear, month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition, savingAmountSecs);
295
return this;
296
}
297
298
//-----------------------------------------------------------------------
299
/**
300
* Completes the build converting the builder to a set of time-zone rules.
301
* <p>
302
* Calling this method alters the state of the builder.
303
* Further rules should not be added to this builder once this method is called.
304
*
305
* @param zoneId the time-zone ID, not null
306
* @return the zone rules, not null
307
* @throws IllegalStateException if no windows have been added
308
* @throws IllegalStateException if there is only one rule defined as being forever for any given window
309
*/
310
public ZoneRules toRules(String zoneId) {
311
Objects.requireNonNull(zoneId, "zoneId");
312
if (windowList.isEmpty()) {
313
throw new IllegalStateException("No windows have been added to the builder");
314
}
315
316
final List<ZoneOffsetTransition> standardTransitionList = new ArrayList<>(4);
317
final List<ZoneOffsetTransition> transitionList = new ArrayList<>(256);
318
final List<ZoneOffsetTransitionRule> lastTransitionRuleList = new ArrayList<>(2);
319
320
// initialize the standard offset calculation
321
final TZWindow firstWindow = windowList.get(0);
322
ZoneOffset loopStandardOffset = firstWindow.standardOffset;
323
int loopSavings = 0;
324
if (firstWindow.fixedSavingAmountSecs != null) {
325
loopSavings = firstWindow.fixedSavingAmountSecs;
326
}
327
final ZoneOffset firstWallOffset = ZoneOffset.ofTotalSeconds(loopStandardOffset.getTotalSeconds() + loopSavings);
328
LocalDateTime loopWindowStart = LocalDateTime.of(YEAR_MIN_VALUE, 1, 1, 0, 0);
329
ZoneOffset loopWindowOffset = firstWallOffset;
330
331
// build the windows and rules to interesting data
332
for (TZWindow window : windowList) {
333
// tidy the state
334
window.tidy(loopWindowStart.getYear());
335
336
// calculate effective savings at the start of the window
337
Integer effectiveSavings = window.fixedSavingAmountSecs;
338
if (effectiveSavings == null) {
339
// apply rules from this window together with the standard offset and
340
// savings from the last window to find the savings amount applicable
341
// at start of this window
342
effectiveSavings = 0;
343
for (TZRule rule : window.ruleList) {
344
if (rule.toEpochSecond(loopStandardOffset, loopSavings) > loopWindowStart.toEpochSecond(loopWindowOffset)) {
345
// previous savings amount found, which could be the savings amount at
346
// the instant that the window starts (hence isAfter)
347
break;
348
}
349
effectiveSavings = rule.savingAmountSecs;
350
}
351
}
352
353
// check if standard offset changed, and update it
354
if (loopStandardOffset.equals(window.standardOffset) == false) {
355
standardTransitionList.add(
356
new ZoneOffsetTransition(
357
LocalDateTime.ofEpochSecond(loopWindowStart.toEpochSecond(loopWindowOffset), 0, loopStandardOffset),
358
loopStandardOffset, window.standardOffset));
359
loopStandardOffset = window.standardOffset;
360
}
361
362
// check if the start of the window represents a transition
363
ZoneOffset effectiveWallOffset = ZoneOffset.ofTotalSeconds(loopStandardOffset.getTotalSeconds() + effectiveSavings);
364
if (loopWindowOffset.equals(effectiveWallOffset) == false) {
365
transitionList.add(new ZoneOffsetTransition(loopWindowStart, loopWindowOffset, effectiveWallOffset));
366
}
367
loopSavings = effectiveSavings;
368
369
// apply rules within the window
370
for (TZRule rule : window.ruleList) {
371
if (rule.isTransition(loopSavings)) {
372
ZoneOffsetTransition trans = rule.toTransition(loopStandardOffset, loopSavings);
373
if (trans.toEpochSecond() < loopWindowStart.toEpochSecond(loopWindowOffset) == false &&
374
trans.toEpochSecond() < window.createDateTimeEpochSecond(loopSavings)) {
375
transitionList.add(trans);
376
loopSavings = rule.savingAmountSecs;
377
}
378
}
379
}
380
381
// calculate last rules
382
for (TZRule lastRule : window.lastRuleList) {
383
lastTransitionRuleList.add(lastRule.toTransitionRule(loopStandardOffset, loopSavings));
384
loopSavings = lastRule.savingAmountSecs;
385
}
386
387
// finally we can calculate the true end of the window, passing it to the next window
388
loopWindowOffset = window.createWallOffset(loopSavings);
389
loopWindowStart = LocalDateTime.ofEpochSecond(
390
window.createDateTimeEpochSecond(loopSavings), 0, loopWindowOffset);
391
}
392
393
return new ZoneRules(
394
firstWindow.standardOffset, firstWallOffset, standardTransitionList,
395
transitionList, lastTransitionRuleList);
396
}
397
398
//-----------------------------------------------------------------------
399
/**
400
* A definition of a window in the time-line.
401
* The window will have one standard offset and will either have a
402
* fixed DST savings or a set of rules.
403
*/
404
class TZWindow {
405
/** The standard offset during the window, not null. */
406
private final ZoneOffset standardOffset;
407
/** The end local time, not null. */
408
private final LocalDateTime windowEnd;
409
/** The type of the end time, not null. */
410
private final TimeDefinition timeDefinition;
411
412
/** The fixed amount of the saving to be applied during this window. */
413
private Integer fixedSavingAmountSecs;
414
/** The rules for the current window. */
415
private List<TZRule> ruleList = new ArrayList<>();
416
/** The latest year that the last year starts at. */
417
private int maxLastRuleStartYear = YEAR_MIN_VALUE;
418
/** The last rules. */
419
private List<TZRule> lastRuleList = new ArrayList<>();
420
421
/**
422
* Constructor.
423
*
424
* @param standardOffset the standard offset applicable during the window, not null
425
* @param windowEnd the end of the window, relative to the time definition, null if forever
426
* @param timeDefinition the time definition for calculating the true end, not null
427
*/
428
TZWindow(
429
ZoneOffset standardOffset,
430
LocalDateTime windowEnd,
431
TimeDefinition timeDefinition) {
432
super();
433
this.windowEnd = windowEnd;
434
this.timeDefinition = timeDefinition;
435
this.standardOffset = standardOffset;
436
}
437
438
/**
439
* Sets the fixed savings amount for the window.
440
*
441
* @param fixedSavingAmount the amount of daylight saving to apply throughout the window, may be null
442
* @throws IllegalStateException if the window already has rules
443
*/
444
void setFixedSavings(int fixedSavingAmount) {
445
if (ruleList.size() > 0 || lastRuleList.size() > 0) {
446
throw new IllegalStateException("Window has DST rules, so cannot have fixed savings");
447
}
448
this.fixedSavingAmountSecs = fixedSavingAmount;
449
}
450
451
/**
452
* Adds a rule to the current window.
453
*
454
* @param startYear the start year of the rule, from MIN_YEAR to MAX_YEAR
455
* @param endYear the end year of the rule, from MIN_YEAR to MAX_YEAR
456
* @param month the month of the transition, not null
457
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
458
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
459
* @param dayOfWeek the day-of-week to adjust to, null if day-of-month should not be adjusted
460
* @param time the time that the transition occurs as defined by timeDefintion, not null
461
* @param timeEndOfDay whether midnight is at the end of day
462
* @param timeDefinition the definition of how to convert local to actual time, not null
463
* @param savingAmountSecs the amount of saving from the standard offset in seconds
464
* @throws IllegalStateException if the window already has fixed savings
465
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
466
*/
467
void addRule(
468
int startYear,
469
int endYear,
470
int month,
471
int dayOfMonthIndicator,
472
int dayOfWeek,
473
LocalTime time,
474
boolean timeEndOfDay,
475
TimeDefinition timeDefinition,
476
int savingAmountSecs) {
477
478
if (fixedSavingAmountSecs != null) {
479
throw new IllegalStateException("Window has a fixed DST saving, so cannot have DST rules");
480
}
481
if (ruleList.size() >= 2000) {
482
throw new IllegalStateException("Window has reached the maximum number of allowed rules");
483
}
484
boolean lastRule = false;
485
if (endYear == YEAR_MAX_VALUE) {
486
lastRule = true;
487
endYear = startYear;
488
}
489
int year = startYear;
490
while (year <= endYear) {
491
TZRule rule = new TZRule(year, month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition, savingAmountSecs);
492
if (lastRule) {
493
lastRuleList.add(rule);
494
} else {
495
ruleList.add(rule);
496
}
497
maxLastRuleStartYear = Math.max(startYear, maxLastRuleStartYear);
498
year++;
499
}
500
}
501
502
/**
503
* Validates that this window is after the previous one.
504
*
505
* @param previous the previous window, not null
506
* @throws IllegalStateException if the window order is invalid
507
*/
508
void validateWindowOrder(TZWindow previous) {
509
if (windowEnd.compareTo(previous.windowEnd) < 0) {
510
throw new IllegalStateException("Windows must be added in date-time order: " +
511
windowEnd + " < " + previous.windowEnd);
512
}
513
}
514
515
/**
516
* Adds rules to make the last rules all start from the same year.
517
* Also add one more year to avoid weird case where penultimate year has odd offset.
518
*
519
* @param windowStartYear the window start year
520
* @throws IllegalStateException if there is only one rule defined as being forever
521
*/
522
void tidy(int windowStartYear) {
523
if (lastRuleList.size() == 1) {
524
throw new IllegalStateException("Cannot have only one rule defined as being forever");
525
}
526
527
// handle last rules
528
if (windowEnd.equals(LocalDateTime.MAX)) {
529
// setup at least one real rule, which closes off other windows nicely
530
maxLastRuleStartYear = Math.max(maxLastRuleStartYear, windowStartYear) + 1;
531
for (TZRule lastRule : lastRuleList) {
532
addRule(lastRule.year, maxLastRuleStartYear, lastRule.month, lastRule.dayOfMonthIndicator,
533
lastRule.dayOfWeek, lastRule.time, lastRule.timeEndOfDay, lastRule.timeDefinition, lastRule.savingAmountSecs);
534
lastRule.year = maxLastRuleStartYear + 1;
535
}
536
if (maxLastRuleStartYear == YEAR_MAX_VALUE) {
537
lastRuleList.clear();
538
} else {
539
maxLastRuleStartYear++;
540
}
541
} else {
542
// convert all within the endYear limit
543
int endYear = windowEnd.getYear();
544
for (TZRule lastRule : lastRuleList) {
545
addRule(lastRule.year, endYear + 1, lastRule.month, lastRule.dayOfMonthIndicator,
546
lastRule.dayOfWeek, lastRule.time, lastRule.timeEndOfDay, lastRule.timeDefinition, lastRule.savingAmountSecs);
547
}
548
lastRuleList.clear();
549
maxLastRuleStartYear = YEAR_MAX_VALUE;
550
}
551
552
// ensure lists are sorted
553
Collections.sort(ruleList);
554
Collections.sort(lastRuleList);
555
556
// default fixed savings to zero
557
if (ruleList.size() == 0 && fixedSavingAmountSecs == null) {
558
fixedSavingAmountSecs = 0;
559
}
560
}
561
562
/**
563
* Checks if the window is empty.
564
*
565
* @return true if the window is only a standard offset
566
*/
567
boolean isSingleWindowStandardOffset() {
568
return windowEnd.equals(LocalDateTime.MAX) && timeDefinition == TimeDefinition.WALL &&
569
fixedSavingAmountSecs == null && lastRuleList.isEmpty() && ruleList.isEmpty();
570
}
571
572
/**
573
* Creates the wall offset for the local date-time at the end of the window.
574
*
575
* @param savingsSecs the amount of savings in use in seconds
576
* @return the created date-time epoch second in the wall offset, not null
577
*/
578
ZoneOffset createWallOffset(int savingsSecs) {
579
return ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsSecs);
580
}
581
582
/**
583
* Creates the offset date-time for the local date-time at the end of the window.
584
*
585
* @param savingsSecs the amount of savings in use in seconds
586
* @return the created date-time epoch second in the wall offset, not null
587
*/
588
long createDateTimeEpochSecond(int savingsSecs) {
589
ZoneOffset wallOffset = createWallOffset(savingsSecs);
590
LocalDateTime ldt = timeDefinition.createDateTime(windowEnd, standardOffset, wallOffset);
591
return ldt.toEpochSecond(wallOffset);
592
}
593
}
594
595
//-----------------------------------------------------------------------
596
/**
597
* A definition of the way a local time can be converted to an offset time.
598
*/
599
class TZRule implements Comparable<TZRule> {
600
private int year;
601
private int month;
602
private int dayOfMonthIndicator;
603
private int dayOfWeek;
604
private LocalTime time;
605
private boolean timeEndOfDay; // Whether the local time is end of day.
606
private TimeDefinition timeDefinition; // The type of the time.
607
private int savingAmountSecs; // The amount of the saving to be applied after this point.
608
609
/**
610
* Constructor.
611
*
612
* @param year the year
613
* @param month the month, value from 1 to 12
614
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
615
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
616
* @param dayOfWeek the day-of-week, -1 if day-of-month is exact
617
* @param time the time, not null
618
* @param timeEndOfDay whether midnight is at the end of day
619
* @param timeDefinition the time definition, not null
620
* @param savingAfterSecs the savings amount in seconds
621
*/
622
TZRule(int year, int month, int dayOfMonthIndicator,
623
int dayOfWeek, LocalTime time, boolean timeEndOfDay,
624
TimeDefinition timeDefinition, int savingAfterSecs) {
625
this.year = year;
626
this.month = month;
627
this.dayOfMonthIndicator = dayOfMonthIndicator;
628
this.dayOfWeek = dayOfWeek;
629
this.time = time;
630
this.timeEndOfDay = timeEndOfDay;
631
this.timeDefinition = timeDefinition;
632
this.savingAmountSecs = savingAfterSecs;
633
}
634
635
/**
636
* Converts this to a transition.
637
*
638
* @param standardOffset the active standard offset, not null
639
* @param savingsBeforeSecs the active savings in seconds
640
* @return the transition, not null
641
*/
642
ZoneOffsetTransition toTransition(ZoneOffset standardOffset, int savingsBeforeSecs) {
643
// copy of code in ZoneOffsetTransitionRule to avoid infinite loop
644
LocalDate date = toLocalDate();
645
LocalDateTime ldt = LocalDateTime.of(date, time);
646
ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsBeforeSecs);
647
LocalDateTime dt = timeDefinition.createDateTime(ldt, standardOffset, wallOffset);
648
ZoneOffset offsetAfter = ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingAmountSecs);
649
return new ZoneOffsetTransition(dt, wallOffset, offsetAfter);
650
}
651
652
/**
653
* Returns the apoch second of this rules with the specified
654
* active standard offset and active savings
655
*
656
* @param standardOffset the active standard offset, not null
657
* @param savingsBeforeSecs the active savings in seconds
658
* @return the transition epoch second
659
*/
660
long toEpochSecond(ZoneOffset standardOffset, int savingsBeforeSecs) {
661
LocalDateTime ldt = LocalDateTime.of(toLocalDate(), time);
662
ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsBeforeSecs);
663
return timeDefinition.createDateTime(ldt, standardOffset, wallOffset)
664
.toEpochSecond(wallOffset);
665
}
666
667
/**
668
* Tests if this a real transition with the active savings in seconds
669
*
670
* @param savingsBeforeSecs the active savings in seconds
671
* @return true, if savings in seconds changes
672
*/
673
boolean isTransition(int savingsBeforeSecs) {
674
return savingAmountSecs != savingsBeforeSecs;
675
}
676
677
/**
678
* Converts this to a transition rule.
679
*
680
* @param standardOffset the active standard offset, not null
681
* @param savingsBeforeSecs the active savings before the transition in seconds
682
* @return the transition, not null
683
*/
684
ZoneOffsetTransitionRule toTransitionRule(ZoneOffset standardOffset, int savingsBeforeSecs) {
685
// optimize stored format
686
if (dayOfMonthIndicator < 0) {
687
if (month != 2) { // not Month.FEBRUARY
688
dayOfMonthIndicator = maxLengthOfMonth(month) - 6;
689
}
690
}
691
if (timeEndOfDay && dayOfMonthIndicator > 0 &&
692
(dayOfMonthIndicator == 28 && month == 2) == false) {
693
LocalDate date = LocalDate.of(2004, month, dayOfMonthIndicator).plusDays(1); // leap-year
694
month = date.getMonth();
695
dayOfMonthIndicator = date.getDayOfMonth();
696
if (dayOfWeek != -1) {
697
dayOfWeek = plusDayOfWeek(dayOfWeek, 1);
698
}
699
timeEndOfDay = false;
700
}
701
// build rule
702
return new ZoneOffsetTransitionRule(
703
month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition,
704
standardOffset,
705
ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsBeforeSecs),
706
ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingAmountSecs));
707
}
708
709
public int compareTo(TZRule other) {
710
int cmp = year - other.year;
711
cmp = (cmp == 0 ? month - other.month : cmp);
712
if (cmp == 0) {
713
// convert to date to handle dow/domIndicator/timeEndOfDay
714
LocalDate thisDate = toLocalDate();
715
LocalDate otherDate = other.toLocalDate();
716
cmp = thisDate.compareTo(otherDate);
717
}
718
cmp = (cmp == 0 ? time.compareTo(other.time) : cmp);
719
return cmp;
720
}
721
722
private LocalDate toLocalDate() {
723
LocalDate date;
724
if (dayOfMonthIndicator < 0) {
725
int monthLen = lengthOfMonth(month, isLeapYear(year));
726
date = LocalDate.of(year, month, monthLen + 1 + dayOfMonthIndicator);
727
if (dayOfWeek != -1) {
728
date = previousOrSame(date, dayOfWeek);
729
}
730
} else {
731
date = LocalDate.of(year, month, dayOfMonthIndicator);
732
if (dayOfWeek != -1) {
733
date = nextOrSame(date, dayOfWeek);
734
}
735
}
736
if (timeEndOfDay) {
737
date = date.plusDays(1);
738
}
739
return date;
740
}
741
}
742
743
}
744
745