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