001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.util;
019:
020: import java.io.Serializable;
021: import java.security.AccessController;
022: import java.security.PrivilegedAction;
023:
024: import org.apache.harmony.luni.util.PriviAction;
025:
026: /**
027: * TimeZone is an abstract class which represents a local time zone and its
028: * daylight savings time rules. Subclasses support a particular calendar type,
029: * such as the gregorian calendar.
030: *
031: * Please note the type returned by factory methods, i.e.
032: * <code>getDefault()</code> and <code>getTimeZone(String)</code>, is
033: * implementation dependent, so that it may introduce serialization
034: * incompatibility issue between different implementations. Harmony returns
035: * instance of {@link SimpleTimeZone SimpleTimeZone} so that the bytes
036: * serialized by Harmony can be deserialized on other implementation
037: * successfully, but the reverse compatibility cannot be guaranteed.
038: *
039: * @see GregorianCalendar
040: * @see SimpleTimeZone
041: */
042:
043: public abstract class TimeZone implements Serializable, Cloneable {
044: private static final long serialVersionUID = 3581463369166924961L;
045:
046: /**
047: * The SHORT display name style.
048: */
049: public static final int SHORT = 0;
050:
051: /**
052: * The LONG display name style.
053: */
054: public static final int LONG = 1;
055:
056: private static HashMap<String, TimeZone> AvailableZones;
057:
058: private static TimeZone Default;
059:
060: static TimeZone GMT = new SimpleTimeZone(0, "GMT"); // Greenwich Mean Time
061:
062: private String ID;
063:
064: private com.ibm.icu.util.TimeZone icuTimeZone = null;
065:
066: private static void initializeAvailable() {
067: TimeZone[] zones = TimeZones.getTimeZones();
068: AvailableZones = new HashMap<String, TimeZone>(
069: (zones.length + 1) * 4 / 3);
070: AvailableZones.put(GMT.getID(), GMT);
071: for (int i = 0; i < zones.length; i++) {
072: AvailableZones.put(zones[i].getID(), zones[i]);
073: }
074: }
075:
076: /**
077: * Constructs a new instance of this class.
078: *
079: */
080: public TimeZone() {
081: }
082:
083: /**
084: * Answers a new TimeZone with the same ID, rawOffset and daylight savings
085: * time rules as this TimeZone.
086: *
087: * @return a shallow copy of this TimeZone
088: *
089: * @see java.lang.Cloneable
090: */
091: @Override
092: public Object clone() {
093: try {
094: TimeZone zone = (TimeZone) super .clone();
095: return zone;
096: } catch (CloneNotSupportedException e) {
097: return null;
098: }
099: }
100:
101: /**
102: * Gets the available time zone IDs.
103: *
104: * @return an array of time zone ID strings
105: */
106: public static synchronized String[] getAvailableIDs() {
107: if (AvailableZones == null) {
108: initializeAvailable();
109: }
110: int length = AvailableZones.size();
111: String[] answer = new String[length];
112: Iterator<String> keys = AvailableZones.keySet().iterator();
113: for (int i = 0; i < length; i++) {
114: answer[i] = keys.next();
115: }
116: return answer;
117: }
118:
119: /**
120: * Gets the available time zone IDs which match the specified offset from
121: * GMT.
122: *
123: * @param offset
124: * the offset from GMT in milliseconds
125: * @return an array of time zone ID strings
126: */
127: public static synchronized String[] getAvailableIDs(int offset) {
128: if (AvailableZones == null) {
129: initializeAvailable();
130: }
131: int count = 0, length = AvailableZones.size();
132: String[] all = new String[length];
133: Iterator<TimeZone> zones = AvailableZones.values().iterator();
134: for (int i = 0; i < length; i++) {
135: TimeZone tz = zones.next();
136: if (tz.getRawOffset() == offset) {
137: all[count++] = tz.getID();
138: }
139: }
140: String[] answer = new String[count];
141: System.arraycopy(all, 0, answer, 0, count);
142: return answer;
143: }
144:
145: /**
146: * Gets the default time zone.
147: *
148: * @return the default time zone
149: */
150: public static synchronized TimeZone getDefault() {
151: if (Default == null) {
152: setDefault(null);
153: }
154: return (TimeZone) Default.clone();
155: }
156:
157: /**
158: * Gets the LONG name for this TimeZone for the default Locale in standard
159: * time. If the name is not available, the result is in the format
160: * GMT[+-]hh:mm.
161: *
162: * @return the TimeZone name
163: */
164: public final String getDisplayName() {
165: return getDisplayName(false, LONG, Locale.getDefault());
166: }
167:
168: /**
169: * Gets the LONG name for this TimeZone for the specified Locale in standard
170: * time. If the name is not available, the result is in the format
171: * GMT[+-]hh:mm.
172: *
173: * @param locale
174: * the Locale
175: * @return the TimeZone name
176: */
177: public final String getDisplayName(Locale locale) {
178: return getDisplayName(false, LONG, locale);
179: }
180:
181: /**
182: * Gets the specified style of name (LONG or SHORT) for this TimeZone for
183: * the default Locale in either standard or daylight time as specified. If
184: * the name is not available, the result is in the format GMT[+-]hh:mm.
185: *
186: * @param daylightTime
187: * true for daylight time, false for standard time
188: * @param style
189: * Either LONG or SHORT
190: * @return the TimeZone name
191: */
192: public final String getDisplayName(boolean daylightTime, int style) {
193: return getDisplayName(daylightTime, style, Locale.getDefault());
194: }
195:
196: /**
197: * Gets the specified style of name (LONG or SHORT) for this TimeZone for
198: * the specified Locale in either standard or daylight time as specified. If
199: * the name is not available, the result is in the format GMT[+-]hh:mm.
200: *
201: * @param daylightTime
202: * true for daylight time, false for standard time
203: * @param style
204: * Either LONG or SHORT
205: * @param locale
206: * the Locale
207: * @return the TimeZone name
208: */
209: public String getDisplayName(boolean daylightTime, int style,
210: Locale locale) {
211: if (icuTimeZone == null || !ID.equals(icuTimeZone.getID())) {
212: icuTimeZone = com.ibm.icu.util.TimeZone.getTimeZone(ID);
213: }
214: return icuTimeZone.getDisplayName(daylightTime, style, locale);
215: }
216:
217: /**
218: * Gets the ID of this TimeZone.
219: *
220: * @return the time zone ID string
221: */
222: public String getID() {
223: return ID;
224: }
225:
226: /**
227: * Gets the daylight savings offset in milliseconds for this TimeZone.
228: * <p>
229: * This implementation returns 3600000 (1 hour), or 0 if the time zone does
230: * not observe daylight savings.
231: * <p>
232: * Subclasses may override to return daylight savings values other than 1
233: * hour.
234: * <p>
235: *
236: * @return the daylight savings offset in milliseconds if this TimeZone
237: * observes daylight savings, zero otherwise.
238: *
239: */
240: public int getDSTSavings() {
241: if (useDaylightTime()) {
242: return 3600000;
243: }
244: return 0;
245: }
246:
247: /**
248: * Gets the offset from GMT of this TimeZone for the specified date. The
249: * offset includes daylight savings time if the specified date is within the
250: * daylight savings time period.
251: *
252: * @param time
253: * the date in milliseconds since January 1, 1970 00:00:00 GMT
254: * @return the offset from GMT in milliseconds
255: */
256: public int getOffset(long time) {
257: if (inDaylightTime(new Date(time))) {
258: return getRawOffset() + getDSTSavings();
259: }
260: return getRawOffset();
261: }
262:
263: /**
264: * Gets the offset from GMT of this TimeZone for the specified date and
265: * time. The offset includes daylight savings time if the specified date and
266: * time are within the daylight savings time period.
267: *
268: * @param era
269: * the GregorianCalendar era, either GregorianCalendar.BC or
270: * GregorianCalendar.AD
271: * @param year
272: * the year
273: * @param month
274: * the Calendar month
275: * @param day
276: * the day of the month
277: * @param dayOfWeek
278: * the Calendar day of the week
279: * @param time
280: * the time of day in milliseconds
281: * @return the offset from GMT in milliseconds
282: */
283: abstract public int getOffset(int era, int year, int month,
284: int day, int dayOfWeek, int time);
285:
286: /**
287: * Gets the offset for standard time from GMT for this TimeZone.
288: *
289: * @return the offset from GMT in milliseconds
290: */
291: abstract public int getRawOffset();
292:
293: /**
294: * Gets the time zone with the specified ID.
295: *
296: * @param name
297: * a time zone string ID
298: * @return the time zone with the specified ID or null if a time zone with
299: * the specified ID does not exist
300: */
301: public static synchronized TimeZone getTimeZone(String name) {
302: if (AvailableZones == null) {
303: initializeAvailable();
304: }
305:
306: TimeZone zone = AvailableZones.get(name);
307: if (zone == null) {
308: if (name.startsWith("GMT") && name.length() > 3) {
309: char sign = name.charAt(3);
310: if (sign == '+' || sign == '-') {
311: int[] position = new int[1];
312: String formattedName = formatTimeZoneName(name, 4);
313: int hour = parseNumber(formattedName, 4, position);
314: if (hour < 0 || hour > 23) {
315: return (TimeZone) GMT.clone();
316: }
317: int index = position[0];
318: if (index != -1) {
319: int raw = hour * 3600000;
320: if (index < formattedName.length()
321: && formattedName.charAt(index) == ':') {
322: int minute = parseNumber(formattedName,
323: index + 1, position);
324: if (position[0] == -1 || minute < 0
325: || minute > 59) {
326: return (TimeZone) GMT.clone();
327: }
328: raw += minute * 60000;
329: } else if (hour >= 30 || index > 6) {
330: raw = (hour / 100 * 3600000)
331: + (hour % 100 * 60000);
332: }
333: if (sign == '-') {
334: raw = -raw;
335: }
336: return new SimpleTimeZone(raw, formattedName);
337: }
338: }
339: }
340: zone = GMT;
341: }
342: return (TimeZone) zone.clone();
343: }
344:
345: private static String formatTimeZoneName(String name, int offset) {
346: StringBuffer buf = new StringBuffer();
347: int index = offset, length = name.length();
348: buf.append(name.substring(0, offset));
349:
350: while (index < length) {
351: if (Character.digit(name.charAt(index), 10) != -1) {
352: buf.append(name.charAt(index));
353: if ((length - (index + 1)) == 2) {
354: buf.append(':');
355: }
356: } else if (name.charAt(index) == ':') {
357: buf.append(':');
358: }
359: index++;
360: }
361:
362: if (buf.toString().indexOf(":") == -1) {
363: buf.append(':');
364: buf.append("00");
365: }
366:
367: if (buf.toString().indexOf(":") == 5) {
368: buf.insert(4, '0');
369: }
370:
371: return buf.toString();
372: }
373:
374: /**
375: * Answers if the specified TimeZone has the same raw offset as this
376: * TimeZone.
377: *
378: * @param zone
379: * a TimeZone
380: * @return true when the TimeZones have the same raw offset, false otherwise
381: */
382: public boolean hasSameRules(TimeZone zone) {
383: if (zone == null) {
384: return false;
385: }
386: return getRawOffset() == zone.getRawOffset();
387: }
388:
389: /**
390: * Answers if the specified Date is in the daylight savings time period for
391: * this TimeZone.
392: *
393: * @param time
394: * a Date
395: * @return true when the Date is in the daylight savings time period, false
396: * otherwise
397: */
398: abstract public boolean inDaylightTime(Date time);
399:
400: private static int parseNumber(String string, int offset,
401: int[] position) {
402: int index = offset, length = string.length(), digit, result = 0;
403: while (index < length
404: && (digit = Character.digit(string.charAt(index), 10)) != -1) {
405: index++;
406: result = result * 10 + digit;
407: }
408: position[0] = index == offset ? -1 : index;
409: return result;
410: }
411:
412: /**
413: * Sets the default time zone.
414: *
415: * @param timezone
416: * a TimeZone object
417: */
418: public static synchronized void setDefault(TimeZone timezone) {
419: if (timezone != null) {
420: setICUDefaultTimeZone(timezone);
421: Default = timezone;
422: return;
423: }
424:
425: String zone = AccessController
426: .doPrivileged(new PriviAction<String>("user.timezone"));
427:
428: // sometimes DRLVM incorrectly adds "\n" to the end of timezone ID
429: if (zone != null && zone.contains("\n")) {
430: zone = zone.substring(0, zone.indexOf("\n"));
431: }
432:
433: // if property user.timezone is not set, we call the native method
434: // getCustomTimeZone
435: if (zone == null || zone.length() == 0) {
436: int[] tzinfo = new int[10];
437: boolean[] isCustomTimeZone = new boolean[1];
438:
439: String zoneId = getCustomTimeZone(tzinfo, isCustomTimeZone);
440:
441: // if returned TimeZone is a user customized TimeZone
442: if (isCustomTimeZone[0]) {
443: // build a new SimpleTimeZone
444: switch (tzinfo[1]) {
445: case 0:
446: // does not observe DST
447: Default = new SimpleTimeZone(tzinfo[0], zoneId);
448: break;
449: default:
450: // observes DST
451: Default = new SimpleTimeZone(tzinfo[0], zoneId,
452: tzinfo[5], tzinfo[4], tzinfo[3], tzinfo[2],
453: tzinfo[9], tzinfo[8], tzinfo[7], tzinfo[6],
454: tzinfo[1]);
455: }
456: } else {
457: // get TimeZone
458: Default = getTimeZone(zoneId);
459: }
460: } else {
461: // if property user.timezone is set in command line (with -D option)
462: Default = getTimeZone(zone);
463: }
464: setICUDefaultTimeZone(Default);
465: }
466:
467: private static void setICUDefaultTimeZone(TimeZone timezone) {
468: final com.ibm.icu.util.TimeZone icuTZ = com.ibm.icu.util.TimeZone
469: .getTimeZone(timezone.getID());
470:
471: AccessController
472: .doPrivileged(new PrivilegedAction<java.lang.reflect.Field>() {
473: public java.lang.reflect.Field run() {
474: java.lang.reflect.Field field = null;
475: try {
476: field = com.ibm.icu.util.TimeZone.class
477: .getDeclaredField("defaultZone");
478: field.setAccessible(true);
479: field.set("defaultZone", icuTZ);
480: } catch (Exception e) {
481: return null;
482: }
483: return field;
484: }
485: });
486: }
487:
488: /**
489: * Sets the ID of this TimeZone.
490: *
491: * @param name
492: * a string which is the time zone ID
493: */
494: public void setID(String name) {
495: if (name == null) {
496: throw new NullPointerException();
497: }
498: ID = name;
499: }
500:
501: /**
502: * Sets the offset for standard time from GMT for this TimeZone.
503: *
504: * @param offset
505: * the offset from GMT in milliseconds
506: */
507: abstract public void setRawOffset(int offset);
508:
509: /**
510: * Answers if this TimeZone has a daylight savings time period.
511: *
512: * @return true if this time zone has a daylight savings time period, false
513: * otherwise
514: */
515: abstract public boolean useDaylightTime();
516:
517: /**
518: * Gets the name and the details of the user-selected TimeZone on the
519: * device.
520: *
521: * @param tzinfo
522: * int array of 10 elements to be filled with the TimeZone
523: * information. Once filled, the contents of the array are
524: * formatted as follows: tzinfo[0] -> the timezone offset;
525: * tzinfo[1] -> the dst adjustment; tzinfo[2] -> the dst start
526: * hour; tzinfo[3] -> the dst start day of week; tzinfo[4] -> the
527: * dst start week of month; tzinfo[5] -> the dst start month;
528: * tzinfo[6] -> the dst end hour; tzinfo[7] -> the dst end day of
529: * week; tzinfo[8] -> the dst end week of month; tzinfo[9] -> the
530: * dst end month;
531: * @param isCustomTimeZone
532: * boolean array of size 1 that indicates if a timezone match is
533: * found
534: * @return the name of the TimeZone or null if error occurs in native
535: * method.
536: */
537: private static native String getCustomTimeZone(int[] tzinfo,
538: boolean[] isCustomTimeZone);
539: }
|