1 /*
   2  * Copyright (c) 2012, 2019, 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 public final class ZoneInfoFile {
  61 
  62     /**
  63      * Gets all available IDs supported in the Java run-time.
  64      *
  65      * @return a set of time zone IDs.
  66      */
  67     public static String[] getZoneIds() {
  68         int len = regions.length + oldMappings.length;
  69         if (!USE_OLDMAPPING) {
  70             len += 3;    // EST/HST/MST not in tzdb.dat
  71         }
  72         String[] ids = Arrays.copyOf(regions, len);
  73         int i = regions.length;
  74         if (!USE_OLDMAPPING) {
  75             ids[i++] = "EST";
  76             ids[i++] = "HST";
  77             ids[i++] = "MST";
  78         }
  79         for (int j = 0; j < oldMappings.length; j++) {
  80             ids[i++] = oldMappings[j][0];
  81         }
  82         return ids;
  83     }
  84 
  85     /**
  86      * Gets all available IDs that have the same value as the
  87      * specified raw GMT offset.
  88      *
  89      * @param rawOffset  the GMT offset in milliseconds. This
  90      *                   value should not include any daylight saving time.
  91      * @return an array of time zone IDs.
  92      */
  93     public static String[] getZoneIds(int rawOffset) {
  94         List<String> ids = new ArrayList<>();
  95         for (String id : getZoneIds()) {
  96             ZoneInfo zi = getZoneInfo(id);
  97             if (zi.getRawOffset() == rawOffset) {
  98                 ids.add(id);
  99             }
 100         }
 101         // It appears the "zi" implementation returns the
 102         // sorted list, though the specification does not
 103         // specify it. Keep the same behavior for better
 104         // compatibility.
 105         String[] list = ids.toArray(new String[ids.size()]);
 106         Arrays.sort(list);
 107         return list;
 108     }
 109 
 110     public static ZoneInfo getZoneInfo(String zoneId) {
 111         if (zoneId == null) {
 112             return null;
 113         }
 114         ZoneInfo zi = getZoneInfo0(zoneId);
 115         if (zi != null) {
 116             zi = (ZoneInfo)zi.clone();
 117             zi.setID(zoneId);
 118         }
 119         return zi;
 120     }
 121 
 122     private static ZoneInfo getZoneInfo0(String zoneId) {
 123         try {
 124             ZoneInfo zi = zones.get(zoneId);
 125             if (zi != null) {
 126                 return zi;
 127             }
 128             String zid = zoneId;
 129             if (aliases.containsKey(zoneId)) {
 130                 zid = aliases.get(zoneId);
 131             }
 132             int index = Arrays.binarySearch(regions, zid);
 133             if (index < 0) {
 134                 return null;
 135             }
 136             byte[] bytes = ruleArray[indices[index]];
 137             DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
 138             zi = getZoneInfo(dis, zid);
 139             zones.put(zoneId, zi);
 140             return zi;
 141         } catch (Exception ex) {
 142             throw new RuntimeException("Invalid binary time-zone data: TZDB:" +
 143                 zoneId + ", version: " + versionId, ex);
 144         }
 145     }
 146 
 147     /**
 148      * Returns a Map from alias time zone IDs to their standard
 149      * time zone IDs.
 150      *
 151      * @return an unmodified alias mapping
 152      */
 153     public static Map<String, String> getAliasMap() {
 154         return Collections.unmodifiableMap(aliases);
 155     }
 156 
 157     /**
 158      * Gets the version of this tz data.
 159      *
 160      * @return the tzdb version
 161      */
 162     public static String getVersion() {
 163         return versionId;
 164     }
 165 
 166     /**
 167      * Gets a ZoneInfo with the given GMT offset. The object
 168      * has its ID in the format of GMT{+|-}hh:mm.
 169      *
 170      * @param originalId  the given custom id (before normalized such as "GMT+9")
 171      * @param gmtOffset   GMT offset <em>in milliseconds</em>
 172      * @return a ZoneInfo constructed with the given GMT offset
 173      */
 174     public static ZoneInfo getCustomTimeZone(String originalId, int gmtOffset) {
 175         String id = toCustomID(gmtOffset);
 176         return new ZoneInfo(id, gmtOffset);
 177     }
 178 
 179     public static String toCustomID(int gmtOffset) {
 180         char sign;
 181         int offset = gmtOffset / 60000;
 182         if (offset >= 0) {
 183             sign = '+';
 184         } else {
 185             sign = '-';
 186             offset = -offset;
 187         }
 188         int hh = offset / 60;
 189         int mm = offset % 60;
 190 
 191         char[] buf = new char[] { 'G', 'M', 'T', sign, '0', '0', ':', '0', '0' };
 192         if (hh >= 10) {
 193             buf[4] += hh / 10;
 194         }
 195         buf[5] += hh % 10;
 196         if (mm != 0) {
 197             buf[7] += mm / 10;
 198             buf[8] += mm % 10;
 199         }
 200         return new String(buf);
 201     }
 202 
 203     ///////////////////////////////////////////////////////////
 204     private ZoneInfoFile() {
 205     }
 206 
 207     private static String versionId;
 208     private static final Map<String, ZoneInfo> zones = new ConcurrentHashMap<>();
 209     private static Map<String, String> aliases = new HashMap<>();
 210 
 211     private static byte[][] ruleArray;
 212     private static String[] regions;
 213     private static int[] indices;
 214 
 215     // Flag for supporting JDK backward compatible IDs, such as "EST".
 216     private static final boolean USE_OLDMAPPING;
 217 
 218     private static String[][] oldMappings = new String[][] {
 219         { "ACT", "Australia/Darwin" },
 220         { "AET", "Australia/Sydney" },
 221         { "AGT", "America/Argentina/Buenos_Aires" },
 222         { "ART", "Africa/Cairo" },
 223         { "AST", "America/Anchorage" },
 224         { "BET", "America/Sao_Paulo" },
 225         { "BST", "Asia/Dhaka" },
 226         { "CAT", "Africa/Harare" },
 227         { "CNT", "America/St_Johns" },
 228         { "CST", "America/Chicago" },
 229         { "CTT", "Asia/Shanghai" },
 230         { "EAT", "Africa/Addis_Ababa" },
 231         { "ECT", "Europe/Paris" },
 232         { "IET", "America/Indiana/Indianapolis" },
 233         { "IST", "Asia/Kolkata" },
 234         { "JST", "Asia/Tokyo" },
 235         { "MIT", "Pacific/Apia" },
 236         { "NET", "Asia/Yerevan" },
 237         { "NST", "Pacific/Auckland" },
 238         { "PLT", "Asia/Karachi" },
 239         { "PNT", "America/Phoenix" },
 240         { "PRT", "America/Puerto_Rico" },
 241         { "PST", "America/Los_Angeles" },
 242         { "SST", "Pacific/Guadalcanal" },
 243         { "VST", "Asia/Ho_Chi_Minh" },
 244     };
 245 
 246     static {
 247         String oldmapping = GetPropertyAction
 248                 .privilegedGetProperty("sun.timezone.ids.oldmapping", "false")
 249                 .toLowerCase(Locale.ROOT);
 250         USE_OLDMAPPING = (oldmapping.equals("yes") || oldmapping.equals("true"));
 251         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 252             public Void run() {
 253                 try {
 254                     String libDir = StaticProperty.javaHome() + 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 tzdata2019a 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                         params[6] = -1;
 605                         params[7] = toCalendarDOW[dow];
 606                     } else {
 607                         params[6] = dom;
 608                         params[7] = -toCalendarDOW[dow];
 609                     }
 610                 }
 611                 params[8] = endRule.secondOfDay * 1000;
 612                 params[9] = toSTZTime[endRule.timeDefinition];
 613                 dstSavings = (startRule.offsetAfter - startRule.offsetBefore) * 1000;
 614 
 615                 // Note: known mismatching -> Asia/Amman
 616                 //                            Asia/Gaza
 617                 //                            Asia/Hebron
 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                      zoneId.equals("Asia/Gaza") ||
 628                      zoneId.equals("Asia/Hebron"))) {
 629                     params[2] = 5;
 630                     params[3] = 86400000;
 631                 }
 632                 // Additional check for startDayOfWeek=6 and starTime=86400000
 633                 // is needed for Asia/Amman; Asia/Gasa and Asia/Hebron
 634                 if (params[2] == 7 && params[3] == 0 &&
 635                      (zoneId.equals("Asia/Amman") ||
 636                       zoneId.equals("Asia/Gaza") ||
 637                       zoneId.equals("Asia/Hebron"))) {
 638                     params[2] = 6;        // Friday
 639                     params[3] = 86400000; // 24h
 640                 }
 641                 //endDayOfWeek and endTime workaround
 642                 if (params[7] == 6 && params[8] == 0 &&
 643                     (zoneId.equals("Africa/Cairo"))) {
 644                     params[7] = 5;
 645                     params[8] = 86400000;
 646                 }
 647 
 648             } else if (nTrans > 0) {  // only do this if there is something in table already
 649                 if (lastyear < LASTYEAR) {
 650                     // ZoneInfo has an ending entry for 2037
 651                     //long trans = OffsetDateTime.of(LASTYEAR, 1, 1, 0, 0, 0, 0,
 652                     //                               ZoneOffset.ofTotalSeconds(rawOffset/1000))
 653                     //                           .toEpochSecond();
 654                     long trans = LDT2037 - rawOffset/1000;
 655 
 656                     int offsetIndex = indexOf(offsets, 0, nOffsets, rawOffset/1000);
 657                     if (offsetIndex == nOffsets)
 658                         nOffsets++;
 659                     transitions[nTrans++] = (trans * 1000) << TRANSITION_NSHIFT |
 660                                        (offsetIndex & OFFSET_MASK);
 661 
 662                 } else if (savingsInstantTransitions.length > 2) {
 663                     // Workaround: create the params based on the last pair for
 664                     // zones like Israel and Iran which have trans defined
 665                     // up until 2037, but no "transition rule" defined
 666                     //
 667                     // Note: Known mismatching for Israel, Asia/Jerusalem/Tel Aviv
 668                     // ZoneInfo:        startMode=3
 669                     //                  startMonth=2
 670                     //                  startDay=26
 671                     //                  startDayOfWeek=6
 672                     //
 673                     // This:            startMode=1
 674                     //                  startMonth=2
 675                     //                  startDay=27
 676                     //                  startDayOfWeek=0
 677                     // these two are actually the same for 2037, the SimpleTimeZone
 678                     // for the last "known" year
 679                     int m = savingsInstantTransitions.length;
 680                     long startTrans = savingsInstantTransitions[m - 2];
 681                     int startOffset = wallOffsets[m - 2 + 1];
 682                     int startStd = getStandardOffset(standardTransitions, standardOffsets, startTrans);
 683                     long endTrans =  savingsInstantTransitions[m - 1];
 684                     int endOffset = wallOffsets[m - 1 + 1];
 685                     int endStd = getStandardOffset(standardTransitions, standardOffsets, endTrans);
 686                     if (startOffset > startStd && endOffset == endStd) {
 687                         // last - 1 trans
 688                         m = savingsInstantTransitions.length - 2;
 689                         ZoneOffset before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);
 690                         ZoneOffset after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);
 691                         LocalDateTime ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);
 692                         LocalDateTime startLDT;
 693                         if (after.getTotalSeconds() > before.getTotalSeconds()) {  // isGap()
 694                             startLDT = ldt;
 695                         } else {
 696                             startLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);
 697                         }
 698                         // last trans
 699                         m = savingsInstantTransitions.length - 1;
 700                         before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);
 701                         after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);
 702                         ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);
 703                         LocalDateTime endLDT;
 704                         if (after.getTotalSeconds() > before.getTotalSeconds()) {  // isGap()
 705                             endLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);
 706                         } else {
 707                             endLDT = ldt;
 708                         }
 709                         params = new int[10];
 710                         params[0] = startLDT.getMonthValue() - 1;
 711                         params[1] = startLDT.getDayOfMonth();
 712                         params[2] = 0;
 713                         params[3] = startLDT.toLocalTime().toSecondOfDay() * 1000;
 714                         params[4] = SimpleTimeZone.WALL_TIME;
 715                         params[5] = endLDT.getMonthValue() - 1;
 716                         params[6] = endLDT.getDayOfMonth();
 717                         params[7] = 0;
 718                         params[8] = endLDT.toLocalTime().toSecondOfDay() * 1000;
 719                         params[9] = SimpleTimeZone.WALL_TIME;
 720                         dstSavings = (startOffset - startStd) * 1000;
 721                     }
 722                 }
 723             }
 724             if (transitions != null && transitions.length != nTrans) {
 725                 if (nTrans == 0) {
 726                     transitions = null;
 727                 } else {
 728                     transitions = Arrays.copyOf(transitions, nTrans);
 729                 }
 730             }
 731             if (offsets != null && offsets.length != nOffsets) {
 732                 if (nOffsets == 0) {
 733                     offsets = null;
 734                 } else {
 735                     offsets = Arrays.copyOf(offsets, nOffsets);
 736                 }
 737             }
 738             if (transitions != null) {
 739                 Checksum sum = new Checksum();
 740                 for (i = 0; i < transitions.length; i++) {
 741                     long val = transitions[i];
 742                     int dst = (int)((val >>> DST_NSHIFT) & 0xfL);
 743                     int saving = (dst == 0) ? 0 : offsets[dst];
 744                     int index = (int)(val & OFFSET_MASK);
 745                     int offset = offsets[index];
 746                     long second = (val >> TRANSITION_NSHIFT);
 747                     // javazic uses "index of the offset in offsets",
 748                     // instead of the real offset value itself to
 749                     // calculate the checksum. Have to keep doing
 750                     // the same thing, checksum is part of the
 751                     // ZoneInfo serialization form.
 752                     sum.update(second + index);
 753                     sum.update(index);
 754                     sum.update(dst == 0 ? -1 : dst);
 755                 }
 756                 checksum = (int)sum.getValue();
 757             }
 758         }
 759         return new ZoneInfo(zoneId, rawOffset, dstSavings, checksum, transitions,
 760                             offsets, params, willGMTOffsetChange);
 761     }
 762 
 763     private static int getStandardOffset(long[] standardTransitions,
 764                                          int[] standardOffsets,
 765                                          long epochSec) {
 766         // The size of stdOffsets is [0..9], with most are
 767         // [1..4] entries , simple loop search is faster
 768         //
 769         // int index  = Arrays.binarySearch(standardTransitions, epochSec);
 770         // if (index < 0) {
 771         //    // switch negative insert position to start of matched range
 772         //    index = -index - 2;
 773         // }
 774         // return standardOffsets[index + 1];
 775         int index = 0;
 776         for (; index < standardTransitions.length; index++) {
 777             if (epochSec < standardTransitions[index]) {
 778                 break;
 779             }
 780         }
 781         return standardOffsets[index];
 782     }
 783 
 784     static final int SECONDS_PER_DAY = 86400;
 785     static final int DAYS_PER_CYCLE = 146097;
 786     static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);
 787 
 788     private static int getYear(long epochSecond, int offset) {
 789         long second = epochSecond + offset;  // overflow caught later
 790         long epochDay = Math.floorDiv(second, SECONDS_PER_DAY);
 791         long zeroDay = epochDay + DAYS_0000_TO_1970;
 792         // find the march-based year
 793         zeroDay -= 60;  // adjust to 0000-03-01 so leap day is at end of four year cycle
 794         long adjust = 0;
 795         if (zeroDay < 0) {
 796             // adjust negative years to positive for calculation
 797             long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
 798             adjust = adjustCycles * 400;
 799             zeroDay += -adjustCycles * DAYS_PER_CYCLE;
 800         }
 801         long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
 802         long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
 803         if (doyEst < 0) {
 804             // fix estimate
 805             yearEst--;
 806             doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
 807         }
 808         yearEst += adjust;  // reset any negative year
 809         int marchDoy0 = (int) doyEst;
 810         // convert march-based values back to january-based
 811         int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
 812         int month = (marchMonth0 + 2) % 12 + 1;
 813         int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
 814         yearEst += marchMonth0 / 10;
 815         return (int)yearEst;
 816     }
 817 
 818     private static final int toCalendarDOW[] = new int[] {
 819         -1,
 820         Calendar.MONDAY,
 821         Calendar.TUESDAY,
 822         Calendar.WEDNESDAY,
 823         Calendar.THURSDAY,
 824         Calendar.FRIDAY,
 825         Calendar.SATURDAY,
 826         Calendar.SUNDAY
 827     };
 828 
 829     private static final int toSTZTime[] = new int[] {
 830         SimpleTimeZone.UTC_TIME,
 831         SimpleTimeZone.WALL_TIME,
 832         SimpleTimeZone.STANDARD_TIME,
 833     };
 834 
 835     private static final long OFFSET_MASK = 0x0fL;
 836     private static final long DST_MASK = 0xf0L;
 837     private static final int  DST_NSHIFT = 4;
 838     private static final int  TRANSITION_NSHIFT = 12;
 839     private static final int  LASTYEAR = 2037;
 840 
 841     // from: 0 for offset lookup, 1 for dstsvings lookup
 842     private static int indexOf(int[] offsets, int from, int nOffsets, int offset) {
 843         offset *= 1000;
 844         for (; from < nOffsets; from++) {
 845             if (offsets[from] == offset)
 846                 return from;
 847         }
 848         offsets[from] = offset;
 849         return from;
 850     }
 851 
 852     // return updated nOffsets
 853     private static int addTrans(long transitions[], int nTrans,
 854                                 int offsets[], int nOffsets,
 855                                 long trans, int offset, int stdOffset) {
 856         int offsetIndex = indexOf(offsets, 0, nOffsets, offset);
 857         if (offsetIndex == nOffsets)
 858             nOffsets++;
 859         int dstIndex = 0;
 860         if (offset != stdOffset) {
 861             dstIndex = indexOf(offsets, 1, nOffsets, offset - stdOffset);
 862             if (dstIndex == nOffsets)
 863                 nOffsets++;
 864         }
 865         transitions[nTrans] = ((trans * 1000) << TRANSITION_NSHIFT) |
 866                               ((dstIndex << DST_NSHIFT) & DST_MASK) |
 867                               (offsetIndex & OFFSET_MASK);
 868         return nOffsets;
 869     }
 870 
 871     // ZoneInfo checksum, copy/pasted from javazic
 872     private static class Checksum extends CRC32 {
 873         public void update(int val) {
 874             byte[] b = new byte[4];
 875             b[0] = (byte)(val >>> 24);
 876             b[1] = (byte)(val >>> 16);
 877             b[2] = (byte)(val >>> 8);
 878             b[3] = (byte)(val);
 879             update(b);
 880         }
 881         void update(long val) {
 882             byte[] b = new byte[8];
 883             b[0] = (byte)(val >>> 56);
 884             b[1] = (byte)(val >>> 48);
 885             b[2] = (byte)(val >>> 40);
 886             b[3] = (byte)(val >>> 32);
 887             b[4] = (byte)(val >>> 24);
 888             b[5] = (byte)(val >>> 16);
 889             b[6] = (byte)(val >>> 8);
 890             b[7] = (byte)(val);
 891             update(b);
 892         }
 893     }
 894 
 895     // A simple/raw version of j.t.ZoneOffsetTransitionRule
 896     private static class ZoneOffsetTransitionRule {
 897         private final int month;
 898         private final byte dom;
 899         private final int dow;
 900         private final int secondOfDay;
 901         private final boolean timeEndOfDay;
 902         private final int timeDefinition;
 903         private final int standardOffset;
 904         private final int offsetBefore;
 905         private final int offsetAfter;
 906 
 907         ZoneOffsetTransitionRule(DataInput in) throws IOException {
 908             int data = in.readInt();
 909             int dowByte = (data & (7 << 19)) >>> 19;
 910             int timeByte = (data & (31 << 14)) >>> 14;
 911             int stdByte = (data & (255 << 4)) >>> 4;
 912             int beforeByte = (data & (3 << 2)) >>> 2;
 913             int afterByte = (data & 3);
 914 
 915             this.month = data >>> 28;
 916             this.dom = (byte)(((data & (63 << 22)) >>> 22) - 32);
 917             this.dow = dowByte == 0 ? -1 : dowByte;
 918             this.secondOfDay = timeByte == 31 ? in.readInt() : timeByte * 3600;
 919             this.timeEndOfDay = timeByte == 24;
 920             this.timeDefinition = (data & (3 << 12)) >>> 12;
 921 
 922             this.standardOffset = stdByte == 255 ? in.readInt() : (stdByte - 128) * 900;
 923             this.offsetBefore = beforeByte == 3 ? in.readInt() : standardOffset + beforeByte * 1800;
 924             this.offsetAfter = afterByte == 3 ? in.readInt() : standardOffset + afterByte * 1800;
 925         }
 926 
 927         long getTransitionEpochSecond(int year) {
 928             long epochDay = 0;
 929             if (dom < 0) {
 930                 epochDay = toEpochDay(year, month, lengthOfMonth(year, month) + 1 + dom);
 931                 if (dow != -1) {
 932                     epochDay = previousOrSame(epochDay, dow);
 933                 }
 934             } else {
 935                 epochDay = toEpochDay(year, month, dom);
 936                 if (dow != -1) {
 937                     epochDay = nextOrSame(epochDay, dow);
 938                 }
 939             }
 940             if (timeEndOfDay) {
 941                 epochDay += 1;
 942             }
 943             int difference = 0;
 944             switch (timeDefinition) {
 945                 case 0:    // UTC
 946                     difference = 0;
 947                     break;
 948                 case 1:    // WALL
 949                     difference = -offsetBefore;
 950                     break;
 951                 case 2:    //STANDARD
 952                     difference = -standardOffset;
 953                     break;
 954             }
 955             return epochDay * 86400 + secondOfDay + difference;
 956         }
 957 
 958         static final boolean isLeapYear(int year) {
 959             return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
 960         }
 961 
 962         static final int lengthOfMonth(int year, int month) {
 963             switch (month) {
 964                 case 2:        //FEBRUARY:
 965                     return isLeapYear(year)? 29 : 28;
 966                 case 4:        //APRIL:
 967                 case 6:        //JUNE:
 968                 case 9:        //SEPTEMBER:
 969                 case 11:       //NOVEMBER:
 970                     return 30;
 971                 default:
 972                     return 31;
 973             }
 974         }
 975 
 976         static final long toEpochDay(int year, int month, int day) {
 977             long y = year;
 978             long m = month;
 979             long total = 0;
 980             total += 365 * y;
 981             if (y >= 0) {
 982                 total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;
 983             } else {
 984                 total -= y / -4 - y / -100 + y / -400;
 985             }
 986             total += ((367 * m - 362) / 12);
 987             total += day - 1;
 988             if (m > 2) {
 989                 total--;
 990                 if (!isLeapYear(year)) {
 991                     total--;
 992                 }
 993             }
 994             return total - DAYS_0000_TO_1970;
 995         }
 996 
 997         static final long previousOrSame(long epochDay, int dayOfWeek) {
 998             return adjust(epochDay, dayOfWeek, 1);
 999         }
1000 
1001         static final long nextOrSame(long epochDay, int dayOfWeek) {
1002            return adjust(epochDay, dayOfWeek, 0);
1003         }
1004 
1005         static final long adjust(long epochDay, int dow, int relative) {
1006             int calDow = (int)Math.floorMod(epochDay + 3, 7L) + 1;
1007             if (relative < 2 && calDow == dow) {
1008                 return epochDay;
1009             }
1010             if ((relative & 1) == 0) {
1011                 int daysDiff = calDow - dow;
1012                 return epochDay + (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);
1013             } else {
1014                 int daysDiff = dow - calDow;
1015                 return epochDay - (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);
1016             }
1017         }
1018     }
1019 }