Path: blob/master/src/java.base/share/classes/sun/util/calendar/ZoneInfoFile.java
41159 views
/*1* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.util.calendar;2627import java.io.ByteArrayInputStream;28import java.io.BufferedInputStream;29import java.io.DataInput;30import java.io.DataInputStream;31import java.io.File;32import java.io.FileInputStream;33import java.io.IOException;34import java.io.StreamCorruptedException;35import java.security.AccessController;36import java.security.PrivilegedAction;37import java.time.LocalDateTime;38import java.time.ZoneOffset;39import java.util.ArrayList;40import java.util.Arrays;41import java.util.Calendar;42import java.util.Collections;43import java.util.HashMap;44import java.util.List;45import java.util.Locale;46import java.util.Map;47import java.util.SimpleTimeZone;48import java.util.concurrent.ConcurrentHashMap;49import java.util.zip.CRC32;5051import jdk.internal.util.StaticProperty;52import sun.security.action.GetPropertyAction;5354/**55* Loads TZDB time-zone rules for j.u.TimeZone56* <p>57* @since 1.858*/59@SuppressWarnings("removal")60public final class ZoneInfoFile {6162/**63* Gets all available IDs supported in the Java run-time.64*65* @return a set of time zone IDs.66*/67public static String[] getZoneIds() {68int len = regions.length + oldMappings.length;69if (!USE_OLDMAPPING) {70len += 3; // EST/HST/MST not in tzdb.dat71}72String[] ids = Arrays.copyOf(regions, len);73int i = regions.length;74if (!USE_OLDMAPPING) {75ids[i++] = "EST";76ids[i++] = "HST";77ids[i++] = "MST";78}79for (int j = 0; j < oldMappings.length; j++) {80ids[i++] = oldMappings[j][0];81}82return ids;83}8485/**86* Gets all available IDs that have the same value as the87* specified raw GMT offset.88*89* @param rawOffset the GMT offset in milliseconds. This90* value should not include any daylight saving time.91* @return an array of time zone IDs.92*/93public static String[] getZoneIds(int rawOffset) {94List<String> ids = new ArrayList<>();95for (String id : getZoneIds()) {96ZoneInfo zi = getZoneInfo(id);97if (zi.getRawOffset() == rawOffset) {98ids.add(id);99}100}101// It appears the "zi" implementation returns the102// sorted list, though the specification does not103// specify it. Keep the same behavior for better104// compatibility.105String[] list = ids.toArray(new String[ids.size()]);106Arrays.sort(list);107return list;108}109110public static ZoneInfo getZoneInfo(String zoneId) {111if (zoneId == null) {112return null;113}114ZoneInfo zi = getZoneInfo0(zoneId);115if (zi != null) {116zi = (ZoneInfo)zi.clone();117zi.setID(zoneId);118}119return zi;120}121122private static ZoneInfo getZoneInfo0(String zoneId) {123try {124ZoneInfo zi = zones.get(zoneId);125if (zi != null) {126return zi;127}128String zid = zoneId;129if (aliases.containsKey(zoneId)) {130zid = aliases.get(zoneId);131}132int index = Arrays.binarySearch(regions, zid);133if (index < 0) {134return null;135}136byte[] bytes = ruleArray[indices[index]];137DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));138zi = getZoneInfo(dis, zid);139zones.put(zoneId, zi);140return zi;141} catch (Exception ex) {142throw new RuntimeException("Invalid binary time-zone data: TZDB:" +143zoneId + ", version: " + versionId, ex);144}145}146147/**148* Returns a Map from alias time zone IDs to their standard149* time zone IDs.150*151* @return an unmodified alias mapping152*/153public static Map<String, String> getAliasMap() {154return Collections.unmodifiableMap(aliases);155}156157/**158* Gets the version of this tz data.159*160* @return the tzdb version161*/162public static String getVersion() {163return versionId;164}165166/**167* Gets a ZoneInfo with the given GMT offset. The object168* 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 offset173*/174public static ZoneInfo getCustomTimeZone(String originalId, int gmtOffset) {175String id = toCustomID(gmtOffset);176return new ZoneInfo(id, gmtOffset);177}178179public static String toCustomID(int gmtOffset) {180char sign;181int offset = gmtOffset / 60000;182if (offset >= 0) {183sign = '+';184} else {185sign = '-';186offset = -offset;187}188int hh = offset / 60;189int mm = offset % 60;190191char[] buf = new char[] { 'G', 'M', 'T', sign, '0', '0', ':', '0', '0' };192if (hh >= 10) {193buf[4] += hh / 10;194}195buf[5] += hh % 10;196if (mm != 0) {197buf[7] += mm / 10;198buf[8] += mm % 10;199}200return new String(buf);201}202203///////////////////////////////////////////////////////////204private ZoneInfoFile() {205}206207private static String versionId;208private static final Map<String, ZoneInfo> zones = new ConcurrentHashMap<>();209private static Map<String, String> aliases = new HashMap<>();210211private static byte[][] ruleArray;212private static String[] regions;213private static int[] indices;214215// Flag for supporting JDK backward compatible IDs, such as "EST".216private static final boolean USE_OLDMAPPING;217218private 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};245246static {247String oldmapping = GetPropertyAction248.privilegedGetProperty("sun.timezone.ids.oldmapping", "false")249.toLowerCase(Locale.ROOT);250USE_OLDMAPPING = (oldmapping.equals("yes") || oldmapping.equals("true"));251AccessController.doPrivileged(new PrivilegedAction<Void>() {252public Void run() {253try {254String libDir = StaticProperty.javaHome() + File.separator + "lib";255try (DataInputStream dis = new DataInputStream(256new BufferedInputStream(new FileInputStream(257new File(libDir, "tzdb.dat"))))) {258load(dis);259}260} catch (Exception x) {261throw new Error(x);262}263return null;264}265});266}267268private static void addOldMapping() {269for (String[] alias : oldMappings) {270aliases.put(alias[0], alias[1]);271}272if (USE_OLDMAPPING) {273aliases.put("EST", "America/New_York");274aliases.put("MST", "America/Denver");275aliases.put("HST", "Pacific/Honolulu");276} else {277zones.put("EST", new ZoneInfo("EST", -18000000));278zones.put("MST", new ZoneInfo("MST", -25200000));279zones.put("HST", new ZoneInfo("HST", -36000000));280}281}282283public static boolean useOldMapping() {284return USE_OLDMAPPING;285}286287/**288* Loads the rules from a DateInputStream289*290* @param dis the DateInputStream to load, not null291* @throws Exception if an error occurs292*/293private static void load(DataInputStream dis) throws ClassNotFoundException, IOException {294if (dis.readByte() != 1) {295throw new StreamCorruptedException("File format not recognised");296}297// group298String groupId = dis.readUTF();299if ("TZDB".equals(groupId) == false) {300throw new StreamCorruptedException("File format not recognised");301}302// versions, only keep the last one303int versionCount = dis.readShort();304for (int i = 0; i < versionCount; i++) {305versionId = dis.readUTF();306307}308// regions309int regionCount = dis.readShort();310String[] regionArray = new String[regionCount];311for (int i = 0; i < regionCount; i++) {312regionArray[i] = dis.readUTF();313}314// rules315int ruleCount = dis.readShort();316ruleArray = new byte[ruleCount][];317for (int i = 0; i < ruleCount; i++) {318byte[] bytes = new byte[dis.readShort()];319dis.readFully(bytes);320ruleArray[i] = bytes;321}322// link version-region-rules, only keep the last version, if more than one323for (int i = 0; i < versionCount; i++) {324regionCount = dis.readShort();325regions = new String[regionCount];326indices = new int[regionCount];327for (int j = 0; j < regionCount; j++) {328regions[j] = regionArray[dis.readShort()];329indices[j] = dis.readShort();330}331}332// remove the following ids from the map, they333// are exclued from the "old" ZoneInfo334zones.remove("ROC");335for (int i = 0; i < versionCount; i++) {336int aliasCount = dis.readShort();337aliases.clear();338for (int j = 0; j < aliasCount; j++) {339String alias = regionArray[dis.readShort()];340String region = regionArray[dis.readShort()];341aliases.put(alias, region);342}343}344// old us time-zone names345addOldMapping();346}347348/////////////////////////Ser/////////////////////////////////349public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception {350byte type = in.readByte();351// TBD: assert ZRULES:352int stdSize = in.readInt();353long[] stdTrans = new long[stdSize];354for (int i = 0; i < stdSize; i++) {355stdTrans[i] = readEpochSec(in);356}357int [] stdOffsets = new int[stdSize + 1];358for (int i = 0; i < stdOffsets.length; i++) {359stdOffsets[i] = readOffset(in);360}361int savSize = in.readInt();362long[] savTrans = new long[savSize];363for (int i = 0; i < savSize; i++) {364savTrans[i] = readEpochSec(in);365}366int[] savOffsets = new int[savSize + 1];367for (int i = 0; i < savOffsets.length; i++) {368savOffsets[i] = readOffset(in);369}370int ruleSize = in.readByte();371ZoneOffsetTransitionRule[] rules = new ZoneOffsetTransitionRule[ruleSize];372for (int i = 0; i < ruleSize; i++) {373rules[i] = new ZoneOffsetTransitionRule(in);374}375return getZoneInfo(zoneId, stdTrans, stdOffsets, savTrans, savOffsets, rules);376}377378public static int readOffset(DataInput in) throws IOException {379int offsetByte = in.readByte();380return offsetByte == 127 ? in.readInt() : offsetByte * 900;381}382383static long readEpochSec(DataInput in) throws IOException {384int hiByte = in.readByte() & 255;385if (hiByte == 255) {386return in.readLong();387} else {388int midByte = in.readByte() & 255;389int loByte = in.readByte() & 255;390long tot = ((hiByte << 16) + (midByte << 8) + loByte);391return (tot * 900) - 4575744000L;392}393}394395/////////////////////////ZoneRules --> ZoneInfo/////////////////////////////////396397// ZoneInfo starts with UTC1900398private static final long UTC1900 = -2208988800L;399400// ZoneInfo ends with UTC2037401// LocalDateTime.of(2038, 1, 1, 0, 0, 0).toEpochSecond(ZoneOffset.UTC) - 1;402private static final long UTC2037 = 2145916799L;403404// ZoneInfo has an ending entry for 2037, this need to be offset by405// a "rawOffset"406// LocalDateTime.of(2037, 1, 1, 0, 0, 0).toEpochSecond(ZoneOffset.UTC));407private static final long LDT2037 = 2114380800L;408409//Current time. Used to determine future GMToffset transitions410private static final long CURRT = System.currentTimeMillis()/1000;411412/* Get a ZoneInfo instance.413*414* @param standardTransitions the standard transitions, not null415* @param standardOffsets the standard offsets, not null416* @param savingsInstantTransitions the standard transitions, not null417* @param wallOffsets the wall offsets, not null418* @param lastRules the recurring last rules, size 15 or less, not null419*/420private static ZoneInfo getZoneInfo(String zoneId,421long[] standardTransitions,422int[] standardOffsets,423long[] savingsInstantTransitions,424int[] wallOffsets,425ZoneOffsetTransitionRule[] lastRules) {426int rawOffset = 0;427int dstSavings = 0;428int checksum = 0;429int[] params = null;430boolean willGMTOffsetChange = false;431432// rawOffset, pick the last one433if (standardTransitions.length > 0) {434rawOffset = standardOffsets[standardOffsets.length - 1] * 1000;435willGMTOffsetChange = standardTransitions[standardTransitions.length - 1] > CURRT;436}437else438rawOffset = standardOffsets[0] * 1000;439440// transitions, offsets;441long[] transitions = null;442int[] offsets = null;443int nOffsets = 0;444int nTrans = 0;445446if (savingsInstantTransitions.length != 0) {447transitions = new long[250];448offsets = new int[100]; // TBD: ZoneInfo actually can't handle449// offsets.length > 16 (4-bit index limit)450// last year in trans table451// It should not matter to use before or after offset for year452int lastyear = getYear(savingsInstantTransitions[savingsInstantTransitions.length - 1],453wallOffsets[savingsInstantTransitions.length - 1]);454int i = 0, k = 1;455while (i < savingsInstantTransitions.length &&456savingsInstantTransitions[i] < UTC1900) {457i++; // skip any date before UTC1900458}459if (i < savingsInstantTransitions.length) {460// javazic writes the last GMT offset into index 0!461if (i < savingsInstantTransitions.length) {462offsets[0] = standardOffsets[standardOffsets.length - 1] * 1000;463nOffsets = 1;464}465// ZoneInfo has a beginning entry for 1900.466// Only add it if this is not the only one in table467nOffsets = addTrans(transitions, nTrans++,468offsets, nOffsets,469UTC1900,470wallOffsets[i],471getStandardOffset(standardTransitions, standardOffsets, UTC1900));472}473474for (; i < savingsInstantTransitions.length; i++) {475long trans = savingsInstantTransitions[i];476if (trans > UTC2037) {477// no trans beyond LASTYEAR478lastyear = LASTYEAR;479break;480}481while (k < standardTransitions.length) {482// some standard offset transitions don't exist in483// savingInstantTrans, if the offset "change" doesn't484// really change the "effectiveWallOffset". For example485// the 1999/2000 pair in Zone Arg/Buenos_Aires, in which486// the daylightsaving "happened" but it actually does487// not result in the timezone switch. ZoneInfo however488// needs them in its transitions table489long trans_s = standardTransitions[k];490if (trans_s >= UTC1900) {491if (trans_s > trans)492break;493if (trans_s < trans) {494if (nOffsets + 2 >= offsets.length) {495offsets = Arrays.copyOf(offsets, offsets.length + 100);496}497if (nTrans + 1 >= transitions.length) {498transitions = Arrays.copyOf(transitions, transitions.length + 100);499}500nOffsets = addTrans(transitions, nTrans++, offsets, nOffsets,501trans_s,502wallOffsets[i],503standardOffsets[k+1]);504505}506}507k++;508}509if (nOffsets + 2 >= offsets.length) {510offsets = Arrays.copyOf(offsets, offsets.length + 100);511}512if (nTrans + 1 >= transitions.length) {513transitions = Arrays.copyOf(transitions, transitions.length + 100);514}515nOffsets = addTrans(transitions, nTrans++, offsets, nOffsets,516trans,517wallOffsets[i + 1],518getStandardOffset(standardTransitions, standardOffsets, trans));519520}521// append any leftover standard trans522while (k < standardTransitions.length) {523long trans = standardTransitions[k];524if (trans >= UTC1900) {525int offset = wallOffsets[i];526int offsetIndex = indexOf(offsets, 0, nOffsets, offset);527if (offsetIndex == nOffsets)528nOffsets++;529transitions[nTrans++] = ((trans * 1000) << TRANSITION_NSHIFT) |530(offsetIndex & OFFSET_MASK);531}532k++;533}534if (lastRules.length > 1) {535// fill the gap between the last trans until LASTYEAR536while (lastyear++ < LASTYEAR) {537for (ZoneOffsetTransitionRule zotr : lastRules) {538long trans = zotr.getTransitionEpochSecond(lastyear);539if (nOffsets + 2 >= offsets.length) {540offsets = Arrays.copyOf(offsets, offsets.length + 100);541}542if (nTrans + 1 >= transitions.length) {543transitions = Arrays.copyOf(transitions, transitions.length + 100);544}545nOffsets = addTrans(transitions, nTrans++,546offsets, nOffsets,547trans,548zotr.offsetAfter,549zotr.standardOffset);550}551}552ZoneOffsetTransitionRule startRule = lastRules[lastRules.length - 2];553ZoneOffsetTransitionRule endRule = lastRules[lastRules.length - 1];554params = new int[10];555if (startRule.offsetAfter - startRule.offsetBefore < 0 &&556endRule.offsetAfter - endRule.offsetBefore > 0) {557ZoneOffsetTransitionRule tmp;558tmp = startRule;559startRule = endRule;560endRule = tmp;561}562params[0] = startRule.month - 1;563int dom = startRule.dom;564int dow = startRule.dow;565if (dow == -1) {566params[1] = dom;567params[2] = 0;568} else {569// ZoneRulesBuilder adjusts < 0 case (-1, for last, don't have570// "<=" case yet) to positive value if not February (it appears571// we don't have February cutoff in tzdata table yet)572// Ideally, if JSR310 can just pass in the nagative and573// we can then pass in the dom = -1, dow > 0 into ZoneInfo574//575// hacking, assume the >=24 is the result of ZRB optimization for576// "last", it works for now. From tzdata2020d this hacking577// will not work for Asia/Gaza and Asia/Hebron which follow578// Palestine DST rules.579if (dom < 0 || dom >= 24 &&580!(zoneId.equals("Asia/Gaza") ||581zoneId.equals("Asia/Hebron"))) {582params[1] = -1;583params[2] = toCalendarDOW[dow];584} else {585params[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 the588// day on or after which the rule is applied, and day-of-week589// to a negative Calendar.DAY_OF_WEEK DAY_OF_WEEK field value.590params[2] = -toCalendarDOW[dow];591}592}593params[3] = startRule.secondOfDay * 1000;594params[4] = toSTZTime[startRule.timeDefinition];595params[5] = endRule.month - 1;596dom = endRule.dom;597dow = endRule.dow;598if (dow == -1) {599params[6] = dom;600params[7] = 0;601} else {602// hacking: see comment above603if (dom < 0 || dom >= 24 &&604!(zoneId.equals("Asia/Gaza") ||605zoneId.equals("Asia/Hebron"))) {606params[6] = -1;607params[7] = toCalendarDOW[dow];608} else {609params[6] = dom;610params[7] = -toCalendarDOW[dow];611}612}613params[8] = endRule.secondOfDay * 1000;614params[9] = toSTZTime[endRule.timeDefinition];615dstSavings = (startRule.offsetAfter - startRule.offsetBefore) * 1000;616617// Note: known mismatching -> Asia/Amman618// ZoneInfo : startDayOfWeek=5 <= Thursday619// startTime=86400000 <= 24 hours620// This: startDayOfWeek=6621// startTime=0622// Similar workaround needs to be applied to Africa/Cairo and623// its endDayOfWeek and endTime624// Below is the workarounds, it probably slows down everyone a little625if (params[2] == 6 && params[3] == 0 &&626(zoneId.equals("Asia/Amman"))) {627params[2] = 5;628params[3] = 86400000;629}630// Additional check for startDayOfWeek=6 and starTime=86400000631// is needed for Asia/Amman;632if (params[2] == 7 && params[3] == 0 &&633(zoneId.equals("Asia/Amman"))) {634params[2] = 6; // Friday635params[3] = 86400000; // 24h636}637//endDayOfWeek and endTime workaround638if (params[7] == 6 && params[8] == 0 &&639(zoneId.equals("Africa/Cairo"))) {640params[7] = 5;641params[8] = 86400000;642}643644} else if (nTrans > 0) { // only do this if there is something in table already645if (lastyear < LASTYEAR) {646// ZoneInfo has an ending entry for 2037647//long trans = OffsetDateTime.of(LASTYEAR, 1, 1, 0, 0, 0, 0,648// ZoneOffset.ofTotalSeconds(rawOffset/1000))649// .toEpochSecond();650long trans = LDT2037 - rawOffset/1000;651652int offsetIndex = indexOf(offsets, 0, nOffsets, rawOffset/1000);653if (offsetIndex == nOffsets)654nOffsets++;655transitions[nTrans++] = (trans * 1000) << TRANSITION_NSHIFT |656(offsetIndex & OFFSET_MASK);657658} else if (savingsInstantTransitions.length > 2) {659// Workaround: create the params based on the last pair for660// zones like Israel and Iran which have trans defined661// up until 2037, but no "transition rule" defined662//663// Note: Known mismatching for Israel, Asia/Jerusalem/Tel Aviv664// ZoneInfo: startMode=3665// startMonth=2666// startDay=26667// startDayOfWeek=6668//669// This: startMode=1670// startMonth=2671// startDay=27672// startDayOfWeek=0673// these two are actually the same for 2037, the SimpleTimeZone674// for the last "known" year675int m = savingsInstantTransitions.length;676long startTrans = savingsInstantTransitions[m - 2];677int startOffset = wallOffsets[m - 2 + 1];678int startStd = getStandardOffset(standardTransitions, standardOffsets, startTrans);679long endTrans = savingsInstantTransitions[m - 1];680int endOffset = wallOffsets[m - 1 + 1];681int endStd = getStandardOffset(standardTransitions, standardOffsets, endTrans);682if (startOffset > startStd && endOffset == endStd) {683// last - 1 trans684m = savingsInstantTransitions.length - 2;685ZoneOffset before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);686ZoneOffset after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);687LocalDateTime ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);688LocalDateTime startLDT;689if (after.getTotalSeconds() > before.getTotalSeconds()) { // isGap()690startLDT = ldt;691} else {692startLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);693}694// last trans695m = savingsInstantTransitions.length - 1;696before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);697after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);698ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);699LocalDateTime endLDT;700if (after.getTotalSeconds() > before.getTotalSeconds()) { // isGap()701endLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);702} else {703endLDT = ldt;704}705params = new int[10];706params[0] = startLDT.getMonthValue() - 1;707params[1] = startLDT.getDayOfMonth();708params[2] = 0;709params[3] = startLDT.toLocalTime().toSecondOfDay() * 1000;710params[4] = SimpleTimeZone.WALL_TIME;711params[5] = endLDT.getMonthValue() - 1;712params[6] = endLDT.getDayOfMonth();713params[7] = 0;714params[8] = endLDT.toLocalTime().toSecondOfDay() * 1000;715params[9] = SimpleTimeZone.WALL_TIME;716dstSavings = (startOffset - startStd) * 1000;717}718}719}720if (transitions != null && transitions.length != nTrans) {721if (nTrans == 0) {722transitions = null;723} else {724transitions = Arrays.copyOf(transitions, nTrans);725}726}727if (offsets != null && offsets.length != nOffsets) {728if (nOffsets == 0) {729offsets = null;730} else {731offsets = Arrays.copyOf(offsets, nOffsets);732}733}734if (transitions != null) {735Checksum sum = new Checksum();736for (i = 0; i < transitions.length; i++) {737long val = transitions[i];738int dst = (int)((val >>> DST_NSHIFT) & 0xfL);739int saving = (dst == 0) ? 0 : offsets[dst];740int index = (int)(val & OFFSET_MASK);741int offset = offsets[index];742long second = (val >> TRANSITION_NSHIFT);743// javazic uses "index of the offset in offsets",744// instead of the real offset value itself to745// calculate the checksum. Have to keep doing746// the same thing, checksum is part of the747// ZoneInfo serialization form.748sum.update(second + index);749sum.update(index);750sum.update(dst == 0 ? -1 : dst);751}752checksum = (int)sum.getValue();753}754}755return new ZoneInfo(zoneId, rawOffset, dstSavings, checksum, transitions,756offsets, params, willGMTOffsetChange);757}758759private static int getStandardOffset(long[] standardTransitions,760int[] standardOffsets,761long epochSec) {762// The size of stdOffsets is [0..9], with most are763// [1..4] entries , simple loop search is faster764//765// int index = Arrays.binarySearch(standardTransitions, epochSec);766// if (index < 0) {767// // switch negative insert position to start of matched range768// index = -index - 2;769// }770// return standardOffsets[index + 1];771int index = 0;772for (; index < standardTransitions.length; index++) {773if (epochSec < standardTransitions[index]) {774break;775}776}777return standardOffsets[index];778}779780static final int SECONDS_PER_DAY = 86400;781static final int DAYS_PER_CYCLE = 146097;782static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);783784private static int getYear(long epochSecond, int offset) {785long second = epochSecond + offset; // overflow caught later786long epochDay = Math.floorDiv(second, SECONDS_PER_DAY);787long zeroDay = epochDay + DAYS_0000_TO_1970;788// find the march-based year789zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle790long adjust = 0;791if (zeroDay < 0) {792// adjust negative years to positive for calculation793long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;794adjust = adjustCycles * 400;795zeroDay += -adjustCycles * DAYS_PER_CYCLE;796}797long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;798long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);799if (doyEst < 0) {800// fix estimate801yearEst--;802doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);803}804yearEst += adjust; // reset any negative year805int marchDoy0 = (int) doyEst;806// convert march-based values back to january-based807int marchMonth0 = (marchDoy0 * 5 + 2) / 153;808int month = (marchMonth0 + 2) % 12 + 1;809int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;810yearEst += marchMonth0 / 10;811return (int)yearEst;812}813814private static final int toCalendarDOW[] = new int[] {815-1,816Calendar.MONDAY,817Calendar.TUESDAY,818Calendar.WEDNESDAY,819Calendar.THURSDAY,820Calendar.FRIDAY,821Calendar.SATURDAY,822Calendar.SUNDAY823};824825private static final int toSTZTime[] = new int[] {826SimpleTimeZone.UTC_TIME,827SimpleTimeZone.WALL_TIME,828SimpleTimeZone.STANDARD_TIME,829};830831private static final long OFFSET_MASK = 0x0fL;832private static final long DST_MASK = 0xf0L;833private static final int DST_NSHIFT = 4;834private static final int TRANSITION_NSHIFT = 12;835private static final int LASTYEAR = 2037;836837// from: 0 for offset lookup, 1 for dstsvings lookup838private static int indexOf(int[] offsets, int from, int nOffsets, int offset) {839offset *= 1000;840for (; from < nOffsets; from++) {841if (offsets[from] == offset)842return from;843}844offsets[from] = offset;845return from;846}847848// return updated nOffsets849private static int addTrans(long transitions[], int nTrans,850int offsets[], int nOffsets,851long trans, int offset, int stdOffset) {852int offsetIndex = indexOf(offsets, 0, nOffsets, offset);853if (offsetIndex == nOffsets)854nOffsets++;855int dstIndex = 0;856if (offset != stdOffset) {857dstIndex = indexOf(offsets, 1, nOffsets, offset - stdOffset);858if (dstIndex == nOffsets)859nOffsets++;860}861transitions[nTrans] = ((trans * 1000) << TRANSITION_NSHIFT) |862((dstIndex << DST_NSHIFT) & DST_MASK) |863(offsetIndex & OFFSET_MASK);864return nOffsets;865}866867// ZoneInfo checksum, copy/pasted from javazic868private static class Checksum extends CRC32 {869public void update(int val) {870byte[] b = new byte[4];871b[0] = (byte)(val >>> 24);872b[1] = (byte)(val >>> 16);873b[2] = (byte)(val >>> 8);874b[3] = (byte)(val);875update(b);876}877void update(long val) {878byte[] b = new byte[8];879b[0] = (byte)(val >>> 56);880b[1] = (byte)(val >>> 48);881b[2] = (byte)(val >>> 40);882b[3] = (byte)(val >>> 32);883b[4] = (byte)(val >>> 24);884b[5] = (byte)(val >>> 16);885b[6] = (byte)(val >>> 8);886b[7] = (byte)(val);887update(b);888}889}890891// A simple/raw version of j.t.ZoneOffsetTransitionRule892private static class ZoneOffsetTransitionRule {893private final int month;894private final byte dom;895private final int dow;896private final int secondOfDay;897private final boolean timeEndOfDay;898private final int timeDefinition;899private final int standardOffset;900private final int offsetBefore;901private final int offsetAfter;902903ZoneOffsetTransitionRule(DataInput in) throws IOException {904int data = in.readInt();905int dowByte = (data & (7 << 19)) >>> 19;906int timeByte = (data & (31 << 14)) >>> 14;907int stdByte = (data & (255 << 4)) >>> 4;908int beforeByte = (data & (3 << 2)) >>> 2;909int afterByte = (data & 3);910911this.month = data >>> 28;912this.dom = (byte)(((data & (63 << 22)) >>> 22) - 32);913this.dow = dowByte == 0 ? -1 : dowByte;914this.secondOfDay = timeByte == 31 ? in.readInt() : timeByte * 3600;915this.timeEndOfDay = timeByte == 24;916this.timeDefinition = (data & (3 << 12)) >>> 12;917918this.standardOffset = stdByte == 255 ? in.readInt() : (stdByte - 128) * 900;919this.offsetBefore = beforeByte == 3 ? in.readInt() : standardOffset + beforeByte * 1800;920this.offsetAfter = afterByte == 3 ? in.readInt() : standardOffset + afterByte * 1800;921}922923long getTransitionEpochSecond(int year) {924long epochDay = 0;925if (dom < 0) {926epochDay = toEpochDay(year, month, lengthOfMonth(year, month) + 1 + dom);927if (dow != -1) {928epochDay = previousOrSame(epochDay, dow);929}930} else {931epochDay = toEpochDay(year, month, dom);932if (dow != -1) {933epochDay = nextOrSame(epochDay, dow);934}935}936if (timeEndOfDay) {937epochDay += 1;938}939int difference = 0;940switch (timeDefinition) {941case 0: // UTC942difference = 0;943break;944case 1: // WALL945difference = -offsetBefore;946break;947case 2: //STANDARD948difference = -standardOffset;949break;950}951return epochDay * 86400 + secondOfDay + difference;952}953954static final boolean isLeapYear(int year) {955return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);956}957958static final int lengthOfMonth(int year, int month) {959switch (month) {960case 2: //FEBRUARY:961return isLeapYear(year)? 29 : 28;962case 4: //APRIL:963case 6: //JUNE:964case 9: //SEPTEMBER:965case 11: //NOVEMBER:966return 30;967default:968return 31;969}970}971972static final long toEpochDay(int year, int month, int day) {973long y = year;974long m = month;975long total = 0;976total += 365 * y;977if (y >= 0) {978total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;979} else {980total -= y / -4 - y / -100 + y / -400;981}982total += ((367 * m - 362) / 12);983total += day - 1;984if (m > 2) {985total--;986if (!isLeapYear(year)) {987total--;988}989}990return total - DAYS_0000_TO_1970;991}992993static final long previousOrSame(long epochDay, int dayOfWeek) {994return adjust(epochDay, dayOfWeek, 1);995}996997static final long nextOrSame(long epochDay, int dayOfWeek) {998return adjust(epochDay, dayOfWeek, 0);999}10001001static final long adjust(long epochDay, int dow, int relative) {1002int calDow = (int)Math.floorMod(epochDay + 3, 7L) + 1;1003if (relative < 2 && calDow == dow) {1004return epochDay;1005}1006if ((relative & 1) == 0) {1007int daysDiff = calDow - dow;1008return epochDay + (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);1009} else {1010int daysDiff = dow - calDow;1011return epochDay - (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);1012}1013}1014}1015}101610171018