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