Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/util/calendar/ZoneInfoFile.java
38918 views
/*1* Copyright (c) 2012, 2020, 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.Map.Entry;48import java.util.Objects;49import java.util.Set;50import java.util.SimpleTimeZone;51import java.util.concurrent.ConcurrentHashMap;52import java.util.zip.CRC32;53import sun.security.action.GetPropertyAction;5455/**56* Loads TZDB time-zone rules for j.u.TimeZone57* <p>58* @since 1.859*/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 final static 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 = AccessController.doPrivileged(248new GetPropertyAction("sun.timezone.ids.oldmapping", "false")).toLowerCase(Locale.ROOT);249USE_OLDMAPPING = (oldmapping.equals("yes") || oldmapping.equals("true"));250AccessController.doPrivileged(new PrivilegedAction<Object>() {251public Object run() {252try {253String libDir = System.getProperty("java.home") + File.separator + "lib";254try (DataInputStream dis = new DataInputStream(255new BufferedInputStream(new FileInputStream(256new File(libDir, "tzdb.dat"))))) {257load(dis);258}259} catch (Exception x) {260throw new Error(x);261}262return null;263}264});265}266267private static void addOldMapping() {268for (String[] alias : oldMappings) {269aliases.put(alias[0], alias[1]);270}271if (USE_OLDMAPPING) {272aliases.put("EST", "America/New_York");273aliases.put("MST", "America/Denver");274aliases.put("HST", "Pacific/Honolulu");275} else {276zones.put("EST", new ZoneInfo("EST", -18000000));277zones.put("MST", new ZoneInfo("MST", -25200000));278zones.put("HST", new ZoneInfo("HST", -36000000));279}280}281282public static boolean useOldMapping() {283return USE_OLDMAPPING;284}285286/**287* Loads the rules from a DateInputStream288*289* @param dis the DateInputStream to load, not null290* @throws Exception if an error occurs291*/292private static void load(DataInputStream dis) throws ClassNotFoundException, IOException {293if (dis.readByte() != 1) {294throw new StreamCorruptedException("File format not recognised");295}296// group297String groupId = dis.readUTF();298if ("TZDB".equals(groupId) == false) {299throw new StreamCorruptedException("File format not recognised");300}301// versions, only keep the last one302int versionCount = dis.readShort();303for (int i = 0; i < versionCount; i++) {304versionId = dis.readUTF();305306}307// regions308int regionCount = dis.readShort();309String[] regionArray = new String[regionCount];310for (int i = 0; i < regionCount; i++) {311regionArray[i] = dis.readUTF();312}313// rules314int ruleCount = dis.readShort();315ruleArray = new byte[ruleCount][];316for (int i = 0; i < ruleCount; i++) {317byte[] bytes = new byte[dis.readShort()];318dis.readFully(bytes);319ruleArray[i] = bytes;320}321// link version-region-rules, only keep the last version, if more than one322for (int i = 0; i < versionCount; i++) {323regionCount = dis.readShort();324regions = new String[regionCount];325indices = new int[regionCount];326for (int j = 0; j < regionCount; j++) {327regions[j] = regionArray[dis.readShort()];328indices[j] = dis.readShort();329}330}331// remove the following ids from the map, they332// are exclued from the "old" ZoneInfo333zones.remove("ROC");334for (int i = 0; i < versionCount; i++) {335int aliasCount = dis.readShort();336aliases.clear();337for (int j = 0; j < aliasCount; j++) {338String alias = regionArray[dis.readShort()];339String region = regionArray[dis.readShort()];340aliases.put(alias, region);341}342}343// old us time-zone names344addOldMapping();345}346347/////////////////////////Ser/////////////////////////////////348public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception {349byte type = in.readByte();350// TBD: assert ZRULES:351int stdSize = in.readInt();352long[] stdTrans = new long[stdSize];353for (int i = 0; i < stdSize; i++) {354stdTrans[i] = readEpochSec(in);355}356int [] stdOffsets = new int[stdSize + 1];357for (int i = 0; i < stdOffsets.length; i++) {358stdOffsets[i] = readOffset(in);359}360int savSize = in.readInt();361long[] savTrans = new long[savSize];362for (int i = 0; i < savSize; i++) {363savTrans[i] = readEpochSec(in);364}365int[] savOffsets = new int[savSize + 1];366for (int i = 0; i < savOffsets.length; i++) {367savOffsets[i] = readOffset(in);368}369int ruleSize = in.readByte();370ZoneOffsetTransitionRule[] rules = new ZoneOffsetTransitionRule[ruleSize];371for (int i = 0; i < ruleSize; i++) {372rules[i] = new ZoneOffsetTransitionRule(in);373}374return getZoneInfo(zoneId, stdTrans, stdOffsets, savTrans, savOffsets, rules);375}376377public static int readOffset(DataInput in) throws IOException {378int offsetByte = in.readByte();379return offsetByte == 127 ? in.readInt() : offsetByte * 900;380}381382static long readEpochSec(DataInput in) throws IOException {383int hiByte = in.readByte() & 255;384if (hiByte == 255) {385return in.readLong();386} else {387int midByte = in.readByte() & 255;388int loByte = in.readByte() & 255;389long tot = ((hiByte << 16) + (midByte << 8) + loByte);390return (tot * 900) - 4575744000L;391}392}393394/////////////////////////ZoneRules --> ZoneInfo/////////////////////////////////395396// ZoneInfo starts with UTC1900397private static final long UTC1900 = -2208988800L;398399// ZoneInfo ends with UTC2037400// LocalDateTime.of(2038, 1, 1, 0, 0, 0).toEpochSecond(ZoneOffset.UTC) - 1;401private static final long UTC2037 = 2145916799L;402403// ZoneInfo has an ending entry for 2037, this need to be offset by404// a "rawOffset"405// LocalDateTime.of(2037, 1, 1, 0, 0, 0).toEpochSecond(ZoneOffset.UTC));406private static final long LDT2037 = 2114380800L;407408//Current time. Used to determine future GMToffset transitions409private static final long CURRT = System.currentTimeMillis()/1000;410411/* Get a ZoneInfo instance.412*413* @param standardTransitions the standard transitions, not null414* @param standardOffsets the standard offsets, not null415* @param savingsInstantTransitions the standard transitions, not null416* @param wallOffsets the wall offsets, not null417* @param lastRules the recurring last rules, size 15 or less, not null418*/419private static ZoneInfo getZoneInfo(String zoneId,420long[] standardTransitions,421int[] standardOffsets,422long[] savingsInstantTransitions,423int[] wallOffsets,424ZoneOffsetTransitionRule[] lastRules) {425int rawOffset = 0;426int dstSavings = 0;427int checksum = 0;428int[] params = null;429boolean willGMTOffsetChange = false;430431// rawOffset, pick the last one432if (standardTransitions.length > 0) {433rawOffset = standardOffsets[standardOffsets.length - 1] * 1000;434willGMTOffsetChange = standardTransitions[standardTransitions.length - 1] > CURRT;435}436else437rawOffset = standardOffsets[0] * 1000;438439// transitions, offsets;440long[] transitions = null;441int[] offsets = null;442int nOffsets = 0;443int nTrans = 0;444445if (savingsInstantTransitions.length != 0) {446transitions = new long[250];447offsets = new int[100]; // TBD: ZoneInfo actually can't handle448// offsets.length > 16 (4-bit index limit)449// last year in trans table450// It should not matter to use before or after offset for year451int lastyear = getYear(savingsInstantTransitions[savingsInstantTransitions.length - 1],452wallOffsets[savingsInstantTransitions.length - 1]);453int i = 0, k = 1;454while (i < savingsInstantTransitions.length &&455savingsInstantTransitions[i] < UTC1900) {456i++; // skip any date before UTC1900457}458if (i < savingsInstantTransitions.length) {459// javazic writes the last GMT offset into index 0!460if (i < savingsInstantTransitions.length) {461offsets[0] = standardOffsets[standardOffsets.length - 1] * 1000;462nOffsets = 1;463}464// ZoneInfo has a beginning entry for 1900.465// Only add it if this is not the only one in table466nOffsets = addTrans(transitions, nTrans++,467offsets, nOffsets,468UTC1900,469wallOffsets[i],470getStandardOffset(standardTransitions, standardOffsets, UTC1900));471}472473for (; i < savingsInstantTransitions.length; i++) {474long trans = savingsInstantTransitions[i];475if (trans > UTC2037) {476// no trans beyond LASTYEAR477lastyear = LASTYEAR;478break;479}480while (k < standardTransitions.length) {481// some standard offset transitions don't exist in482// savingInstantTrans, if the offset "change" doesn't483// really change the "effectiveWallOffset". For example484// the 1999/2000 pair in Zone Arg/Buenos_Aires, in which485// the daylightsaving "happened" but it actually does486// not result in the timezone switch. ZoneInfo however487// needs them in its transitions table488long trans_s = standardTransitions[k];489if (trans_s >= UTC1900) {490if (trans_s > trans)491break;492if (trans_s < trans) {493if (nOffsets + 2 >= offsets.length) {494offsets = Arrays.copyOf(offsets, offsets.length + 100);495}496if (nTrans + 1 >= transitions.length) {497transitions = Arrays.copyOf(transitions, transitions.length + 100);498}499nOffsets = addTrans(transitions, nTrans++, offsets, nOffsets,500trans_s,501wallOffsets[i],502standardOffsets[k+1]);503504}505}506k++;507}508if (nOffsets + 2 >= offsets.length) {509offsets = Arrays.copyOf(offsets, offsets.length + 100);510}511if (nTrans + 1 >= transitions.length) {512transitions = Arrays.copyOf(transitions, transitions.length + 100);513}514nOffsets = addTrans(transitions, nTrans++, offsets, nOffsets,515trans,516wallOffsets[i + 1],517getStandardOffset(standardTransitions, standardOffsets, trans));518519}520// append any leftover standard trans521while (k < standardTransitions.length) {522long trans = standardTransitions[k];523if (trans >= UTC1900) {524int offset = wallOffsets[i];525int offsetIndex = indexOf(offsets, 0, nOffsets, offset);526if (offsetIndex == nOffsets)527nOffsets++;528transitions[nTrans++] = ((trans * 1000) << TRANSITION_NSHIFT) |529(offsetIndex & OFFSET_MASK);530}531k++;532}533if (lastRules.length > 1) {534// fill the gap between the last trans until LASTYEAR535while (lastyear++ < LASTYEAR) {536for (ZoneOffsetTransitionRule zotr : lastRules) {537long trans = zotr.getTransitionEpochSecond(lastyear);538if (nOffsets + 2 >= offsets.length) {539offsets = Arrays.copyOf(offsets, offsets.length + 100);540}541if (nTrans + 1 >= transitions.length) {542transitions = Arrays.copyOf(transitions, transitions.length + 100);543}544nOffsets = addTrans(transitions, nTrans++,545offsets, nOffsets,546trans,547zotr.offsetAfter,548zotr.standardOffset);549}550}551ZoneOffsetTransitionRule startRule = lastRules[lastRules.length - 2];552ZoneOffsetTransitionRule endRule = lastRules[lastRules.length - 1];553params = new int[10];554if (startRule.offsetAfter - startRule.offsetBefore < 0 &&555endRule.offsetAfter - endRule.offsetBefore > 0) {556ZoneOffsetTransitionRule tmp;557tmp = startRule;558startRule = endRule;559endRule = tmp;560}561params[0] = startRule.month - 1;562int dom = startRule.dom;563int dow = startRule.dow;564if (dow == -1) {565params[1] = dom;566params[2] = 0;567} else {568// ZoneRulesBuilder adjusts < 0 case (-1, for last, don't have569// "<=" case yet) to positive value if not February (it appears570// we don't have February cutoff in tzdata table yet)571// Ideally, if JSR310 can just pass in the nagative and572// we can then pass in the dom = -1, dow > 0 into ZoneInfo573//574// hacking, assume the >=24 is the result of ZRB optimization for575// "last", it works for now. From tzdata2020d this hacking576// will not work for Asia/Gaza and Asia/Hebron which follow577// Palestine DST rules.578if (dom < 0 || dom >= 24 &&579!(zoneId.equals("Asia/Gaza") ||580zoneId.equals("Asia/Hebron"))) {581params[1] = -1;582params[2] = toCalendarDOW[dow];583} else {584params[1] = dom;585// To specify a day of week on or after an exact day of month,586// set the month to an exact month value, day-of-month to the587// day on or after which the rule is applied, and day-of-week588// to a negative Calendar.DAY_OF_WEEK DAY_OF_WEEK field value.589params[2] = -toCalendarDOW[dow];590}591}592params[3] = startRule.secondOfDay * 1000;593params[4] = toSTZTime[startRule.timeDefinition];594params[5] = endRule.month - 1;595dom = endRule.dom;596dow = endRule.dow;597if (dow == -1) {598params[6] = dom;599params[7] = 0;600} else {601// hacking: see comment above602if (dom < 0 || dom >= 24 &&603!(zoneId.equals("Asia/Gaza") ||604zoneId.equals("Asia/Hebron"))) {605params[6] = -1;606params[7] = toCalendarDOW[dow];607} else {608params[6] = dom;609params[7] = -toCalendarDOW[dow];610}611}612params[8] = endRule.secondOfDay * 1000;613params[9] = toSTZTime[endRule.timeDefinition];614dstSavings = (startRule.offsetAfter - startRule.offsetBefore) * 1000;615616// Note: known mismatching -> Asia/Amman617// ZoneInfo : startDayOfWeek=5 <= Thursday618// startTime=86400000 <= 24 hours619// This: startDayOfWeek=6620// startTime=0621// Similar workaround needs to be applied to Africa/Cairo and622// its endDayOfWeek and endTime623// Below is the workarounds, it probably slows down everyone a little624if (params[2] == 6 && params[3] == 0 &&625(zoneId.equals("Asia/Amman"))) {626params[2] = 5;627params[3] = 86400000;628}629// Additional check for startDayOfWeek=6 and starTime=86400000630// is needed for Asia/Amman;631if (params[2] == 7 && params[3] == 0 &&632(zoneId.equals("Asia/Amman"))) {633params[2] = 6; // Friday634params[3] = 86400000; // 24h635}636//endDayOfWeek and endTime workaround637if (params[7] == 6 && params[8] == 0 &&638(zoneId.equals("Africa/Cairo"))) {639params[7] = 5;640params[8] = 86400000;641}642643} else if (nTrans > 0) { // only do this if there is something in table already644if (lastyear < LASTYEAR) {645// ZoneInfo has an ending entry for 2037646//long trans = OffsetDateTime.of(LASTYEAR, 1, 1, 0, 0, 0, 0,647// ZoneOffset.ofTotalSeconds(rawOffset/1000))648// .toEpochSecond();649long trans = LDT2037 - rawOffset/1000;650651int offsetIndex = indexOf(offsets, 0, nOffsets, rawOffset/1000);652if (offsetIndex == nOffsets)653nOffsets++;654transitions[nTrans++] = (trans * 1000) << TRANSITION_NSHIFT |655(offsetIndex & OFFSET_MASK);656657} else if (savingsInstantTransitions.length > 2) {658// Workaround: create the params based on the last pair for659// zones like Israel and Iran which have trans defined660// up until 2037, but no "transition rule" defined661//662// Note: Known mismatching for Israel, Asia/Jerusalem/Tel Aviv663// ZoneInfo: startMode=3664// startMonth=2665// startDay=26666// startDayOfWeek=6667//668// This: startMode=1669// startMonth=2670// startDay=27671// startDayOfWeek=0672// these two are actually the same for 2037, the SimpleTimeZone673// for the last "known" year674int m = savingsInstantTransitions.length;675long startTrans = savingsInstantTransitions[m - 2];676int startOffset = wallOffsets[m - 2 + 1];677int startStd = getStandardOffset(standardTransitions, standardOffsets, startTrans);678long endTrans = savingsInstantTransitions[m - 1];679int endOffset = wallOffsets[m - 1 + 1];680int endStd = getStandardOffset(standardTransitions, standardOffsets, endTrans);681if (startOffset > startStd && endOffset == endStd) {682// last - 1 trans683m = savingsInstantTransitions.length - 2;684ZoneOffset before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);685ZoneOffset after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);686LocalDateTime ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);687LocalDateTime startLDT;688if (after.getTotalSeconds() > before.getTotalSeconds()) { // isGap()689startLDT = ldt;690} else {691startLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);692}693// last trans694m = savingsInstantTransitions.length - 1;695before = ZoneOffset.ofTotalSeconds(wallOffsets[m]);696after = ZoneOffset.ofTotalSeconds(wallOffsets[m + 1]);697ldt = LocalDateTime.ofEpochSecond(savingsInstantTransitions[m], 0, before);698LocalDateTime endLDT;699if (after.getTotalSeconds() > before.getTotalSeconds()) { // isGap()700endLDT = ldt.plusSeconds(wallOffsets[m + 1] - wallOffsets[m]);701} else {702endLDT = ldt;703}704params = new int[10];705params[0] = startLDT.getMonthValue() - 1;706params[1] = startLDT.getDayOfMonth();707params[2] = 0;708params[3] = startLDT.toLocalTime().toSecondOfDay() * 1000;709params[4] = SimpleTimeZone.WALL_TIME;710params[5] = endLDT.getMonthValue() - 1;711params[6] = endLDT.getDayOfMonth();712params[7] = 0;713params[8] = endLDT.toLocalTime().toSecondOfDay() * 1000;714params[9] = SimpleTimeZone.WALL_TIME;715dstSavings = (startOffset - startStd) * 1000;716}717}718}719if (transitions != null && transitions.length != nTrans) {720if (nTrans == 0) {721transitions = null;722} else {723transitions = Arrays.copyOf(transitions, nTrans);724}725}726if (offsets != null && offsets.length != nOffsets) {727if (nOffsets == 0) {728offsets = null;729} else {730offsets = Arrays.copyOf(offsets, nOffsets);731}732}733if (transitions != null) {734Checksum sum = new Checksum();735for (i = 0; i < transitions.length; i++) {736long val = transitions[i];737int dst = (int)((val >>> DST_NSHIFT) & 0xfL);738int saving = (dst == 0) ? 0 : offsets[dst];739int index = (int)(val & OFFSET_MASK);740int offset = offsets[index];741long second = (val >> TRANSITION_NSHIFT);742// javazic uses "index of the offset in offsets",743// instead of the real offset value itself to744// calculate the checksum. Have to keep doing745// the same thing, checksum is part of the746// ZoneInfo serialization form.747sum.update(second + index);748sum.update(index);749sum.update(dst == 0 ? -1 : dst);750}751checksum = (int)sum.getValue();752}753}754return new ZoneInfo(zoneId, rawOffset, dstSavings, checksum, transitions,755offsets, params, willGMTOffsetChange);756}757758private static int getStandardOffset(long[] standardTransitions,759int[] standardOffsets,760long epochSec) {761// The size of stdOffsets is [0..9], with most are762// [1..4] entries , simple loop search is faster763//764// int index = Arrays.binarySearch(standardTransitions, epochSec);765// if (index < 0) {766// // switch negative insert position to start of matched range767// index = -index - 2;768// }769// return standardOffsets[index + 1];770int index = 0;771for (; index < standardTransitions.length; index++) {772if (epochSec < standardTransitions[index]) {773break;774}775}776return standardOffsets[index];777}778779static final int SECONDS_PER_DAY = 86400;780static final int DAYS_PER_CYCLE = 146097;781static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);782783private static int getYear(long epochSecond, int offset) {784long second = epochSecond + offset; // overflow caught later785long epochDay = Math.floorDiv(second, SECONDS_PER_DAY);786long zeroDay = epochDay + DAYS_0000_TO_1970;787// find the march-based year788zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle789long adjust = 0;790if (zeroDay < 0) {791// adjust negative years to positive for calculation792long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;793adjust = adjustCycles * 400;794zeroDay += -adjustCycles * DAYS_PER_CYCLE;795}796long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;797long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);798if (doyEst < 0) {799// fix estimate800yearEst--;801doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);802}803yearEst += adjust; // reset any negative year804int marchDoy0 = (int) doyEst;805// convert march-based values back to january-based806int marchMonth0 = (marchDoy0 * 5 + 2) / 153;807int month = (marchMonth0 + 2) % 12 + 1;808int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;809yearEst += marchMonth0 / 10;810return (int)yearEst;811}812813private static final int toCalendarDOW[] = new int[] {814-1,815Calendar.MONDAY,816Calendar.TUESDAY,817Calendar.WEDNESDAY,818Calendar.THURSDAY,819Calendar.FRIDAY,820Calendar.SATURDAY,821Calendar.SUNDAY822};823824private static final int toSTZTime[] = new int[] {825SimpleTimeZone.UTC_TIME,826SimpleTimeZone.WALL_TIME,827SimpleTimeZone.STANDARD_TIME,828};829830private static final long OFFSET_MASK = 0x0fL;831private static final long DST_MASK = 0xf0L;832private static final int DST_NSHIFT = 4;833private static final int TRANSITION_NSHIFT = 12;834private static final int LASTYEAR = 2037;835836// from: 0 for offset lookup, 1 for dstsvings lookup837private static int indexOf(int[] offsets, int from, int nOffsets, int offset) {838offset *= 1000;839for (; from < nOffsets; from++) {840if (offsets[from] == offset)841return from;842}843offsets[from] = offset;844return from;845}846847// return updated nOffsets848private static int addTrans(long transitions[], int nTrans,849int offsets[], int nOffsets,850long trans, int offset, int stdOffset) {851int offsetIndex = indexOf(offsets, 0, nOffsets, offset);852if (offsetIndex == nOffsets)853nOffsets++;854int dstIndex = 0;855if (offset != stdOffset) {856dstIndex = indexOf(offsets, 1, nOffsets, offset - stdOffset);857if (dstIndex == nOffsets)858nOffsets++;859}860transitions[nTrans] = ((trans * 1000) << TRANSITION_NSHIFT) |861((dstIndex << DST_NSHIFT) & DST_MASK) |862(offsetIndex & OFFSET_MASK);863return nOffsets;864}865866// ZoneInfo checksum, copy/pasted from javazic867private static class Checksum extends CRC32 {868public void update(int val) {869byte[] b = new byte[4];870b[0] = (byte)(val >>> 24);871b[1] = (byte)(val >>> 16);872b[2] = (byte)(val >>> 8);873b[3] = (byte)(val);874update(b);875}876void update(long val) {877byte[] b = new byte[8];878b[0] = (byte)(val >>> 56);879b[1] = (byte)(val >>> 48);880b[2] = (byte)(val >>> 40);881b[3] = (byte)(val >>> 32);882b[4] = (byte)(val >>> 24);883b[5] = (byte)(val >>> 16);884b[6] = (byte)(val >>> 8);885b[7] = (byte)(val);886update(b);887}888}889890// A simple/raw version of j.t.ZoneOffsetTransitionRule891private static class ZoneOffsetTransitionRule {892private final int month;893private final byte dom;894private final int dow;895private final int secondOfDay;896private final boolean timeEndOfDay;897private final int timeDefinition;898private final int standardOffset;899private final int offsetBefore;900private final int offsetAfter;901902ZoneOffsetTransitionRule(DataInput in) throws IOException {903int data = in.readInt();904int dowByte = (data & (7 << 19)) >>> 19;905int timeByte = (data & (31 << 14)) >>> 14;906int stdByte = (data & (255 << 4)) >>> 4;907int beforeByte = (data & (3 << 2)) >>> 2;908int afterByte = (data & 3);909910this.month = data >>> 28;911this.dom = (byte)(((data & (63 << 22)) >>> 22) - 32);912this.dow = dowByte == 0 ? -1 : dowByte;913this.secondOfDay = timeByte == 31 ? in.readInt() : timeByte * 3600;914this.timeEndOfDay = timeByte == 24;915this.timeDefinition = (data & (3 << 12)) >>> 12;916917this.standardOffset = stdByte == 255 ? in.readInt() : (stdByte - 128) * 900;918this.offsetBefore = beforeByte == 3 ? in.readInt() : standardOffset + beforeByte * 1800;919this.offsetAfter = afterByte == 3 ? in.readInt() : standardOffset + afterByte * 1800;920}921922long getTransitionEpochSecond(int year) {923long epochDay = 0;924if (dom < 0) {925epochDay = toEpochDay(year, month, lengthOfMonth(year, month) + 1 + dom);926if (dow != -1) {927epochDay = previousOrSame(epochDay, dow);928}929} else {930epochDay = toEpochDay(year, month, dom);931if (dow != -1) {932epochDay = nextOrSame(epochDay, dow);933}934}935if (timeEndOfDay) {936epochDay += 1;937}938int difference = 0;939switch (timeDefinition) {940case 0: // UTC941difference = 0;942break;943case 1: // WALL944difference = -offsetBefore;945break;946case 2: //STANDARD947difference = -standardOffset;948break;949}950return epochDay * 86400 + secondOfDay + difference;951}952953static final boolean isLeapYear(int year) {954return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);955}956957static final int lengthOfMonth(int year, int month) {958switch (month) {959case 2: //FEBRUARY:960return isLeapYear(year)? 29 : 28;961case 4: //APRIL:962case 6: //JUNE:963case 9: //SEPTEMBER:964case 11: //NOVEMBER:965return 30;966default:967return 31;968}969}970971static final long toEpochDay(int year, int month, int day) {972long y = year;973long m = month;974long total = 0;975total += 365 * y;976if (y >= 0) {977total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;978} else {979total -= y / -4 - y / -100 + y / -400;980}981total += ((367 * m - 362) / 12);982total += day - 1;983if (m > 2) {984total--;985if (!isLeapYear(year)) {986total--;987}988}989return total - DAYS_0000_TO_1970;990}991992static final long previousOrSame(long epochDay, int dayOfWeek) {993return adjust(epochDay, dayOfWeek, 1);994}995996static final long nextOrSame(long epochDay, int dayOfWeek) {997return adjust(epochDay, dayOfWeek, 0);998}9991000static final long adjust(long epochDay, int dow, int relative) {1001int calDow = (int)Math.floorMod(epochDay + 3, 7L) + 1;1002if (relative < 2 && calDow == dow) {1003return epochDay;1004}1005if ((relative & 1) == 0) {1006int daysDiff = calDow - dow;1007return epochDay + (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);1008} else {1009int daysDiff = dow - calDow;1010return epochDay - (daysDiff >= 0 ? 7 - daysDiff : -daysDiff);1011}1012}1013}1014}101510161017