Path: blob/master/src/java.base/share/classes/sun/util/calendar/ZoneInfo.java
41159 views
/*1* Copyright (c) 2000, 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.IOException;28import java.io.ObjectInputStream;29import java.util.Date;30import java.util.Map;31import java.util.SimpleTimeZone;32import java.util.TimeZone;3334/**35* <code>ZoneInfo</code> is an implementation subclass of {@link36* java.util.TimeZone TimeZone} that represents GMT offsets and37* daylight saving time transitions of a time zone.38* <p>39* The daylight saving time transitions are described in the {@link40* #transitions transitions} table consisting of a chronological41* sequence of transitions of GMT offset and/or daylight saving time42* changes. Since all transitions are represented in UTC, in theory,43* <code>ZoneInfo</code> can be used with any calendar systems except44* for the {@link #getOffset(int,int,int,int,int,int) getOffset}45* method that takes Gregorian calendar date fields.46* <p>47* This table covers transitions from 1900 until 2037 (as of version48* 1.4), Before 1900, it assumes that there was no daylight saving49* time and the <code>getOffset</code> methods always return the50* {@link #getRawOffset} value. No Local Mean Time is supported. If a51* specified date is beyond the transition table and this time zone is52* supposed to observe daylight saving time in 2037, it delegates53* operations to a {@link java.util.SimpleTimeZone SimpleTimeZone}54* object created using the daylight saving time schedule as of 2037.55* <p>56* The date items, transitions, GMT offset(s), etc. are read from a database57* file. See {@link ZoneInfoFile} for details.58* @see java.util.SimpleTimeZone59* @since 1.460*/6162public class ZoneInfo extends TimeZone {6364private static final int UTC_TIME = 0;65private static final int STANDARD_TIME = 1;66private static final int WALL_TIME = 2;6768private static final long OFFSET_MASK = 0x0fL;69private static final long DST_MASK = 0xf0L;70private static final int DST_NSHIFT = 4;71// this bit field is reserved for abbreviation support72private static final long ABBR_MASK = 0xf00L;73private static final int TRANSITION_NSHIFT = 12;7475/**76* The raw GMT offset in milliseconds between this zone and GMT.77* Negative offsets are to the west of Greenwich. To obtain local78* <em>standard</em> time, add the offset to GMT time.79* @serial80*/81private int rawOffset;8283/**84* Difference in milliseconds from the original GMT offset in case85* the raw offset value has been modified by calling {@link86* #setRawOffset}. The initial value is 0.87* @serial88*/89private int rawOffsetDiff = 0;9091/**92* A CRC32 value of all pairs of transition time (in milliseconds93* in <code>long</code>) in local time and its GMT offset (in94* seconds in <code>int</code>) in the chronological order. Byte95* values of each <code>long</code> and <code>int</code> are taken96* in the big endian order (i.e., MSB to LSB).97* @serial98*/99private int checksum;100101/**102* The amount of time in milliseconds saved during daylight saving103* time. If <code>useDaylight</code> is false, this value is 0.104* @serial105*/106private int dstSavings;107108/**109* This array describes transitions of GMT offsets of this time110* zone, including both raw offset changes and daylight saving111* time changes.112* A long integer consists of four bit fields.113* <ul>114* <li>The most significant 52-bit field represents transition115* time in milliseconds from Gregorian January 1 1970, 00:00:00116* GMT.</li>117* <li>The next 4-bit field is reserved and must be 0.</li>118* <li>The next 4-bit field is an index value to {@link #offsets119* offsets[]} for the amount of daylight saving at the120* transition. If this value is zero, it means that no daylight121* saving, not the index value zero.</li>122* <li>The least significant 4-bit field is an index value to123* {@link #offsets offsets[]} for <em>total</em> GMT offset at the124* transition.</li>125* </ul>126* If this time zone doesn't observe daylight saving time and has127* never changed any GMT offsets in the past, this value is null.128* @serial129*/130private long[] transitions;131132/**133* This array holds all unique offset values in134* milliseconds. Index values to this array are stored in the135* transitions array elements.136* @serial137*/138private int[] offsets;139140/**141* SimpleTimeZone parameter values. It has to have either 8 for142* {@link java.util.SimpleTimeZone#SimpleTimeZone(int, String,143* int, int , int , int , int , int , int , int , int) the144* 11-argument SimpleTimeZone constructor} or 10 for {@link145* java.util.SimpleTimeZone#SimpleTimeZone(int, String, int, int,146* int , int , int , int , int , int , int, int, int) the147* 13-argument SimpleTimeZone constructor} parameters.148* @serial149*/150private int[] simpleTimeZoneParams;151152/**153* True if the raw GMT offset value would change after the time154* zone data has been generated; false, otherwise. The default155* value is false.156* @serial157*/158private boolean willGMTOffsetChange = false;159160/**161* True if the object has been modified after its instantiation.162*/163private transient boolean dirty = false;164165@java.io.Serial166private static final long serialVersionUID = 2653134537216586139L;167168/**169* A constructor.170*/171public ZoneInfo() {172}173174/**175* A Constructor for CustomID.176*/177public ZoneInfo(String ID, int rawOffset) {178this(ID, rawOffset, 0, 0, null, null, null, false);179}180181/**182* Constructs a ZoneInfo instance.183*184* @param ID time zone name185* @param rawOffset GMT offset in milliseconds186* @param dstSavings daylight saving value in milliseconds or 0187* (zero) if this time zone doesn't observe Daylight Saving Time.188* @param checksum CRC32 value with all transitions table entry189* values190* @param transitions transition table191* @param offsets offset value table192* @param simpleTimeZoneParams parameter values for constructing193* SimpleTimeZone194* @param willGMTOffsetChange the value of willGMTOffsetChange195*/196ZoneInfo(String ID,197int rawOffset,198int dstSavings,199int checksum,200long[] transitions,201int[] offsets,202int[] simpleTimeZoneParams,203boolean willGMTOffsetChange) {204setID(ID);205this.rawOffset = rawOffset;206this.dstSavings = dstSavings;207this.checksum = checksum;208this.transitions = transitions;209this.offsets = offsets;210this.simpleTimeZoneParams = simpleTimeZoneParams;211this.willGMTOffsetChange = willGMTOffsetChange;212}213214/**215* Returns the difference in milliseconds between local time and UTC216* of given time, taking into account both the raw offset and the217* effect of daylight savings.218*219* @param date the milliseconds in UTC220* @return the milliseconds to add to UTC to get local wall time221*/222public int getOffset(long date) {223return getOffsets(date, null, UTC_TIME);224}225226public int getOffsets(long utc, int[] offsets) {227return getOffsets(utc, offsets, UTC_TIME);228}229230public int getOffsetsByStandard(long standard, int[] offsets) {231return getOffsets(standard, offsets, STANDARD_TIME);232}233234public int getOffsetsByWall(long wall, int[] offsets) {235return getOffsets(wall, offsets, WALL_TIME);236}237238private int getOffsets(long date, int[] offsets, int type) {239// if dst is never observed, there is no transition.240if (transitions == null) {241int offset = getLastRawOffset();242if (offsets != null) {243offsets[0] = offset;244offsets[1] = 0;245}246return offset;247}248249date -= rawOffsetDiff;250int index = getTransitionIndex(date, type);251252// prior to the transition table, returns the raw offset.253// FIXME: should support LMT.254if (index < 0) {255int offset = getLastRawOffset();256if (offsets != null) {257offsets[0] = offset;258offsets[1] = 0;259}260return offset;261}262263if (index < transitions.length) {264long val = transitions[index];265int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;266if (offsets != null) {267int dst = (int)((val >>> DST_NSHIFT) & 0xfL);268int save = (dst == 0) ? 0 : this.offsets[dst];269offsets[0] = offset - save;270offsets[1] = save;271}272return offset;273}274275// beyond the transitions, delegate to SimpleTimeZone if there276// is a rule; otherwise, return the offset of the last transition.277SimpleTimeZone tz = getLastRule();278if (tz != null) {279int rawoffset = tz.getRawOffset();280long msec = date;281if (type != UTC_TIME) {282msec -= rawOffset;283}284int dstoffset = tz.getOffset(msec) - rawOffset;285286// Check if it's in a standard-to-daylight transition.287if (dstoffset > 0 && tz.getOffset(msec - dstoffset) == rawoffset && type == WALL_TIME) {288dstoffset = 0;289}290291if (offsets != null) {292offsets[0] = rawoffset;293offsets[1] = dstoffset;294}295return rawoffset + dstoffset;296} else {297// use the last transition298long val = transitions[transitions.length - 1];299int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;300if (offsets != null) {301int dst = (int)((val >>> DST_NSHIFT) & 0xfL);302int save = (dst == 0) ? 0 : this.offsets[dst];303offsets[0] = offset - save;304offsets[1] = save;305}306return offset;307}308}309310private int getTransitionIndex(long date, int type) {311int low = 0;312int high = transitions.length - 1;313314while (low <= high) {315int mid = (low + high) / 2;316long val = transitions[mid];317long midVal = val >> TRANSITION_NSHIFT; // sign extended318if (type != UTC_TIME) {319midVal += offsets[(int)(val & OFFSET_MASK)]; // wall time320}321if (type == STANDARD_TIME) {322int dstIndex = (int)((val >>> DST_NSHIFT) & 0xfL);323if (dstIndex != 0) {324midVal -= offsets[dstIndex]; // make it standard time325}326}327328if (midVal < date) {329low = mid + 1;330} else if (midVal > date) {331high = mid - 1;332} else {333return mid;334}335}336337// if beyond the transitions, returns that index.338if (low >= transitions.length) {339return low;340}341return low - 1;342}343344/**345* Returns the difference in milliseconds between local time and346* UTC, taking into account both the raw offset and the effect of347* daylight savings, for the specified date and time. This method348* assumes that the start and end month are distinct. This method349* assumes a Gregorian calendar for calculations.350* <p>351* <em>Note: In general, clients should use352* {@link Calendar#ZONE_OFFSET Calendar.get(ZONE_OFFSET)} +353* {@link Calendar#DST_OFFSET Calendar.get(DST_OFFSET)}354* instead of calling this method.</em>355*356* @param era The era of the given date. The value must be either357* GregorianCalendar.AD or GregorianCalendar.BC.358* @param year The year in the given date.359* @param month The month in the given date. Month is 0-based. e.g.,360* 0 for January.361* @param day The day-in-month of the given date.362* @param dayOfWeek The day-of-week of the given date.363* @param milliseconds The milliseconds in day in <em>standard</em> local time.364* @return The milliseconds to add to UTC to get local time.365*/366public int getOffset(int era, int year, int month, int day,367int dayOfWeek, int milliseconds) {368if (milliseconds < 0 || milliseconds >= AbstractCalendar.DAY_IN_MILLIS) {369throw new IllegalArgumentException();370}371372if (era == java.util.GregorianCalendar.BC) { // BC373year = 1 - year;374} else if (era != java.util.GregorianCalendar.AD) {375throw new IllegalArgumentException();376}377378Gregorian gcal = CalendarSystem.getGregorianCalendar();379CalendarDate date = gcal.newCalendarDate(null);380date.setDate(year, month + 1, day);381if (gcal.validate(date) == false) {382throw new IllegalArgumentException();383}384385// bug-for-bug compatible argument checking386if (dayOfWeek < java.util.GregorianCalendar.SUNDAY387|| dayOfWeek > java.util.GregorianCalendar.SATURDAY) {388throw new IllegalArgumentException();389}390391if (transitions == null) {392return getLastRawOffset();393}394395long dateInMillis = gcal.getTime(date) + milliseconds;396dateInMillis -= (long) rawOffset; // make it UTC397return getOffsets(dateInMillis, null, UTC_TIME);398}399400/**401* Sets the base time zone offset from GMT. This operation402* modifies all the transitions of this ZoneInfo object, including403* historical ones, if applicable.404*405* @param offsetMillis the base time zone offset to GMT.406* @see getRawOffset407*/408public synchronized void setRawOffset(int offsetMillis) {409if (offsetMillis == rawOffset + rawOffsetDiff) {410return;411}412rawOffsetDiff = offsetMillis - rawOffset;413if (lastRule != null) {414lastRule.setRawOffset(offsetMillis);415}416dirty = true;417}418419/**420* Returns the GMT offset of the current date. This GMT offset421* value is not modified during Daylight Saving Time.422*423* @return the GMT offset value in milliseconds to add to UTC time424* to get local standard time425*/426public int getRawOffset() {427if (!willGMTOffsetChange) {428return rawOffset + rawOffsetDiff;429}430431int[] offsets = new int[2];432getOffsets(System.currentTimeMillis(), offsets, UTC_TIME);433return offsets[0];434}435436public boolean isDirty() {437return dirty;438}439440private int getLastRawOffset() {441return rawOffset + rawOffsetDiff;442}443444/**445* Queries if this time zone uses Daylight Saving Time in the last known rule.446*/447public boolean useDaylightTime() {448return (simpleTimeZoneParams != null);449}450451@Override452public boolean observesDaylightTime() {453if (simpleTimeZoneParams != null) {454return true;455}456if (transitions == null) {457return false;458}459460// Look up the transition table to see if it's in DST right461// now or if there's any standard-to-daylight transition at462// any future.463long utc = System.currentTimeMillis() - rawOffsetDiff;464int index = getTransitionIndex(utc, UTC_TIME);465466// before transitions in the transition table467if (index < 0) {468return false;469}470471// the time is in the table range.472for (int i = index; i < transitions.length; i++) {473if ((transitions[i] & DST_MASK) != 0) {474return true;475}476}477// No further DST is observed.478return false;479}480481/**482* Queries if the specified date is in Daylight Saving Time.483*/484public boolean inDaylightTime(Date date) {485if (date == null) {486throw new NullPointerException();487}488489if (transitions == null) {490return false;491}492493long utc = date.getTime() - rawOffsetDiff;494int index = getTransitionIndex(utc, UTC_TIME);495496// before transitions in the transition table497if (index < 0) {498return false;499}500501// the time is in the table range.502if (index < transitions.length) {503return (transitions[index] & DST_MASK) != 0;504}505506// beyond the transition table507SimpleTimeZone tz = getLastRule();508if (tz != null) {509return tz.inDaylightTime(date);510} else {511// use the last transition512return (transitions[transitions.length - 1] & DST_MASK) != 0;513}514}515516/**517* Returns the amount of time in milliseconds that the clock is advanced518* during daylight saving time is in effect in its last daylight saving time rule.519*520* @return the number of milliseconds the time is advanced with respect to521* standard time when daylight saving time is in effect.522*/523public int getDSTSavings() {524return dstSavings;525}526527// /**528// * @return the last year in the transition table or -1 if this529// * time zone doesn't observe any daylight saving time.530// */531// public int getMaxTransitionYear() {532// if (transitions == null) {533// return -1;534// }535// long val = transitions[transitions.length - 1];536// int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;537// val = (val >> TRANSITION_NSHIFT) + offset;538// CalendarDate lastDate = Gregorian.getCalendarDate(val);539// return lastDate.getYear();540// }541542/**543* Returns a string representation of this time zone.544* @return the string545*/546public String toString() {547return getClass().getName() +548"[id=\"" + getID() + "\"" +549",offset=" + getLastRawOffset() +550",dstSavings=" + dstSavings +551",useDaylight=" + useDaylightTime() +552",transitions=" + ((transitions != null) ? transitions.length : 0) +553",lastRule=" + (lastRule == null ? getLastRuleInstance() : lastRule) +554"]";555}556557/**558* Gets all available IDs supported in the Java run-time.559*560* @return an array of time zone IDs.561*/562public static String[] getAvailableIDs() {563return ZoneInfoFile.getZoneIds();564}565566/**567* Gets all available IDs that have the same value as the568* specified raw GMT offset.569*570* @param rawOffset the GMT offset in milliseconds. This571* value should not include any daylight saving time.572*573* @return an array of time zone IDs.574*/575public static String[] getAvailableIDs(int rawOffset) {576return ZoneInfoFile.getZoneIds(rawOffset);577}578579/**580* Gets the ZoneInfo for the given ID.581*582* @param ID the ID for a ZoneInfo. See TimeZone for detail.583*584* @return the specified ZoneInfo object, or null if there is no585* time zone of the ID.586*/587public static TimeZone getTimeZone(String ID) {588return ZoneInfoFile.getZoneInfo(ID);589}590591private transient SimpleTimeZone lastRule;592593/**594* Returns a SimpleTimeZone object representing the last GMT595* offset and DST schedule or null if this time zone doesn't596* observe DST.597*/598private synchronized SimpleTimeZone getLastRule() {599if (lastRule == null) {600lastRule = getLastRuleInstance();601}602return lastRule;603}604605/**606* Returns a SimpleTimeZone object that represents the last607* known daylight saving time rules.608*609* @return a SimpleTimeZone object or null if this time zone610* doesn't observe DST.611*/612public SimpleTimeZone getLastRuleInstance() {613if (simpleTimeZoneParams == null) {614return null;615}616if (simpleTimeZoneParams.length == 10) {617return new SimpleTimeZone(getLastRawOffset(), getID(),618simpleTimeZoneParams[0],619simpleTimeZoneParams[1],620simpleTimeZoneParams[2],621simpleTimeZoneParams[3],622simpleTimeZoneParams[4],623simpleTimeZoneParams[5],624simpleTimeZoneParams[6],625simpleTimeZoneParams[7],626simpleTimeZoneParams[8],627simpleTimeZoneParams[9],628dstSavings);629}630return new SimpleTimeZone(getLastRawOffset(), getID(),631simpleTimeZoneParams[0],632simpleTimeZoneParams[1],633simpleTimeZoneParams[2],634simpleTimeZoneParams[3],635simpleTimeZoneParams[4],636simpleTimeZoneParams[5],637simpleTimeZoneParams[6],638simpleTimeZoneParams[7],639dstSavings);640}641642/**643* Returns a copy of this <code>ZoneInfo</code>.644*/645public Object clone() {646ZoneInfo zi = (ZoneInfo) super.clone();647zi.lastRule = null;648return zi;649}650651/**652* Returns a hash code value calculated from the GMT offset and653* transitions.654* @return a hash code of this time zone655*/656public int hashCode() {657return getLastRawOffset() ^ checksum;658}659660/**661* Compares the equity of two ZoneInfo objects.662*663* @param obj the object to be compared with664* @return true if given object is same as this ZoneInfo object,665* false otherwise.666*/667public boolean equals(Object obj) {668if (this == obj) {669return true;670}671if (!(obj instanceof ZoneInfo)) {672return false;673}674ZoneInfo that = (ZoneInfo) obj;675return (getID().equals(that.getID())676&& (getLastRawOffset() == that.getLastRawOffset())677&& (checksum == that.checksum));678}679680/**681* Returns true if this zone has the same raw GMT offset value and682* transition table as another zone info. If the specified683* TimeZone object is not a ZoneInfo instance, this method returns684* true if the specified TimeZone object has the same raw GMT685* offset value with no daylight saving time.686*687* @param other the ZoneInfo object to be compared with688* @return true if the given <code>TimeZone</code> has the same689* GMT offset and transition information; false, otherwise.690*/691public boolean hasSameRules(TimeZone other) {692if (this == other) {693return true;694}695if (other == null) {696return false;697}698if (!(other instanceof ZoneInfo)) {699if (getRawOffset() != other.getRawOffset()) {700return false;701}702// if both have the same raw offset and neither observes703// DST, they have the same rule.704if ((transitions == null)705&& (useDaylightTime() == false)706&& (other.useDaylightTime() == false)) {707return true;708}709return false;710}711if (getLastRawOffset() != ((ZoneInfo)other).getLastRawOffset()) {712return false;713}714return (checksum == ((ZoneInfo)other).checksum);715}716717/**718* Returns a Map from alias time zone IDs to their standard719* time zone IDs.720*721* @return the Map that holds the mappings from alias time zone IDs722* to their standard time zone IDs, or null if723* <code>ZoneInfoMappings</code> file is not available.724*/725public static Map<String, String> getAliasTable() {726return ZoneInfoFile.getAliasMap();727}728729@java.io.Serial730private void readObject(ObjectInputStream stream)731throws IOException, ClassNotFoundException {732stream.defaultReadObject();733// We don't know how this object from 1.4.x or earlier has734// been mutated. So it should always be marked as `dirty'.735dirty = true;736}737}738739740