Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/util/calendar/ZoneInfoFile.java
38918 views
1
/*
2
* Copyright (c) 2012, 2020, 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
package sun.util.calendar;
27
28
import java.io.ByteArrayInputStream;
29
import java.io.BufferedInputStream;
30
import java.io.DataInput;
31
import java.io.DataInputStream;
32
import java.io.File;
33
import java.io.FileInputStream;
34
import java.io.IOException;
35
import java.io.StreamCorruptedException;
36
import java.security.AccessController;
37
import java.security.PrivilegedAction;
38
import java.time.LocalDateTime;
39
import java.time.ZoneOffset;
40
import java.util.ArrayList;
41
import java.util.Arrays;
42
import java.util.Calendar;
43
import java.util.Collections;
44
import java.util.HashMap;
45
import java.util.List;
46
import java.util.Locale;
47
import java.util.Map;
48
import java.util.Map.Entry;
49
import java.util.Objects;
50
import java.util.Set;
51
import java.util.SimpleTimeZone;
52
import java.util.concurrent.ConcurrentHashMap;
53
import java.util.zip.CRC32;
54
import sun.security.action.GetPropertyAction;
55
56
/**
57
* Loads TZDB time-zone rules for j.u.TimeZone
58
* <p>
59
* @since 1.8
60
*/
61
public final class ZoneInfoFile {
62
63
/**
64
* Gets all available IDs supported in the Java run-time.
65
*
66
* @return a set of time zone IDs.
67
*/
68
public static String[] getZoneIds() {
69
int len = regions.length + oldMappings.length;
70
if (!USE_OLDMAPPING) {
71
len += 3; // EST/HST/MST not in tzdb.dat
72
}
73
String[] ids = Arrays.copyOf(regions, len);
74
int i = regions.length;
75
if (!USE_OLDMAPPING) {
76
ids[i++] = "EST";
77
ids[i++] = "HST";
78
ids[i++] = "MST";
79
}
80
for (int j = 0; j < oldMappings.length; j++) {
81
ids[i++] = oldMappings[j][0];
82
}
83
return ids;
84
}
85
86
/**
87
* Gets all available IDs that have the same value as the
88
* specified raw GMT offset.
89
*
90
* @param rawOffset the GMT offset in milliseconds. This
91
* value should not include any daylight saving time.
92
* @return an array of time zone IDs.
93
*/
94
public static String[] getZoneIds(int rawOffset) {
95
List<String> ids = new ArrayList<>();
96
for (String id : getZoneIds()) {
97
ZoneInfo zi = getZoneInfo(id);
98
if (zi.getRawOffset() == rawOffset) {
99
ids.add(id);
100
}
101
}
102
// It appears the "zi" implementation returns the
103
// sorted list, though the specification does not
104
// specify it. Keep the same behavior for better
105
// compatibility.
106
String[] list = ids.toArray(new String[ids.size()]);
107
Arrays.sort(list);
108
return list;
109
}
110
111
public static ZoneInfo getZoneInfo(String zoneId) {
112
if (zoneId == null) {
113
return null;
114
}
115
ZoneInfo zi = getZoneInfo0(zoneId);
116
if (zi != null) {
117
zi = (ZoneInfo)zi.clone();
118
zi.setID(zoneId);
119
}
120
return zi;
121
}
122
123
private static ZoneInfo getZoneInfo0(String zoneId) {
124
try {
125
ZoneInfo zi = zones.get(zoneId);
126
if (zi != null) {
127
return zi;
128
}
129
String zid = zoneId;
130
if (aliases.containsKey(zoneId)) {
131
zid = aliases.get(zoneId);
132
}
133
int index = Arrays.binarySearch(regions, zid);
134
if (index < 0) {
135
return null;
136
}
137
byte[] bytes = ruleArray[indices[index]];
138
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
139
zi = getZoneInfo(dis, zid);
140
zones.put(zoneId, zi);
141
return zi;
142
} catch (Exception ex) {
143
throw new RuntimeException("Invalid binary time-zone data: TZDB:" +
144
zoneId + ", version: " + versionId, ex);
145
}
146
}
147
148
/**
149
* Returns a Map from alias time zone IDs to their standard
150
* time zone IDs.
151
*
152
* @return an unmodified alias mapping
153
*/
154
public static Map<String, String> getAliasMap() {
155
return Collections.unmodifiableMap(aliases);
156
}
157
158
/**
159
* Gets the version of this tz data.
160
*
161
* @return the tzdb version
162
*/
163
public static String getVersion() {
164
return versionId;
165
}
166
167
/**
168
* Gets a ZoneInfo with the given GMT offset. The object
169
* has its ID in the format of GMT{+|-}hh:mm.
170
*
171
* @param originalId the given custom id (before normalized such as "GMT+9")
172
* @param gmtOffset GMT offset <em>in milliseconds</em>
173
* @return a ZoneInfo constructed with the given GMT offset
174
*/
175
public static ZoneInfo getCustomTimeZone(String originalId, int gmtOffset) {
176
String id = toCustomID(gmtOffset);
177
return new ZoneInfo(id, gmtOffset);
178
}
179
180
public static String toCustomID(int gmtOffset) {
181
char sign;
182
int offset = gmtOffset / 60000;
183
if (offset >= 0) {
184
sign = '+';
185
} else {
186
sign = '-';
187
offset = -offset;
188
}
189
int hh = offset / 60;
190
int mm = offset % 60;
191
192
char[] buf = new char[] { 'G', 'M', 'T', sign, '0', '0', ':', '0', '0' };
193
if (hh >= 10) {
194
buf[4] += hh / 10;
195
}
196
buf[5] += hh % 10;
197
if (mm != 0) {
198
buf[7] += mm / 10;
199
buf[8] += mm % 10;
200
}
201
return new String(buf);
202
}
203
204
///////////////////////////////////////////////////////////
205
private ZoneInfoFile() {
206
}
207
208
private static String versionId;
209
private final static Map<String, ZoneInfo> zones = new ConcurrentHashMap<>();
210
private static Map<String, String> aliases = new HashMap<>();
211
212
private static byte[][] ruleArray;
213
private static String[] regions;
214
private static int[] indices;
215
216
// Flag for supporting JDK backward compatible IDs, such as "EST".
217
private static final boolean USE_OLDMAPPING;
218
219
private static String[][] oldMappings = new String[][] {
220
{ "ACT", "Australia/Darwin" },
221
{ "AET", "Australia/Sydney" },
222
{ "AGT", "America/Argentina/Buenos_Aires" },
223
{ "ART", "Africa/Cairo" },
224
{ "AST", "America/Anchorage" },
225
{ "BET", "America/Sao_Paulo" },
226
{ "BST", "Asia/Dhaka" },
227
{ "CAT", "Africa/Harare" },
228
{ "CNT", "America/St_Johns" },
229
{ "CST", "America/Chicago" },
230
{ "CTT", "Asia/Shanghai" },
231
{ "EAT", "Africa/Addis_Ababa" },
232
{ "ECT", "Europe/Paris" },
233
{ "IET", "America/Indiana/Indianapolis" },
234
{ "IST", "Asia/Kolkata" },
235
{ "JST", "Asia/Tokyo" },
236
{ "MIT", "Pacific/Apia" },
237
{ "NET", "Asia/Yerevan" },
238
{ "NST", "Pacific/Auckland" },
239
{ "PLT", "Asia/Karachi" },
240
{ "PNT", "America/Phoenix" },
241
{ "PRT", "America/Puerto_Rico" },
242
{ "PST", "America/Los_Angeles" },
243
{ "SST", "Pacific/Guadalcanal" },
244
{ "VST", "Asia/Ho_Chi_Minh" },
245
};
246
247
static {
248
String oldmapping = AccessController.doPrivileged(
249
new GetPropertyAction("sun.timezone.ids.oldmapping", "false")).toLowerCase(Locale.ROOT);
250
USE_OLDMAPPING = (oldmapping.equals("yes") || oldmapping.equals("true"));
251
AccessController.doPrivileged(new PrivilegedAction<Object>() {
252
public Object run() {
253
try {
254
String libDir = System.getProperty("java.home") + File.separator + "lib";
255
try (DataInputStream dis = new DataInputStream(
256
new BufferedInputStream(new FileInputStream(
257
new File(libDir, "tzdb.dat"))))) {
258
load(dis);
259
}
260
} catch (Exception x) {
261
throw new Error(x);
262
}
263
return null;
264
}
265
});
266
}
267
268
private static void addOldMapping() {
269
for (String[] alias : oldMappings) {
270
aliases.put(alias[0], alias[1]);
271
}
272
if (USE_OLDMAPPING) {
273
aliases.put("EST", "America/New_York");
274
aliases.put("MST", "America/Denver");
275
aliases.put("HST", "Pacific/Honolulu");
276
} else {
277
zones.put("EST", new ZoneInfo("EST", -18000000));
278
zones.put("MST", new ZoneInfo("MST", -25200000));
279
zones.put("HST", new ZoneInfo("HST", -36000000));
280
}
281
}
282
283
public static boolean useOldMapping() {
284
return USE_OLDMAPPING;
285
}
286
287
/**
288
* Loads the rules from a DateInputStream
289
*
290
* @param dis the DateInputStream to load, not null
291
* @throws Exception if an error occurs
292
*/
293
private static void load(DataInputStream dis) throws ClassNotFoundException, IOException {
294
if (dis.readByte() != 1) {
295
throw new StreamCorruptedException("File format not recognised");
296
}
297
// group
298
String groupId = dis.readUTF();
299
if ("TZDB".equals(groupId) == false) {
300
throw new StreamCorruptedException("File format not recognised");
301
}
302
// versions, only keep the last one
303
int versionCount = dis.readShort();
304
for (int i = 0; i < versionCount; i++) {
305
versionId = dis.readUTF();
306
307
}
308
// regions
309
int regionCount = dis.readShort();
310
String[] regionArray = new String[regionCount];
311
for (int i = 0; i < regionCount; i++) {
312
regionArray[i] = dis.readUTF();
313
}
314
// rules
315
int ruleCount = dis.readShort();
316
ruleArray = new byte[ruleCount][];
317
for (int i = 0; i < ruleCount; i++) {
318
byte[] bytes = new byte[dis.readShort()];
319
dis.readFully(bytes);
320
ruleArray[i] = bytes;
321
}
322
// link version-region-rules, only keep the last version, if more than one
323
for (int i = 0; i < versionCount; i++) {
324
regionCount = dis.readShort();
325
regions = new String[regionCount];
326
indices = new int[regionCount];
327
for (int j = 0; j < regionCount; j++) {
328
regions[j] = regionArray[dis.readShort()];
329
indices[j] = dis.readShort();
330
}
331
}
332
// remove the following ids from the map, they
333
// are exclued from the "old" ZoneInfo
334
zones.remove("ROC");
335
for (int i = 0; i < versionCount; i++) {
336
int aliasCount = dis.readShort();
337
aliases.clear();
338
for (int j = 0; j < aliasCount; j++) {
339
String alias = regionArray[dis.readShort()];
340
String region = regionArray[dis.readShort()];
341
aliases.put(alias, region);
342
}
343
}
344
// old us time-zone names
345
addOldMapping();
346
}
347
348
/////////////////////////Ser/////////////////////////////////
349
public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception {
350
byte type = in.readByte();
351
// TBD: assert ZRULES:
352
int stdSize = in.readInt();
353
long[] stdTrans = new long[stdSize];
354
for (int i = 0; i < stdSize; i++) {
355
stdTrans[i] = readEpochSec(in);
356
}
357
int [] stdOffsets = new int[stdSize + 1];
358
for (int i = 0; i < stdOffsets.length; i++) {
359
stdOffsets[i] = readOffset(in);
360
}
361
int savSize = in.readInt();
362
long[] savTrans = new long[savSize];
363
for (int i = 0; i < savSize; i++) {
364
savTrans[i] = readEpochSec(in);
365
}
366
int[] savOffsets = new int[savSize + 1];
367
for (int i = 0; i < savOffsets.length; i++) {
368
savOffsets[i] = readOffset(in);
369
}
370
int ruleSize = in.readByte();
371
ZoneOffsetTransitionRule[] rules = new ZoneOffsetTransitionRule[ruleSize];
372
for (int i = 0; i < ruleSize; i++) {
373
rules[i] = new ZoneOffsetTransitionRule(in);
374
}
375
return getZoneInfo(zoneId, stdTrans, stdOffsets, savTrans, savOffsets, rules);
376
}
377
378
public static int readOffset(DataInput in) throws IOException {
379
int offsetByte = in.readByte();
380
return offsetByte == 127 ? in.readInt() : offsetByte * 900;
381
}
382
383
static long readEpochSec(DataInput in) throws IOException {
384
int hiByte = in.readByte() & 255;
385
if (hiByte == 255) {
386
return in.readLong();
387
} else {
388
int midByte = in.readByte() & 255;
389
int loByte = in.readByte() & 255;
390
long tot = ((hiByte << 16) + (midByte << 8) + loByte);
391
return (tot * 900) - 4575744000L;
392
}
393
}
394
395
/////////////////////////ZoneRules --> ZoneInfo/////////////////////////////////
396
397
// ZoneInfo starts with UTC1900
398
private static final long UTC1900 = -2208988800L;
399
400
// ZoneInfo ends with UTC2037
401
// LocalDateTime.of(2038, 1, 1, 0, 0, 0).toEpochSecond(ZoneOffset.UTC) - 1;
402
private static final long UTC2037 = 2145916799L;
403
404
// ZoneInfo has an ending entry for 2037, this need to be offset by
405
// a "rawOffset"
406
// LocalDateTime.of(2037, 1, 1, 0, 0, 0).toEpochSecond(ZoneOffset.UTC));
407
private static final long LDT2037 = 2114380800L;
408
409
//Current time. Used to determine future GMToffset transitions
410
private static final long CURRT = System.currentTimeMillis()/1000;
411
412
/* Get a ZoneInfo instance.
413
*
414
* @param standardTransitions the standard transitions, not null
415
* @param standardOffsets the standard offsets, not null
416
* @param savingsInstantTransitions the standard transitions, not null
417
* @param wallOffsets the wall offsets, not null
418
* @param lastRules the recurring last rules, size 15 or less, not null
419
*/
420
private static ZoneInfo getZoneInfo(String zoneId,
421
long[] standardTransitions,
422
int[] standardOffsets,
423
long[] savingsInstantTransitions,
424
int[] wallOffsets,
425
ZoneOffsetTransitionRule[] lastRules) {
426
int rawOffset = 0;
427
int dstSavings = 0;
428
int checksum = 0;
429
int[] params = null;
430
boolean willGMTOffsetChange = false;
431
432
// rawOffset, pick the last one
433
if (standardTransitions.length > 0) {
434
rawOffset = standardOffsets[standardOffsets.length - 1] * 1000;
435
willGMTOffsetChange = standardTransitions[standardTransitions.length - 1] > CURRT;
436
}
437
else
438
rawOffset = standardOffsets[0] * 1000;
439
440
// transitions, offsets;
441
long[] transitions = null;
442
int[] offsets = null;
443
int nOffsets = 0;
444
int nTrans = 0;
445
446
if (savingsInstantTransitions.length != 0) {
447
transitions = new long[250];
448
offsets = new int[100]; // TBD: ZoneInfo actually can't handle
449
// offsets.length > 16 (4-bit index limit)
450
// last year in trans table
451
// It should not matter to use before or after offset for year
452
int lastyear = getYear(savingsInstantTransitions[savingsInstantTransitions.length - 1],
453
wallOffsets[savingsInstantTransitions.length - 1]);
454
int i = 0, k = 1;
455
while (i < savingsInstantTransitions.length &&
456
savingsInstantTransitions[i] < UTC1900) {
457
i++; // skip any date before UTC1900
458
}
459
if (i < savingsInstantTransitions.length) {
460
// javazic writes the last GMT offset into index 0!
461
if (i < savingsInstantTransitions.length) {
462
offsets[0] = standardOffsets[standardOffsets.length - 1] * 1000;
463
nOffsets = 1;
464
}
465
// ZoneInfo has a beginning entry for 1900.
466
// Only add it if this is not the only one in table
467
nOffsets = addTrans(transitions, nTrans++,
468
offsets, nOffsets,
469
UTC1900,
470
wallOffsets[i],
471
getStandardOffset(standardTransitions, standardOffsets, UTC1900));
472
}
473
474
for (; i < savingsInstantTransitions.length; i++) {
475
long trans = savingsInstantTransitions[i];
476
if (trans > UTC2037) {
477
// no trans beyond LASTYEAR
478
lastyear = LASTYEAR;
479
break;
480
}
481
while (k < standardTransitions.length) {
482
// some standard offset transitions don't exist in
483
// savingInstantTrans, if the offset "change" doesn't
484
// really change the "effectiveWallOffset". For example
485
// the 1999/2000 pair in Zone Arg/Buenos_Aires, in which
486
// the daylightsaving "happened" but it actually does
487
// not result in the timezone switch. ZoneInfo however
488
// needs them in its transitions table
489
long trans_s = standardTransitions[k];
490
if (trans_s >= UTC1900) {
491
if (trans_s > trans)
492
break;
493
if (trans_s < trans) {
494
if (nOffsets + 2 >= offsets.length) {
495
offsets = Arrays.copyOf(offsets, offsets.length + 100);
496
}
497
if (nTrans + 1 >= transitions.length) {
498
transitions = Arrays.copyOf(transitions, transitions.length + 100);
499
}
500
nOffsets = addTrans(transitions, nTrans++, offsets, nOffsets,
501
trans_s,
502
wallOffsets[i],
503
standardOffsets[k+1]);
504
505
}
506
}
507
k++;
508
}
509
if (nOffsets + 2 >= offsets.length) {
510
offsets = Arrays.copyOf(offsets, offsets.length + 100);
511
}
512
if (nTrans + 1 >= transitions.length) {
513
transitions = Arrays.copyOf(transitions, transitions.length + 100);
514
}
515
nOffsets = addTrans(transitions, nTrans++, offsets, nOffsets,
516
trans,
517
wallOffsets[i + 1],
518
getStandardOffset(standardTransitions, standardOffsets, trans));
519
520
}
521
// append any leftover standard trans
522
while (k < standardTransitions.length) {
523
long trans = standardTransitions[k];
524
if (trans >= UTC1900) {
525
int offset = wallOffsets[i];
526
int offsetIndex = indexOf(offsets, 0, nOffsets, offset);
527
if (offsetIndex == nOffsets)
528
nOffsets++;
529
transitions[nTrans++] = ((trans * 1000) << TRANSITION_NSHIFT) |
530
(offsetIndex & OFFSET_MASK);
531
}
532
k++;
533
}
534
if (lastRules.length > 1) {
535
// fill the gap between the last trans until LASTYEAR
536
while (lastyear++ < LASTYEAR) {
537
for (ZoneOffsetTransitionRule zotr : lastRules) {
538
long trans = zotr.getTransitionEpochSecond(lastyear);
539
if (nOffsets + 2 >= offsets.length) {
540
offsets = Arrays.copyOf(offsets, offsets.length + 100);
541
}
542
if (nTrans + 1 >= transitions.length) {
543
transitions = Arrays.copyOf(transitions, transitions.length + 100);
544
}
545
nOffsets = addTrans(transitions, nTrans++,
546
offsets, nOffsets,
547
trans,
548
zotr.offsetAfter,
549
zotr.standardOffset);
550
}
551
}
552
ZoneOffsetTransitionRule startRule = lastRules[lastRules.length - 2];
553
ZoneOffsetTransitionRule endRule = lastRules[lastRules.length - 1];
554
params = new int[10];
555
if (startRule.offsetAfter - startRule.offsetBefore < 0 &&
556
endRule.offsetAfter - endRule.offsetBefore > 0) {
557
ZoneOffsetTransitionRule tmp;
558
tmp = startRule;
559
startRule = endRule;
560
endRule = tmp;
561
}
562
params[0] = startRule.month - 1;
563
int dom = startRule.dom;
564
int dow = startRule.dow;
565
if (dow == -1) {
566
params[1] = dom;
567
params[2] = 0;
568
} else {
569
// ZoneRulesBuilder adjusts < 0 case (-1, for last, don't have
570
// "<=" case yet) to positive value if not February (it appears
571
// we don't have February cutoff in tzdata table yet)
572
// Ideally, if JSR310 can just pass in the nagative and
573
// we can then pass in the dom = -1, dow > 0 into ZoneInfo
574
//
575
// hacking, assume the >=24 is the result of ZRB optimization for
576
// "last", it works for now. From tzdata2020d this hacking
577
// will not work for Asia/Gaza and Asia/Hebron which follow
578
// Palestine DST rules.
579
if (dom < 0 || dom >= 24 &&
580
!(zoneId.equals("Asia/Gaza") ||
581
zoneId.equals("Asia/Hebron"))) {
582
params[1] = -1;
583
params[2] = toCalendarDOW[dow];
584
} else {
585
params[1] = dom;
586
// To specify a day of week on or after an exact day of month,
587
// set the month to an exact month value, day-of-month to the
588
// day on or after which the rule is applied, and day-of-week
589
// to a negative Calendar.DAY_OF_WEEK DAY_OF_WEEK field value.
590
params[2] = -toCalendarDOW[dow];
591
}
592
}
593
params[3] = startRule.secondOfDay * 1000;
594
params[4] = toSTZTime[startRule.timeDefinition];
595
params[5] = endRule.month - 1;
596
dom = endRule.dom;
597
dow = endRule.dow;
598
if (dow == -1) {
599
params[6] = dom;
600
params[7] = 0;
601
} else {
602
// hacking: see comment above
603
if (dom < 0 || dom >= 24 &&
604
!(zoneId.equals("Asia/Gaza") ||
605
zoneId.equals("Asia/Hebron"))) {
606
params[6] = -1;
607
params[7] = toCalendarDOW[dow];
608
} else {
609
params[6] = dom;
610
params[7] = -toCalendarDOW[dow];
611
}
612
}
613
params[8] = endRule.secondOfDay * 1000;
614
params[9] = toSTZTime[endRule.timeDefinition];
615
dstSavings = (startRule.offsetAfter - startRule.offsetBefore) * 1000;
616
617
// Note: known mismatching -> Asia/Amman
618
// ZoneInfo : startDayOfWeek=5 <= Thursday
619
// startTime=86400000 <= 24 hours
620
// This: startDayOfWeek=6
621
// startTime=0
622
// Similar workaround needs to be applied to Africa/Cairo and
623
// its endDayOfWeek and endTime
624
// Below is the workarounds, it probably slows down everyone a little
625
if (params[2] == 6 && params[3] == 0 &&
626
(zoneId.equals("Asia/Amman"))) {
627
params[2] = 5;
628
params[3] = 86400000;
629
}
630
// Additional check for startDayOfWeek=6 and starTime=86400000
631
// is needed for Asia/Amman;
632
if (params[2] == 7 && params[3] == 0 &&
633
(zoneId.equals("Asia/Amman"))) {
634
params[2] = 6; // Friday
635
params[3] = 86400000; // 24h
636
}
637
//endDayOfWeek and endTime workaround
638
if (params[7] == 6 && params[8] == 0 &&
639
(zoneId.equals("Africa/Cairo"))) {
640
params[7] = 5;
641
params[8] = 86400000;
642
}
643
644
} else if (nTrans > 0) { // only do this if there is something in table already
645
if (lastyear < LASTYEAR) {
646
// ZoneInfo has an ending entry for 2037
647
//long trans = OffsetDateTime.of(LASTYEAR, 1, 1, 0, 0, 0, 0,
648
// ZoneOffset.ofTotalSeconds(rawOffset/1000))
649
// .toEpochSecond();
650
long trans = LDT2037 - rawOffset/1000;
651
652
int offsetIndex = indexOf(offsets, 0, nOffsets, rawOffset/1000);
653
if (offsetIndex == nOffsets)
654
nOffsets++;
655
transitions[nTrans++] = (trans * 1000) << TRANSITION_NSHIFT |
656
(offsetIndex & OFFSET_MASK);
657
658
} else if (savingsInstantTransitions.length > 2) {
659
// Workaround: create the params based on the last pair for
660
// zones like Israel and Iran which have trans defined
661
// up until 2037, but no "transition rule" defined
662
//
663
// Note: Known mismatching for Israel, Asia/Jerusalem/Tel Aviv
664
// ZoneInfo: startMode=3
665
// startMonth=2
666
// startDay=26
667
// startDayOfWeek=6
668
//
669
// This: startMode=1
670
// startMonth=2
671
// startDay=27
672
// startDayOfWeek=0
673
// these two are actually the same for 2037, the SimpleTimeZone
674
// for the last "known" year
675
int m = savingsInstantTransitions.length;
676
long startTrans = savingsInstantTransitions[m - 2];
677
int startOffset = wallOffsets[m - 2 + 1];
678
int startStd = getStandardOffset(standardTransitions, standardOffsets, startTrans);
679
long endTrans = savingsInstantTransitions[m - 1];
680
int endOffset = wallOffsets[m - 1 + 1];
681
int endStd = getStandardOffset(standardTransitions, standardOffsets, endTrans);
682
if (startOffset > startStd && endOffset == endStd) {
683
// last - 1 trans
684
m = savingsInstantTransitions.length - 2;
685
ZoneOffset before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);
686
ZoneOffset after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);
687
LocalDateTime ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);
688
LocalDateTime startLDT;
689
if (after.getTotalSeconds() > before.getTotalSeconds()) { // isGap()
690
startLDT = ldt;
691
} else {
692
startLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);
693
}
694
// last trans
695
m = savingsInstantTransitions.length - 1;
696
before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);
697
after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);
698
ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);
699
LocalDateTime endLDT;
700
if (after.getTotalSeconds() > before.getTotalSeconds()) { // isGap()
701
endLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);
702
} else {
703
endLDT = ldt;
704
}
705
params = new int[10];
706
params[0] = startLDT.getMonthValue() - 1;
707
params[1] = startLDT.getDayOfMonth();
708
params[2] = 0;
709
params[3] = startLDT.toLocalTime().toSecondOfDay() * 1000;
710
params[4] = SimpleTimeZone.WALL_TIME;
711
params[5] = endLDT.getMonthValue() - 1;
712
params[6] = endLDT.getDayOfMonth();
713
params[7] = 0;
714
params[8] = endLDT.toLocalTime().toSecondOfDay() * 1000;
715
params[9] = SimpleTimeZone.WALL_TIME;
716
dstSavings = (startOffset - startStd) * 1000;
717
}
718
}
719
}
720
if (transitions != null && transitions.length != nTrans) {
721
if (nTrans == 0) {
722
transitions = null;
723
} else {
724
transitions = Arrays.copyOf(transitions, nTrans);
725
}
726
}
727
if (offsets != null && offsets.length != nOffsets) {
728
if (nOffsets == 0) {
729
offsets = null;
730
} else {
731
offsets = Arrays.copyOf(offsets, nOffsets);
732
}
733
}
734
if (transitions != null) {
735
Checksum sum = new Checksum();
736
for (i = 0; i < transitions.length; i++) {
737
long val = transitions[i];
738
int dst = (int)((val >>> DST_NSHIFT) & 0xfL);
739
int saving = (dst == 0) ? 0 : offsets[dst];
740
int index = (int)(val & OFFSET_MASK);
741
int offset = offsets[index];
742
long second = (val >> TRANSITION_NSHIFT);
743
// javazic uses "index of the offset in offsets",
744
// instead of the real offset value itself to
745
// calculate the checksum. Have to keep doing
746
// the same thing, checksum is part of the
747
// ZoneInfo serialization form.
748
sum.update(second + index);
749
sum.update(index);
750
sum.update(dst == 0 ? -1 : dst);
751
}
752
checksum = (int)sum.getValue();
753
}
754
}
755
return new ZoneInfo(zoneId, rawOffset, dstSavings, checksum, transitions,
756
offsets, params, willGMTOffsetChange);
757
}
758
759
private static int getStandardOffset(long[] standardTransitions,
760
int[] standardOffsets,
761
long epochSec) {
762
// The size of stdOffsets is [0..9], with most are
763
// [1..4] entries , simple loop search is faster
764
//
765
// int index = Arrays.binarySearch(standardTransitions, epochSec);
766
// if (index < 0) {
767
// // switch negative insert position to start of matched range
768
// index = -index - 2;
769
// }
770
// return standardOffsets[index + 1];
771
int index = 0;
772
for (; index < standardTransitions.length; index++) {
773
if (epochSec < standardTransitions[index]) {
774
break;
775
}
776
}
777
return standardOffsets[index];
778
}
779
780
static final int SECONDS_PER_DAY = 86400;
781
static final int DAYS_PER_CYCLE = 146097;
782
static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);
783
784
private static int getYear(long epochSecond, int offset) {
785
long second = epochSecond + offset; // overflow caught later
786
long epochDay = Math.floorDiv(second, SECONDS_PER_DAY);
787
long zeroDay = epochDay + DAYS_0000_TO_1970;
788
// find the march-based year
789
zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle
790
long adjust = 0;
791
if (zeroDay < 0) {
792
// adjust negative years to positive for calculation
793
long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
794
adjust = adjustCycles * 400;
795
zeroDay += -adjustCycles * DAYS_PER_CYCLE;
796
}
797
long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
798
long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
799
if (doyEst < 0) {
800
// fix estimate
801
yearEst--;
802
doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
803
}
804
yearEst += adjust; // reset any negative year
805
int marchDoy0 = (int) doyEst;
806
// convert march-based values back to january-based
807
int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
808
int month = (marchMonth0 + 2) % 12 + 1;
809
int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
810
yearEst += marchMonth0 / 10;
811
return (int)yearEst;
812
}
813
814
private static final int toCalendarDOW[] = new int[] {
815
-1,
816
Calendar.MONDAY,
817
Calendar.TUESDAY,
818
Calendar.WEDNESDAY,
819
Calendar.THURSDAY,
820
Calendar.FRIDAY,
821
Calendar.SATURDAY,
822
Calendar.SUNDAY
823
};
824
825
private static final int toSTZTime[] = new int[] {
826
SimpleTimeZone.UTC_TIME,
827
SimpleTimeZone.WALL_TIME,
828
SimpleTimeZone.STANDARD_TIME,
829
};
830
831
private static final long OFFSET_MASK = 0x0fL;
832
private static final long DST_MASK = 0xf0L;
833
private static final int DST_NSHIFT = 4;
834
private static final int TRANSITION_NSHIFT = 12;
835
private static final int LASTYEAR = 2037;
836
837
// from: 0 for offset lookup, 1 for dstsvings lookup
838
private static int indexOf(int[] offsets, int from, int nOffsets, int offset) {
839
offset *= 1000;
840
for (; from < nOffsets; from++) {
841
if (offsets[from] == offset)
842
return from;
843
}
844
offsets[from] = offset;
845
return from;
846
}
847
848
// return updated nOffsets
849
private static int addTrans(long transitions[], int nTrans,
850
int offsets[], int nOffsets,
851
long trans, int offset, int stdOffset) {
852
int offsetIndex = indexOf(offsets, 0, nOffsets, offset);
853
if (offsetIndex == nOffsets)
854
nOffsets++;
855
int dstIndex = 0;
856
if (offset != stdOffset) {
857
dstIndex = indexOf(offsets, 1, nOffsets, offset - stdOffset);
858
if (dstIndex == nOffsets)
859
nOffsets++;
860
}
861
transitions[nTrans] = ((trans * 1000) << TRANSITION_NSHIFT) |
862
((dstIndex << DST_NSHIFT) & DST_MASK) |
863
(offsetIndex & OFFSET_MASK);
864
return nOffsets;
865
}
866
867
// ZoneInfo checksum, copy/pasted from javazic
868
private static class Checksum extends CRC32 {
869
public void update(int val) {
870
byte[] b = new byte[4];
871
b[0] = (byte)(val >>> 24);
872
b[1] = (byte)(val >>> 16);
873
b[2] = (byte)(val >>> 8);
874
b[3] = (byte)(val);
875
update(b);
876
}
877
void update(long val) {
878
byte[] b = new byte[8];
879
b[0] = (byte)(val >>> 56);
880
b[1] = (byte)(val >>> 48);
881
b[2] = (byte)(val >>> 40);
882
b[3] = (byte)(val >>> 32);
883
b[4] = (byte)(val >>> 24);
884
b[5] = (byte)(val >>> 16);
885
b[6] = (byte)(val >>> 8);
886
b[7] = (byte)(val);
887
update(b);
888
}
889
}
890
891
// A simple/raw version of j.t.ZoneOffsetTransitionRule
892
private static class ZoneOffsetTransitionRule {
893
private final int month;
894
private final byte dom;
895
private final int dow;
896
private final int secondOfDay;
897
private final boolean timeEndOfDay;
898
private final int timeDefinition;
899
private final int standardOffset;
900
private final int offsetBefore;
901
private final int offsetAfter;
902
903
ZoneOffsetTransitionRule(DataInput in) throws IOException {
904
int data = in.readInt();
905
int dowByte = (data & (7 << 19)) >>> 19;
906
int timeByte = (data & (31 << 14)) >>> 14;
907
int stdByte = (data & (255 << 4)) >>> 4;
908
int beforeByte = (data & (3 << 2)) >>> 2;
909
int afterByte = (data & 3);
910
911
this.month = data >>> 28;
912
this.dom = (byte)(((data & (63 << 22)) >>> 22) - 32);
913
this.dow = dowByte == 0 ? -1 : dowByte;
914
this.secondOfDay = timeByte == 31 ? in.readInt() : timeByte * 3600;
915
this.timeEndOfDay = timeByte == 24;
916
this.timeDefinition = (data & (3 << 12)) >>> 12;
917
918
this.standardOffset = stdByte == 255 ? in.readInt() : (stdByte - 128) * 900;
919
this.offsetBefore = beforeByte == 3 ? in.readInt() : standardOffset + beforeByte * 1800;
920
this.offsetAfter = afterByte == 3 ? in.readInt() : standardOffset + afterByte * 1800;
921
}
922
923
long getTransitionEpochSecond(int year) {
924
long epochDay = 0;
925
if (dom < 0) {
926
epochDay = toEpochDay(year, month, lengthOfMonth(year, month) + 1 + dom);
927
if (dow != -1) {
928
epochDay = previousOrSame(epochDay, dow);
929
}
930
} else {
931
epochDay = toEpochDay(year, month, dom);
932
if (dow != -1) {
933
epochDay = nextOrSame(epochDay, dow);
934
}
935
}
936
if (timeEndOfDay) {
937
epochDay += 1;
938
}
939
int difference = 0;
940
switch (timeDefinition) {
941
case 0: // UTC
942
difference = 0;
943
break;
944
case 1: // WALL
945
difference = -offsetBefore;
946
break;
947
case 2: //STANDARD
948
difference = -standardOffset;
949
break;
950
}
951
return epochDay * 86400 + secondOfDay + difference;
952
}
953
954
static final boolean isLeapYear(int year) {
955
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
956
}
957
958
static final int lengthOfMonth(int year, int month) {
959
switch (month) {
960
case 2: //FEBRUARY:
961
return isLeapYear(year)? 29 : 28;
962
case 4: //APRIL:
963
case 6: //JUNE:
964
case 9: //SEPTEMBER:
965
case 11: //NOVEMBER:
966
return 30;
967
default:
968
return 31;
969
}
970
}
971
972
static final long toEpochDay(int year, int month, int day) {
973
long y = year;
974
long m = month;
975
long total = 0;
976
total += 365 * y;
977
if (y >= 0) {
978
total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;
979
} else {
980
total -= y / -4 - y / -100 + y / -400;
981
}
982
total += ((367 * m - 362) / 12);
983
total += day - 1;
984
if (m > 2) {
985
total--;
986
if (!isLeapYear(year)) {
987
total--;
988
}
989
}
990
return total - DAYS_0000_TO_1970;
991
}
992
993
static final long previousOrSame(long epochDay, int dayOfWeek) {
994
return adjust(epochDay, dayOfWeek, 1);
995
}
996
997
static final long nextOrSame(long epochDay, int dayOfWeek) {
998
return adjust(epochDay, dayOfWeek, 0);
999
}
1000
1001
static final long adjust(long epochDay, int dow, int relative) {
1002
int calDow = (int)Math.floorMod(epochDay + 3, 7L) + 1;
1003
if (relative < 2 && calDow == dow) {
1004
return epochDay;
1005
}
1006
if ((relative & 1) == 0) {
1007
int daysDiff = calDow - dow;
1008
return epochDay + (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);
1009
} else {
1010
int daysDiff = dow - calDow;
1011
return epochDay - (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);
1012
}
1013
}
1014
}
1015
}
1016
1017