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