TimeZone.java revision f20e96718a936499da309766da7f36f123b43d93
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.util; 19 20import java.io.IOException; 21import java.io.Serializable; 22import java.util.regex.Matcher; 23import java.util.regex.Pattern; 24import libcore.icu.TimeZones; 25import libcore.util.ZoneInfoDB; 26 27/** 28 * {@code TimeZone} represents a time zone, primarily used for configuring a {@link Calendar} or 29 * {@link java.text.SimpleDateFormat} instance. 30 * 31 * <p>Most applications will use {@link #getDefault} which returns a {@code TimeZone} based on 32 * the time zone where the program is running. 33 * 34 * <p>You can also get a specific {@code TimeZone} {@link #getTimeZone by Olson ID}. 35 * 36 * <p>It is highly unlikely you'll ever want to use anything but the factory methods yourself. 37 * Let classes like {@link Calendar} and {@link java.text.SimpleDateFormat} do the date 38 * computations for you. 39 * 40 * <p>If you do need to do date computations manually, there are two common cases to take into 41 * account: 42 * <ul> 43 * <li>Somewhere like California, where daylight time is used. 44 * The {@link #useDaylightTime} method will always return true, and {@link #inDaylightTime} 45 * must be used to determine whether or not daylight time applies to a given {@code Date}. 46 * The {@link #getRawOffset} method will return a raw offset of (in this case) -8 hours from UTC, 47 * which isn't usually very useful. More usefully, the {@link #getOffset} methods return the 48 * actual offset from UTC <i>for a given point in time</i>; this is the raw offset plus (if the 49 * point in time is {@link #inDaylightTime in daylight time}) the applicable 50 * {@link #getDSTSavings DST savings} (usually, but not necessarily, 1 hour). 51 * <li>Somewhere like Japan, where daylight time is not used. 52 * The {@link #useDaylightTime} and {@link #inDaylightTime} methods both always return false, 53 * and the raw and actual offsets will always be the same. 54 * </ul> 55 * 56 * <p>Note the type returned by the factory methods {@link #getDefault} and {@link #getTimeZone} is 57 * implementation dependent. This may introduce serialization incompatibility issues between 58 * different implementations, or different versions of Android. 59 * 60 * @see Calendar 61 * @see GregorianCalendar 62 * @see SimpleDateFormat 63 */ 64public abstract class TimeZone implements Serializable, Cloneable { 65 private static final long serialVersionUID = 3581463369166924961L; 66 67 private static final Pattern CUSTOM_ZONE_ID_PATTERN = Pattern.compile("^GMT[-+](\\d{1,2})(:?(\\d\\d))?$"); 68 69 /** 70 * The short display name style, such as {@code PDT}. Requests for this 71 * style may yield GMT offsets like {@code GMT-08:00}. 72 */ 73 public static final int SHORT = 0; 74 75 /** 76 * The long display name style, such as {@code Pacific Daylight Time}. 77 * Requests for this style may yield GMT offsets like {@code GMT-08:00}. 78 */ 79 public static final int LONG = 1; 80 81 private static final TimeZone GMT = new SimpleTimeZone(0, "GMT"); 82 private static final TimeZone UTC = new SimpleTimeZone(0, "UTC"); 83 84 private static TimeZone defaultTimeZone; 85 86 private String ID; 87 88 public TimeZone() {} 89 90 /** 91 * Returns a new time zone with the same ID, raw offset, and daylight 92 * savings time rules as this time zone. 93 */ 94 @Override public Object clone() { 95 try { 96 return super.clone(); 97 } catch (CloneNotSupportedException e) { 98 throw new AssertionError(e); 99 } 100 } 101 102 /** 103 * Returns the system's installed time zone IDs. Any of these IDs can be 104 * passed to {@link #getTimeZone} to lookup the corresponding time zone 105 * instance. 106 */ 107 public static synchronized String[] getAvailableIDs() { 108 return ZoneInfoDB.getAvailableIDs(); 109 } 110 111 /** 112 * Returns the IDs of the time zones whose offset from UTC is {@code 113 * offsetMillis}. Any of these IDs can be passed to {@link #getTimeZone} to 114 * lookup the corresponding time zone instance. 115 * 116 * @return a possibly-empty array. 117 */ 118 public static synchronized String[] getAvailableIDs(int offsetMillis) { 119 return ZoneInfoDB.getAvailableIDs(offsetMillis); 120 } 121 122 /** 123 * Returns the user's preferred time zone. This may have been overridden for 124 * this process with {@link #setDefault}. 125 * 126 * <p>Since the user's time zone changes dynamically, avoid caching this 127 * value. Instead, use this method to look it up for each use. 128 */ 129 public static synchronized TimeZone getDefault() { 130 if (defaultTimeZone == null) { 131 defaultTimeZone = ZoneInfoDB.getSystemDefault(); 132 } 133 return (TimeZone) defaultTimeZone.clone(); 134 } 135 136 /** 137 * Equivalent to {@code getDisplayName(false, TimeZone.LONG, Locale.getDefault())}. 138 * <a href="../util/Locale.html#default_locale">Be wary of the default locale</a>. 139 */ 140 public final String getDisplayName() { 141 return getDisplayName(false, LONG, Locale.getDefault()); 142 } 143 144 /** 145 * Equivalent to {@code getDisplayName(false, TimeZone.LONG, locale)}. 146 */ 147 public final String getDisplayName(Locale locale) { 148 return getDisplayName(false, LONG, locale); 149 } 150 151 /** 152 * Equivalent to {@code getDisplayName(daylightTime, style, Locale.getDefault())}. 153 * <a href="../util/Locale.html#default_locale">Be wary of the default locale</a>. 154 */ 155 public final String getDisplayName(boolean daylightTime, int style) { 156 return getDisplayName(daylightTime, style, Locale.getDefault()); 157 } 158 159 /** 160 * Returns the {@link #SHORT short} or {@link #LONG long} name of this time 161 * zone with either standard or daylight time, as written in {@code locale}. 162 * If the name is not available, the result is in the format 163 * {@code GMT[+-]hh:mm}. 164 * 165 * @param daylightTime true for daylight time, false for standard time. 166 * @param style either {@link TimeZone#LONG} or {@link TimeZone#SHORT}. 167 * @param locale the display locale. 168 */ 169 public String getDisplayName(boolean daylightTime, int style, Locale locale) { 170 if (style != SHORT && style != LONG) { 171 throw new IllegalArgumentException("Bad style: " + style); 172 } 173 174 String[][] zoneStrings = TimeZones.getZoneStrings(locale); 175 String result = TimeZones.getDisplayName(zoneStrings, getID(), daylightTime, style); 176 if (result != null) { 177 return result; 178 } 179 180 // If we get here, it's because icu4c has nothing for us. Most commonly, this is in the 181 // case of short names. For Pacific/Fiji, for example, icu4c has nothing better to offer 182 // than "GMT+12:00". Why do we re-do this work ourselves? Because we have up-to-date 183 // time zone transition data, which icu4c _doesn't_ use --- it uses its own baked-in copy, 184 // which only gets updated when we update icu4c. http://b/7955614 and http://b/8026776. 185 186 // TODO: should we generate these once, in TimeZones.getDisplayName? Revisit when we 187 // upgrade to icu4c 50 and rewrite the underlying native code. 188 189 int offset = getRawOffset(); 190 if (daylightTime) { 191 offset += getDSTSavings(); 192 } 193 offset /= 60000; 194 char sign = '+'; 195 if (offset < 0) { 196 sign = '-'; 197 offset = -offset; 198 } 199 StringBuilder builder = new StringBuilder(9); 200 builder.append("GMT"); 201 builder.append(sign); 202 appendNumber(builder, 2, offset / 60); 203 builder.append(':'); 204 appendNumber(builder, 2, offset % 60); 205 return builder.toString(); 206 } 207 208 private void appendNumber(StringBuilder builder, int count, int value) { 209 String string = Integer.toString(value); 210 for (int i = 0; i < count - string.length(); i++) { 211 builder.append('0'); 212 } 213 builder.append(string); 214 } 215 216 /** 217 * Returns the ID of this {@code TimeZone}, such as 218 * {@code America/Los_Angeles}, {@code GMT-08:00} or {@code UTC}. 219 */ 220 public String getID() { 221 return ID; 222 } 223 224 /** 225 * Returns the latest daylight savings in milliseconds for this time zone, relative 226 * to this time zone's regular UTC offset (as returned by {@link #getRawOffset}). 227 * 228 * <p>This class returns {@code 3600000} (1 hour) for time zones 229 * that use daylight savings time and {@code 0} for timezones that do not, 230 * leaving it to subclasses to override this method for other daylight savings 231 * offsets. (There are time zones, such as {@code Australia/Lord_Howe}, 232 * that use other values.) 233 * 234 * <p>Note that this method doesn't tell you whether or not to <i>apply</i> the 235 * offset: you need to call {@code inDaylightTime} for the specific time 236 * you're interested in. If this method returns a non-zero offset, that only 237 * tells you that this {@code TimeZone} sometimes observes daylight savings. 238 * 239 * <p>Note also that this method doesn't necessarily return the value you need 240 * to apply to the time you're working with. This value can and does change over 241 * time for a given time zone. 242 * 243 * <p>It's highly unlikely that you should ever call this method. You 244 * probably want {@link #getOffset} instead, which tells you the offset 245 * for a specific point in time, and takes daylight savings into account for you. 246 */ 247 public int getDSTSavings() { 248 return useDaylightTime() ? 3600000 : 0; 249 } 250 251 /** 252 * Returns the offset in milliseconds from UTC for this time zone at {@code 253 * time}. The offset includes daylight savings time if the specified 254 * date is within the daylight savings time period. 255 * 256 * @param time the date in milliseconds since January 1, 1970 00:00:00 UTC 257 */ 258 public int getOffset(long time) { 259 if (inDaylightTime(new Date(time))) { 260 return getRawOffset() + getDSTSavings(); 261 } 262 return getRawOffset(); 263 } 264 265 /** 266 * Returns this time zone's offset in milliseconds from UTC at the specified 267 * date and time. The offset includes daylight savings time if the date 268 * and time is within the daylight savings time period. 269 * 270 * <p>This method is intended to be used by {@link Calendar} to compute 271 * {@link Calendar#DST_OFFSET} and {@link Calendar#ZONE_OFFSET}. Application 272 * code should have no reason to call this method directly. Each parameter 273 * is interpreted in the same way as the corresponding {@code Calendar} 274 * field. Refer to {@link Calendar} for specific definitions of this 275 * method's parameters. 276 */ 277 public abstract int getOffset(int era, int year, int month, int day, 278 int dayOfWeek, int timeOfDayMillis); 279 280 /** 281 * Returns the offset in milliseconds from UTC of this time zone's standard 282 * time. 283 */ 284 public abstract int getRawOffset(); 285 286 /** 287 * Returns a {@code TimeZone} corresponding to the given {@code id}, or {@code GMT} 288 * for unknown ids. 289 * 290 * <p>An ID can be an Olson name of the form <i>Area</i>/<i>Location</i>, such 291 * as {@code America/Los_Angeles}. The {@link #getAvailableIDs} method returns 292 * the supported names. 293 * 294 * <p>This method can also create a custom {@code TimeZone} given an ID with the following 295 * syntax: {@code GMT[+|-]hh[[:]mm]}. For example, {@code "GMT+05:00"}, {@code "GMT+0500"}, 296 * {@code "GMT+5:00"}, {@code "GMT+500"}, {@code "GMT+05"}, and {@code "GMT+5"} all return 297 * an object with a raw offset of +5 hours from UTC, and which does <i>not</i> use daylight 298 * savings. These are rarely useful, because they don't correspond to time zones actually 299 * in use by humans. 300 * 301 * <p>Other than the special cases "UTC" and "GMT" (which are synonymous in this context, 302 * both corresponding to UTC), Android does not support the deprecated three-letter time 303 * zone IDs used in Java 1.1. 304 */ 305 public static synchronized TimeZone getTimeZone(String id) { 306 if (id == null) { 307 throw new NullPointerException("id == null"); 308 } 309 310 // Special cases? These can clone an existing instance. 311 // TODO: should we just add a cache to ZoneInfoDB instead? 312 if (id.length() == 3) { 313 if (id.equals("GMT")) { 314 return (TimeZone) GMT.clone(); 315 } 316 if (id.equals("UTC")) { 317 return (TimeZone) UTC.clone(); 318 } 319 } 320 321 // In the database? 322 TimeZone zone = null; 323 try { 324 zone = ZoneInfoDB.makeTimeZone(id); 325 } catch (IOException ignored) { 326 } 327 328 // Custom time zone? 329 if (zone == null && id.length() > 3 && id.startsWith("GMT")) { 330 zone = getCustomTimeZone(id); 331 } 332 333 // We never return null; on failure we return the equivalent of "GMT". 334 return (zone != null) ? zone : (TimeZone) GMT.clone(); 335 } 336 337 /** 338 * Returns a new SimpleTimeZone for an ID of the form "GMT[+|-]hh[[:]mm]", or null. 339 */ 340 private static TimeZone getCustomTimeZone(String id) { 341 Matcher m = CUSTOM_ZONE_ID_PATTERN.matcher(id); 342 if (!m.matches()) { 343 return null; 344 } 345 346 int hour; 347 int minute = 0; 348 try { 349 hour = Integer.parseInt(m.group(1)); 350 if (m.group(3) != null) { 351 minute = Integer.parseInt(m.group(3)); 352 } 353 } catch (NumberFormatException impossible) { 354 throw new AssertionError(impossible); 355 } 356 357 if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { 358 return null; 359 } 360 361 char sign = id.charAt(3); 362 int raw = (hour * 3600000) + (minute * 60000); 363 if (sign == '-') { 364 raw = -raw; 365 } 366 367 String cleanId = String.format("GMT%c%02d:%02d", sign, hour, minute); 368 return new SimpleTimeZone(raw, cleanId); 369 } 370 371 /** 372 * Returns true if {@code timeZone} has the same rules as this time zone. 373 * 374 * <p>The base implementation returns true if both time zones have the same 375 * raw offset. 376 */ 377 public boolean hasSameRules(TimeZone timeZone) { 378 if (timeZone == null) { 379 return false; 380 } 381 return getRawOffset() == timeZone.getRawOffset(); 382 } 383 384 /** 385 * Returns true if {@code time} is in a daylight savings time period for 386 * this time zone. 387 */ 388 public abstract boolean inDaylightTime(Date time); 389 390 /** 391 * Overrides the default time zone for the current process only. 392 * 393 * <p><strong>Warning</strong>: avoid using this method to use a custom time 394 * zone in your process. This value may be cleared or overwritten at any 395 * time, which can cause unexpected behavior. Instead, manually supply a 396 * custom time zone as needed. 397 * 398 * @param timeZone a custom time zone, or {@code null} to set the default to 399 * the user's preferred value. 400 */ 401 public static synchronized void setDefault(TimeZone timeZone) { 402 defaultTimeZone = timeZone != null ? (TimeZone) timeZone.clone() : null; 403 } 404 405 /** 406 * Sets the ID of this {@code TimeZone}. 407 */ 408 public void setID(String id) { 409 if (id == null) { 410 throw new NullPointerException("id == null"); 411 } 412 ID = id; 413 } 414 415 /** 416 * Sets the offset in milliseconds from UTC of this time zone's standard 417 * time. 418 */ 419 public abstract void setRawOffset(int offsetMillis); 420 421 /** 422 * Returns true if this time zone has a future transition to or from 423 * daylight savings time. 424 * 425 * <p><strong>Warning:</strong> this returns false for time zones like 426 * {@code Asia/Kuala_Lumpur} that have previously used DST but do not 427 * currently. A hypothetical country that has never observed daylight 428 * savings before but plans to start next year would return true. 429 * 430 * <p><strong>Warning:</strong> this returns true for time zones that use 431 * DST, even when it is not active. 432 * 433 * <p>Use {@link #inDaylightTime} to find out whether daylight savings is 434 * in effect at a specific time. 435 * 436 * <p>Most applications should not use this method. 437 */ 438 public abstract boolean useDaylightTime(); 439} 440