001: /*
002: * @(#)ZoneInfo.java 1.9 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package sun.util.calendar;
028:
029: import java.lang.ref.SoftReference;
030: import java.util.ArrayList;
031: import java.util.Date;
032: import java.util.GregorianCalendar;
033: import java.util.HashMap;
034: import java.util.SimpleTimeZone;
035: import java.util.TimeZone;
036:
037: /**
038: * <code>ZoneInfo</code> is an implementation subclass of {@link
039: * java.util.TimeZone TimeZone} that represents GMT offsets and
040: * daylight saving time transitions of a time zone.
041: * <p>
042: * The daylight saving time transitions are described in the {@link
043: * #transitions transitions} table consisting of a chronological
044: * sequence of transitions of GMT offset and/or daylight saving time
045: * changes. Since all transitions are represented in UTC, in theory,
046: * <code>ZoneInfo</code> can be used with any calendar systems except
047: * for the {@link #getOffset(int,int,int,int,int,int) getOffset}
048: * method that takes Gregorian calendar date fields.
049: * <p>
050: * This table covers transitions from 1900 until 2037 (as of version
051: * 1.4), Before 1900, it assumes that there was no daylight saving
052: * time and the <code>getOffset</code> methods always return the
053: * {@link #getRawOffset} value. No Local Mean Time is supported. If a
054: * specified date is beyond the transition table and this time zone is
055: * supposed to observe daylight saving time in 2037, it delegates
056: * operations to a {@link java.util.SimpleTimeZone SimpleTimeZone}
057: * object created using the daylight saving time schedule as of 2037.
058: * <p>
059: * The date items, transitions, GMT offset(s), etc. are read from a database
060: * file. See {@link ZoneInfoFile} for details.
061: * @see java.util.SimpleTimeZone
062: * @since 1.4
063: */
064:
065: public class ZoneInfo extends TimeZone {
066:
067: private static final long OFFSET_MASK = 0x0fL;
068: private static final long DST_MASK = 0xf0L;
069: private static final int DST_NSHIFT = 4;
070: // this bit field is reserved for abbreviation support
071: private static final long ABBR_MASK = 0xf00L;
072: private static final int TRANSITION_NSHIFT = 12;
073:
074: /**
075: * The raw GMT offset in milliseconds between this zone and GMT.
076: * Negative offsets are to the west of Greenwich. To obtain local
077: * <em>standard</em> time, add the offset to GMT time.
078: * @serial
079: */
080: private int rawOffset;
081:
082: /**
083: * Difference in milliseconds from the original GMT offset in case
084: * the raw offset value has been modified by calling {@link
085: * #setRawOffset}. The initial value is 0.
086: * @serial
087: */
088: private int rawOffsetDiff = 0;
089:
090: /**
091: * A CRC32 value of all pairs of transition time (in milliseconds
092: * in <code>long</code>) in local time and its GMT offset (in
093: * seconds in <code>int</code>) in the chronological order. Byte
094: * values of each <code>long</code> and <code>int</code> are taken
095: * in the big endian order (i.e., MSB to LSB).
096: * @serial
097: */
098: private int checksum;
099:
100: /**
101: * The amount of time in milliseconds saved during daylight saving
102: * time. If <code>useDaylight</code> is false, this value is 0.
103: * @serial
104: */
105: private int dstSavings;
106:
107: /**
108: * This array describes transitions of GMT offsets of this time
109: * zone, including both raw offset changes and daylight saving
110: * time changes.
111: * A long integer consists of four bit fields.
112: * <ul>
113: * <li>The most significant 52-bit field represents transition
114: * time in milliseconds from Gregorian January 1 1970, 00:00:00
115: * GMT.</li>
116: * <li>The next 4-bit field is reserved and must be 0.</li>
117: * <li>The next 4-bit field is an index value to {@link #offsets
118: * offsets[]} for the amount of daylight saving at the
119: * transition. If this value is zero, it means that no daylight
120: * saving, not the index value zero.</li>
121: * <li>The least significant 4-bit field is an index value to
122: * {@link #offsets offsets[]} for <em>total</em> GMT offset at the
123: * transition.</li>
124: * </ul>
125: * If this time zone doesn't observe daylight saving time and has
126: * never changed any GMT offsets in the past, this value is null.
127: * @serial
128: */
129: private long[] transitions;
130:
131: /**
132: * This array holds all unique offset values in
133: * milliseconds. Index values to this array are stored in the
134: * transitions array elements.
135: * @serial
136: */
137: private int[] offsets;
138:
139: /**
140: * SimpleTimeZone parameter values. It has to have either 8 for
141: * {@link java.util.SimpleTimeZone#SimpleTimeZone(int, String,
142: * int, int , int , int , int , int , int , int , int) the
143: * 11-argument SimpleTimeZone constructor} or 10 for {@link
144: * java.util.SimpleTimeZone#SimpleTimeZone(int, String, int, int,
145: * int , int , int , int , int , int , int, int, int) the
146: * 13-argument SimpleTimeZone constructor} parameters.
147: * @serial
148: */
149: private int[] simpleTimeZoneParams;
150:
151: /**
152: * True if the raw GMT offset value would change after the time
153: * zone data has been generated; false, otherwise. The default
154: * value is false.
155: * @serial
156: */
157: private boolean willGMTOffsetChange = false;
158:
159: private static final long serialVersionUID = 2653134537216586139L;
160:
161: /**
162: * A constructor.
163: */
164: public ZoneInfo() {
165: }
166:
167: /**
168: * A Constructor for CustomID.
169: */
170: public ZoneInfo(String ID, int rawOffset) {
171: this (ID, rawOffset, 0, 0, null, null, null, false);
172: }
173:
174: /**
175: * Constructs a ZoneInfo instance.
176: *
177: * @param ID time zone name
178: * @param rawOffset GMT offset in milliseconds
179: * @param dstSavings daylight saving value in milliseconds or 0
180: * (zero) if this time zone doesn't observe Daylight Saving Time.
181: * @param checksum CRC32 value with all transitions table entry
182: * values
183: * @param transitions transition table
184: * @param offsets offset value table
185: * @param simpleTimeZoneParams parameter values for constructing
186: * SimpleTimeZone
187: * @param willGMTOffsetChange the value of willGMTOffsetChange
188: */
189: ZoneInfo(String ID, int rawOffset, int dstSavings, int checksum,
190: long[] transitions, int[] offsets,
191: int[] simpleTimeZoneParams, boolean willGMTOffsetChange) {
192: setID(ID);
193: this .rawOffset = rawOffset;
194: this .dstSavings = dstSavings;
195: this .checksum = checksum;
196: this .transitions = transitions;
197: this .offsets = offsets;
198: this .simpleTimeZoneParams = simpleTimeZoneParams;
199: this .willGMTOffsetChange = willGMTOffsetChange;
200: }
201:
202: /**
203: * Returns the difference in milliseconds between local time and UTC
204: * of given time, taking into account both the raw offset and the
205: * effect of daylight savings.
206: *
207: * @param date the milliseconds in UTC
208: * @return the milliseconds to add to UTC to get local wall time
209: */
210: public int getOffset(long date) {
211: return getOffsets(date, null, false);
212: }
213:
214: public int getOffsets(long utc, int[] offsets) {
215: return getOffsets(utc, offsets, false);
216: }
217:
218: public int getOffsetsByWall(long wall, int[] offsets) {
219: return getOffsets(wall, offsets, true);
220: }
221:
222: private int getOffsets(long date, int[] offsets, boolean isWall) {
223: // if dst is never observed, there is no transition.
224: if (transitions == null) {
225: int offset = getLastRawOffset();
226: if (offsets != null) {
227: offsets[0] = offset;
228: offsets[1] = 0;
229: }
230: return offset;
231: }
232:
233: date -= rawOffsetDiff;
234: int index = getTransitionIndex(date, isWall);
235:
236: // prior to the transition table, returns the raw offset.
237: // should support LMT.
238: if (index < 0) {
239: int offset = getLastRawOffset();
240: if (offsets != null) {
241: offsets[0] = offset;
242: offsets[1] = 0;
243: }
244: return offset;
245: }
246:
247: if (index < transitions.length) {
248: long val = transitions[index];
249: int offset = this .offsets[(int) (val & OFFSET_MASK)]
250: + rawOffsetDiff;
251: if (offsets != null) {
252: int dst = (int) ((val >>> DST_NSHIFT) & 0xfL);
253: int save = (dst == 0) ? 0 : this .offsets[dst];
254: offsets[0] = offset - save;
255: offsets[1] = save;
256: }
257: return offset;
258: }
259:
260: // beyond the transitions, delegate to SimpleTimeZone if there
261: // is a rule; otherwise, return rawOffset.
262: SimpleTimeZone tz = getLastRule();
263: if (tz != null) {
264: int rawoffset = tz.getRawOffset();
265: long msec = date;
266: if (isWall) {
267: msec -= rawOffset;
268: }
269: int dstoffset = tz.inDaylightTime(new Date(msec)) ? tz
270: .getDSTSavings() : 0;
271: if (offsets != null) {
272: offsets[0] = rawoffset;
273: offsets[1] = dstoffset;
274: }
275: return rawoffset + dstoffset;
276: }
277: int offset = getLastRawOffset();
278: if (offsets != null) {
279: offsets[0] = offset;
280: offsets[1] = 0;
281: }
282: return offset;
283: }
284:
285: private final int getTransitionIndex(long date, boolean isWall) {
286: int low = 0;
287: int high = transitions.length - 1;
288:
289: while (low <= high) {
290: int mid = (low + high) / 2;
291: long val = transitions[mid];
292: long midVal = val >> TRANSITION_NSHIFT;
293: if (isWall) {
294: midVal += offsets[(int) (val & OFFSET_MASK)]; // wall time
295: }
296:
297: if (midVal < date) {
298: low = mid + 1;
299: } else if (midVal > date) {
300: high = mid - 1;
301: } else {
302: return mid;
303: }
304: }
305:
306: // if beyond the transitions, returns that index.
307: if (low >= transitions.length) {
308: return low;
309: }
310: return low - 1;
311: }
312:
313: /**
314: * Returns the difference in milliseconds between local time and
315: * UTC, taking into account both the raw offset and the effect of
316: * daylight savings, for the specified date and time. This method
317: * assumes that the start and end month are distinct. This method
318: * assumes a Gregorian calendar for calculations.
319: * <p>
320: * <em>Note: In general, clients should use
321: * {@link Calendar#ZONE_OFFSET Calendar.get(ZONE_OFFSET)} +
322: * {@link Calendar#DST_OFFSET Calendar.get(DST_OFFSET)}
323: * instead of calling this method.</em>
324: *
325: * @param era The era of the given date. The value must be either
326: * GregorianCalendar.AD or GregorianCalendar.BC.
327: * @param year The year in the given date.
328: * @param month The month in the given date. Month is 0-based. e.g.,
329: * 0 for January.
330: * @param day The day-in-month of the given date.
331: * @param dayOfWeek The day-of-week of the given date.
332: * @param millis The milliseconds in day in <em>standard</em> local time.
333: * @return The milliseconds to add to UTC to get local time.
334: */
335: public int getOffset(int era, int year, int month, int day,
336: int dayOfWeek, int milliseconds) {
337: if (milliseconds < 0 || milliseconds >= Gregorian.ONE_DAY) {
338: throw new IllegalArgumentException();
339: }
340:
341: if (era == GregorianCalendar.BC) { // BC
342: year = 1 - year;
343: } else if (era != GregorianCalendar.AD) {
344: throw new IllegalArgumentException();
345: }
346:
347: CalendarDate date = new CalendarDate(year, month, day);
348: if (Gregorian.validate(date) == false) {
349: throw new IllegalArgumentException();
350: }
351:
352: // bug-for-bug compatible argument checking
353: if (dayOfWeek < GregorianCalendar.SUNDAY
354: || dayOfWeek > GregorianCalendar.SATURDAY) {
355: throw new IllegalArgumentException();
356: }
357:
358: if (transitions == null) {
359: return getLastRawOffset();
360: }
361:
362: long dateInMillis = Gregorian.dateToMillis(date) + milliseconds;
363: dateInMillis -= (long) rawOffset; // make it UTC
364: return getOffsets(dateInMillis, null, false);
365: }
366:
367: /**
368: * Sets the base time zone offset from GMT. This operation
369: * modifies all the transitions of this ZoneInfo object, including
370: * historical ones, if applicable.
371: *
372: * @param offsetMillis the base time zone offset to GMT.
373: * @see getRawOffset
374: */
375: public synchronized void setRawOffset(int offsetMillis) {
376: rawOffsetDiff = offsetMillis - rawOffset;
377: if (lastRule != null) {
378: lastRule.setRawOffset(offsetMillis);
379: }
380: }
381:
382: /**
383: * Returns the GMT offset of the current date. This GMT offset
384: * value is not modified during Daylight Saving Time.
385: *
386: * @return the GMT offset value in milliseconds to add to UTC time
387: * to get local standard time
388: */
389: public int getRawOffset() {
390: if (!willGMTOffsetChange) {
391: return rawOffset + rawOffsetDiff;
392: }
393:
394: int[] offsets = new int[2];
395: getOffsets(System.currentTimeMillis(), offsets, false);
396: return offsets[0];
397: }
398:
399: private int getLastRawOffset() {
400: return rawOffset + rawOffsetDiff;
401: }
402:
403: /**
404: * Queries if this time zone uses Daylight Saving Time in the last known rule.
405: */
406: public boolean useDaylightTime() {
407: return (simpleTimeZoneParams != null);
408: }
409:
410: /**
411: * Queries if the specified date is in Daylight Saving Time.
412: */
413: public boolean inDaylightTime(Date date) {
414: if (date == null) {
415: throw new NullPointerException();
416: }
417:
418: if (transitions == null) {
419: return false;
420: }
421:
422: long utc = date.getTime() - rawOffsetDiff;
423: int index = getTransitionIndex(utc, false);
424:
425: // before transitions in the transition table
426: if (index < 0) {
427: return false;
428: }
429:
430: // the time is in the table range.
431: if (index < transitions.length) {
432: return (transitions[index] & DST_MASK) != 0;
433: }
434:
435: // beyond the transition table
436: SimpleTimeZone tz = getLastRule();
437: if (tz != null) {
438: return tz.inDaylightTime(date);
439: }
440: return false;
441: }
442:
443: /**
444: * Returns the amount of time in milliseconds that the clock is advanced
445: * during daylight saving time is in effect in its last daylight saving time rule.
446: *
447: * @return the number of milliseconds the time is advanced with respect to
448: * standard time when daylight saving time is in effect.
449: */
450: public int getDSTSavings() {
451: return dstSavings;
452: }
453:
454: // /**
455: // * @return the last year in the transition table or -1 if this
456: // * time zone doesn't observe any daylight saving time.
457: // */
458: // public int getMaxTransitionYear() {
459: // if (transitions == null) {
460: // return -1;
461: // }
462: // long val = transitions[transitions.length - 1];
463: // int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff;
464: // val = (val >> TRANSITION_NSHIFT) + offset;
465: // CalendarDate lastDate = Gregorian.getCalendarDate(val);
466: // return lastDate.getYear();
467: // }
468:
469: /**
470: * Returns a string representation of this time zone.
471: * @return the string
472: */
473: public String toString() {
474: return getClass().getName() + "[id=\"" + getID() + "\""
475: + ",offset=" + getLastRawOffset() + ",dstSavings="
476: + dstSavings + ",useDaylight=" + useDaylightTime()
477: + ",transitions="
478: + ((transitions != null) ? transitions.length : 0)
479: + ",lastRule=" + getLastRuleInstance() + "]";
480: }
481:
482: /**
483: * Gets all available IDs supported in the Java run-time.
484: *
485: * @return an array of time zone IDs.
486: */
487: public static String[] getAvailableIDs() {
488: return ZoneInfoFile.getZoneIDs();
489: }
490:
491: /**
492: * Gets all available IDs that have the same value as the
493: * specified raw GMT offset.
494: *
495: * @param rawOffset the GMT offset in milliseconds. This
496: * value should not include any daylight saving time.
497: *
498: * @return an array of time zone IDs.
499: */
500: public static String[] getAvailableIDs(int rawOffset) {
501: String[] result;
502: ArrayList matched = new ArrayList();
503: String[] IDs = getAvailableIDs();
504: int[] rawOffsets = ZoneInfoFile.getRawOffsets();
505:
506: loop: for (int index = 0; index < rawOffsets.length; index++) {
507: if (rawOffsets[index] == rawOffset) {
508: byte[] indices = ZoneInfoFile.getRawOffsetIndices();
509: for (int i = 0; i < indices.length; i++) {
510: if (indices[i] == index) {
511: matched.add(IDs[i++]);
512: while (i < indices.length
513: && indices[i] == index) {
514: matched.add(IDs[i++]);
515: }
516: break loop;
517: }
518: }
519: }
520: }
521: result = new String[matched.size()];
522: matched.toArray(result);
523:
524: return result;
525: }
526:
527: /**
528: * Gets the ZoneInfo for the given ID.
529: *
530: * @param ID the ID for a ZoneInfo. See TimeZone for detail.
531: *
532: * @return the specified ZoneInfo object or null if there is no
533: * time zone of the ID.
534: */
535: public static TimeZone getTimeZone(String ID) {
536: ZoneInfo zi = null;
537:
538: zi = ZoneInfoFile.getZoneInfo(ID);
539: if (zi == null) {
540: // if we can't create an object for the ID, try aliases.
541: try {
542: HashMap map = getAliasTable();
543: String alias = ID;
544: while ((alias = (String) map.get(alias)) != null) {
545: zi = ZoneInfoFile.getZoneInfo(alias);
546: if (zi != null) {
547: zi.setID(ID);
548: break;
549: }
550: }
551: } catch (Exception e) {
552: // ignore exceptions
553: }
554: }
555: return zi;
556: }
557:
558: private transient SimpleTimeZone lastRule;
559:
560: /**
561: * Returns a SimpleTimeZone object representing the last GMT
562: * offset and DST schedule or null if this time zone doesn't
563: * observe DST.
564: */
565: private synchronized SimpleTimeZone getLastRule() {
566: if (lastRule == null) {
567: lastRule = getLastRuleInstance();
568: }
569: return lastRule;
570: }
571:
572: /**
573: * Returns a SimpleTimeZone object that represents the last
574: * known daylight saving time rules.
575: *
576: * @return a SimpleTimeZone object or null if this time zone
577: * doesn't observe DST.
578: */
579: public SimpleTimeZone getLastRuleInstance() {
580: if (simpleTimeZoneParams == null) {
581: return null;
582: }
583: if (simpleTimeZoneParams.length == 10) {
584: return new SimpleTimeZone(getLastRawOffset(), getID(),
585: simpleTimeZoneParams[0], simpleTimeZoneParams[1],
586: simpleTimeZoneParams[2], simpleTimeZoneParams[3],
587: simpleTimeZoneParams[4], simpleTimeZoneParams[5],
588: simpleTimeZoneParams[6], simpleTimeZoneParams[7],
589: simpleTimeZoneParams[8], simpleTimeZoneParams[9],
590: dstSavings);
591: }
592: return new SimpleTimeZone(getLastRawOffset(), getID(),
593: simpleTimeZoneParams[0], simpleTimeZoneParams[1],
594: simpleTimeZoneParams[2], simpleTimeZoneParams[3],
595: simpleTimeZoneParams[4], simpleTimeZoneParams[5],
596: simpleTimeZoneParams[6], simpleTimeZoneParams[7],
597: dstSavings);
598: }
599:
600: /**
601: * Returns a hash code value calculated from the GMT offset and
602: * transitions.
603: * @return a hash code of this time zone
604: */
605: public int hashCode() {
606: return getLastRawOffset() ^ checksum;
607: }
608:
609: /**
610: * Compares the equity of two ZoneInfo objects.
611: *
612: * @param obj the object to be compared with
613: * @return true if given object is same as this ZoneInfo object,
614: * false otherwise.
615: */
616: public boolean equals(Object obj) {
617: if (this == obj) {
618: return true;
619: }
620: if (!(obj instanceof ZoneInfo)) {
621: return false;
622: }
623: ZoneInfo that = (ZoneInfo) obj;
624: return (getID().equals(that.getID())
625: && (getLastRawOffset() == that.getLastRawOffset()) && (checksum == that.checksum));
626: }
627:
628: /**
629: * Returns true if this zone has the same raw GMT offset value and
630: * transition table as another zone info. If the specified
631: * TimeZone object is not a ZoneInfo instance, this method returns
632: * true if the specified TimeZone object has the same raw GMT
633: * offset value with no daylight saving time.
634: *
635: * @param other the ZoneInfo object to be compared with
636: * @return true if the given <code>TimeZone</code> has the same
637: * GMT offset and transition information, false, otherwise.
638: */
639: public boolean hasSameRules(TimeZone other) {
640: if (this == other) {
641: return true;
642: }
643: if (other == null) {
644: return false;
645: }
646: if (!(other instanceof ZoneInfo)) {
647: if (getRawOffset() != other.getRawOffset()) {
648: return false;
649: }
650: // if both have the same raw offset and neither observes
651: // DST, they have the same rule.
652: if ((transitions == null) && (useDaylightTime() == false)
653: && (other.useDaylightTime() == false)) {
654: return true;
655: }
656: return false;
657: }
658: if (getLastRawOffset() != ((ZoneInfo) other).getLastRawOffset()) {
659: return false;
660: }
661: return (checksum == ((ZoneInfo) other).checksum);
662: }
663:
664: private transient static SoftReference aliasTable;
665:
666: private synchronized static HashMap getAliasTable() {
667: HashMap aliases = null;
668:
669: if (aliasTable != null) {
670: aliases = (HashMap) aliasTable.get();
671: if (aliases != null) {
672: return aliases;
673: }
674: }
675:
676: aliases = ZoneInfoFile.getZoneAliases();
677: if (aliases != null) {
678: aliasTable = new SoftReference(aliases);
679: }
680: return aliases;
681: }
682: }
|