blob: 562ae7ada90ab695d0d368a2dba62b5c87a5a196 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text.format;
18
Neil Fullerd7f08492014-06-25 11:13:25 +010019import android.util.TimeFormatException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020
Aurimas Liutikas4037d512016-10-11 17:20:06 -070021import libcore.util.ZoneInfo;
22import libcore.util.ZoneInfoDB;
23
Neil Fullerd7f08492014-06-25 11:13:25 +010024import java.io.IOException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import java.util.Locale;
26import java.util.TimeZone;
27
28/**
Scott Main31e04652011-09-28 19:14:40 -070029 * An alternative to the {@link java.util.Calendar} and
30 * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031 * a moment in time, specified with second precision. It is modelled after
Neil Fullerd7f08492014-06-25 11:13:25 +010032 * struct tm. This class is not thread-safe and does not consider leap seconds.
33 *
34 * <p>This class has a number of issues and it is recommended that
35 * {@link java.util.GregorianCalendar} is used instead.
36 *
37 * <p>Known issues:
38 * <ul>
39 * <li>For historical reasons when performing time calculations all arithmetic currently takes
40 * place using 32-bit integers. This limits the reliable time range representable from 1902
41 * until 2037.See the wikipedia article on the
42 * <a href="http://en.wikipedia.org/wiki/Year_2038_problem">Year 2038 problem</a> for details.
43 * Do not rely on this behavior; it may change in the future.
44 * </li>
45 * <li>Calling {@link #switchTimezone(String)} on a date that cannot exist, such as a wall time
46 * that was skipped due to a DST transition, will result in a date in 1969 (i.e. -1, or 1 second
47 * before 1st Jan 1970 UTC).</li>
48 * <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for
49 * use with non-ASCII scripts.</li>
Elliott Hughes5c955ee2015-03-27 15:18:12 -070050 * <li>No support for pseudo-zones like "GMT-07:00".</li>
Neil Fullerd7f08492014-06-25 11:13:25 +010051 * </ul>
Neil Fullerbbf88712014-10-23 11:57:06 +010052 *
53 * @deprecated Use {@link java.util.GregorianCalendar} instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 */
Neil Fullerbbf88712014-10-23 11:57:06 +010055@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056public class Time {
57 private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
58 private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
59 private static final String Y_M_D = "%Y-%m-%d";
Christian Mehlmauer0df10e92010-07-06 20:42:22 +020060
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 public static final String TIMEZONE_UTC = "UTC";
62
63 /**
64 * The Julian day of the epoch, that is, January 1, 1970 on the Gregorian
65 * calendar.
66 */
67 public static final int EPOCH_JULIAN_DAY = 2440588;
68
69 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080070 * The Julian day of the Monday in the week of the epoch, December 29, 1969
71 * on the Gregorian calendar.
72 */
73 public static final int MONDAY_BEFORE_JULIAN_EPOCH = EPOCH_JULIAN_DAY - 3;
74
75 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 * True if this is an allDay event. The hour, minute, second fields are
77 * all zero, and the date is displayed the same in all time zones.
78 */
79 public boolean allDay;
80
81 /**
82 * Seconds [0-61] (2 leap seconds allowed)
83 */
84 public int second;
85
86 /**
87 * Minute [0-59]
88 */
89 public int minute;
90
91 /**
92 * Hour of day [0-23]
93 */
94 public int hour;
95
96 /**
97 * Day of month [1-31]
98 */
99 public int monthDay;
100
101 /**
102 * Month [0-11]
103 */
104 public int month;
105
106 /**
Scott Main31e04652011-09-28 19:14:40 -0700107 * Year. For example, 1970.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 */
109 public int year;
110
111 /**
112 * Day of week [0-6]
113 */
114 public int weekDay;
115
116 /**
117 * Day of year [0-365]
118 */
119 public int yearDay;
120
121 /**
122 * This time is in daylight savings time. One of:
123 * <ul>
124 * <li><b>positive</b> - in dst</li>
125 * <li><b>0</b> - not in dst</li>
126 * <li><b>negative</b> - unknown</li>
127 * </ul>
128 */
129 public int isDst;
130
131 /**
Neil Fullerd7f08492014-06-25 11:13:25 +0100132 * Offset in seconds from UTC including any DST offset.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 */
134 public long gmtoff;
135
136 /**
137 * The timezone for this Time. Should not be null.
138 */
139 public String timezone;
140
141 /*
142 * Define symbolic constants for accessing the fields in this class. Used in
143 * getActualMaximum().
144 */
145 public static final int SECOND = 1;
146 public static final int MINUTE = 2;
147 public static final int HOUR = 3;
148 public static final int MONTH_DAY = 4;
149 public static final int MONTH = 5;
150 public static final int YEAR = 6;
151 public static final int WEEK_DAY = 7;
152 public static final int YEAR_DAY = 8;
153 public static final int WEEK_NUM = 9;
154
155 public static final int SUNDAY = 0;
156 public static final int MONDAY = 1;
157 public static final int TUESDAY = 2;
158 public static final int WEDNESDAY = 3;
159 public static final int THURSDAY = 4;
160 public static final int FRIDAY = 5;
161 public static final int SATURDAY = 6;
162
Neil Fullerd7f08492014-06-25 11:13:25 +0100163 // An object that is reused for date calculations.
164 private TimeCalculator calculator;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165
166 /**
167 * Construct a Time object in the timezone named by the string
168 * argument "timezone". The time is initialized to Jan 1, 1970.
Neil Fullerd7f08492014-06-25 11:13:25 +0100169 * @param timezoneId string containing the timezone to use.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 * @see TimeZone
171 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100172 public Time(String timezoneId) {
173 if (timezoneId == null) {
174 throw new NullPointerException("timezoneId is null!");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 }
Neil Fullerd7f08492014-06-25 11:13:25 +0100176 initialize(timezoneId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 }
178
179 /**
180 * Construct a Time object in the default timezone. The time is initialized to
181 * Jan 1, 1970.
182 */
183 public Time() {
Neil Fullerd7f08492014-06-25 11:13:25 +0100184 initialize(TimeZone.getDefault().getID());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 }
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200186
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 /**
188 * A copy constructor. Construct a Time object by copying the given
189 * Time object. No normalization occurs.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200190 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 * @param other
192 */
193 public Time(Time other) {
Neil Fullerd7f08492014-06-25 11:13:25 +0100194 initialize(other.timezone);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 set(other);
196 }
197
Neil Fullerd7f08492014-06-25 11:13:25 +0100198 /** Initialize the Time to 00:00:00 1/1/1970 in the specified timezone. */
199 private void initialize(String timezoneId) {
200 this.timezone = timezoneId;
201 this.year = 1970;
202 this.monthDay = 1;
203 // Set the daylight-saving indicator to the unknown value -1 so that
204 // it will be recomputed.
205 this.isDst = -1;
206
207 // A reusable object that performs the date/time calculations.
208 calculator = new TimeCalculator(timezoneId);
209 }
210
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 /**
212 * Ensures the values in each field are in range. For example if the
213 * current value of this calendar is March 32, normalize() will convert it
214 * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200215 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 * <p>
217 * If "ignoreDst" is true, then this method sets the "isDst" field to -1
218 * (the "unknown" value) before normalizing. It then computes the
Neil Fuller7079f202014-09-30 11:47:19 +0100219 * time in milliseconds and sets the correct value for "isDst" if the
220 * fields resolve to a valid date / time.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200221 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 * <p>
223 * See {@link #toMillis(boolean)} for more information about when to
Neil Fuller7079f202014-09-30 11:47:19 +0100224 * use <tt>true</tt> or <tt>false</tt> for "ignoreDst" and when {@code -1}
225 * might be returned.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200226 *
Neil Fuller7079f202014-09-30 11:47:19 +0100227 * @return the UTC milliseconds since the epoch, or {@code -1}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100229 public long normalize(boolean ignoreDst) {
230 calculator.copyFieldsFromTime(this);
231 long timeInMillis = calculator.toMillis(ignoreDst);
232 calculator.copyFieldsToTime(this);
233 return timeInMillis;
234 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235
236 /**
237 * Convert this time object so the time represented remains the same, but is
238 * instead located in a different timezone. This method automatically calls
Neil Fullerd7f08492014-06-25 11:13:25 +0100239 * normalize() in some cases.
240 *
241 * <p>This method can return incorrect results if the date / time cannot be normalized.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100243 public void switchTimezone(String timezone) {
244 calculator.copyFieldsFromTime(this);
245 calculator.switchTimeZone(timezone);
246 calculator.copyFieldsToTime(this);
247 this.timezone = timezone;
248 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249
250 private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31,
251 31, 30, 31, 30, 31 };
252
253 /**
254 * Return the maximum possible value for the given field given the value of
255 * the other fields. Requires that it be normalized for MONTH_DAY and
256 * YEAR_DAY.
257 * @param field one of the constants for HOUR, MINUTE, SECOND, etc.
258 * @return the maximum value for the field.
259 */
260 public int getActualMaximum(int field) {
261 switch (field) {
262 case SECOND:
263 return 59; // leap seconds, bah humbug
264 case MINUTE:
265 return 59;
266 case HOUR:
267 return 23;
268 case MONTH_DAY: {
269 int n = DAYS_PER_MONTH[this.month];
270 if (n != 28) {
271 return n;
272 } else {
273 int y = this.year;
274 return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29 : 28;
275 }
276 }
277 case MONTH:
278 return 11;
279 case YEAR:
280 return 2037;
281 case WEEK_DAY:
282 return 6;
283 case YEAR_DAY: {
284 int y = this.year;
285 // Year days are numbered from 0, so the last one is usually 364.
286 return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 365 : 364;
287 }
288 case WEEK_NUM:
289 throw new RuntimeException("WEEK_NUM not implemented");
290 default:
291 throw new RuntimeException("bad field=" + field);
292 }
293 }
294
295 /**
296 * Clears all values, setting the timezone to the given timezone. Sets isDst
297 * to a negative value to mean "unknown".
Neil Fullerd7f08492014-06-25 11:13:25 +0100298 * @param timezoneId the timezone to use.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100300 public void clear(String timezoneId) {
301 if (timezoneId == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 throw new NullPointerException("timezone is null!");
303 }
Neil Fullerd7f08492014-06-25 11:13:25 +0100304 this.timezone = timezoneId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 this.allDay = false;
306 this.second = 0;
307 this.minute = 0;
308 this.hour = 0;
309 this.monthDay = 0;
310 this.month = 0;
311 this.year = 0;
312 this.weekDay = 0;
313 this.yearDay = 0;
314 this.gmtoff = 0;
315 this.isDst = -1;
316 }
317
318 /**
Kenny Root467cabe2011-07-25 15:58:31 -0700319 * Compare two {@code Time} objects and return a negative number if {@code
320 * a} is less than {@code b}, a positive number if {@code a} is greater than
321 * {@code b}, or 0 if they are equal.
322 *
Neil Fuller7079f202014-09-30 11:47:19 +0100323 * <p>
324 * This method can return an incorrect answer when the date / time fields of
325 * either {@code Time} have been set to a local time that contradicts the
326 * available timezone information.
327 *
Kenny Root467cabe2011-07-25 15:58:31 -0700328 * @param a first {@code Time} instance to compare
329 * @param b second {@code Time} instance to compare
330 * @throws NullPointerException if either argument is {@code null}
331 * @throws IllegalArgumentException if {@link #allDay} is true but {@code
332 * hour}, {@code minute}, and {@code second} are not 0.
333 * @return a negative result if {@code a} is earlier, a positive result if
Trevor Johns682c24e2016-04-12 10:13:47 -0700334 * {@code b} is earlier, or 0 if they are equal.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 */
Kenny Root467cabe2011-07-25 15:58:31 -0700336 public static int compare(Time a, Time b) {
337 if (a == null) {
338 throw new NullPointerException("a == null");
339 } else if (b == null) {
340 throw new NullPointerException("b == null");
341 }
Neil Fullerd7f08492014-06-25 11:13:25 +0100342 a.calculator.copyFieldsFromTime(a);
343 b.calculator.copyFieldsFromTime(b);
Kenny Root467cabe2011-07-25 15:58:31 -0700344
Neil Fullerd7f08492014-06-25 11:13:25 +0100345 return TimeCalculator.compare(a.calculator, b.calculator);
Kenny Root467cabe2011-07-25 15:58:31 -0700346 }
347
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 /**
349 * Print the current value given the format string provided. See man
350 * strftime for what means what. The final string must be less than 256
351 * characters.
352 * @param format a string containing the desired format.
353 * @return a String containing the current time expressed in the current locale.
354 */
355 public String format(String format) {
Neil Fullerd7f08492014-06-25 11:13:25 +0100356 calculator.copyFieldsFromTime(this);
357 return calculator.format(format);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 }
359
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360 /**
Neil Fuller3d3d7f12018-01-17 21:13:21 +0000361 * Return the current time in YYYYMMDDTHHMMSS&lt;tz&gt; format
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 */
363 @Override
Neil Fullerd7f08492014-06-25 11:13:25 +0100364 public String toString() {
365 // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff
366 // happens during debugging when the debugger calls toString().
367 TimeCalculator calculator = new TimeCalculator(this.timezone);
368 calculator.copyFieldsFromTime(this);
369 return calculator.toStringInternal();
370 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371
372 /**
373 * Parses a date-time string in either the RFC 2445 format or an abbreviated
374 * format that does not include the "time" field. For example, all of the
375 * following strings are valid:
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200376 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 * <ul>
378 * <li>"20081013T160000Z"</li>
379 * <li>"20081013T160000"</li>
380 * <li>"20081013"</li>
381 * </ul>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200382 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 * Returns whether or not the time is in UTC (ends with Z). If the string
384 * ends with "Z" then the timezone is set to UTC. If the date-time string
385 * included only a date and no time field, then the <code>allDay</code>
386 * field of this Time class is set to true and the <code>hour</code>,
387 * <code>minute</code>, and <code>second</code> fields are set to zero;
388 * otherwise (a time field was included in the date-time string)
389 * <code>allDay</code> is set to false. The fields <code>weekDay</code>,
390 * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero,
391 * and the field <code>isDst</code> is set to -1 (unknown). To set those
392 * fields, call {@link #normalize(boolean)} after parsing.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200393 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 * To parse a date-time string and convert it to UTC milliseconds, do
395 * something like this:
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200396 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 * <pre>
398 * Time time = new Time();
399 * String date = "20081013T160000Z";
400 * time.parse(date);
401 * long millis = time.normalize(false);
402 * </pre>
403 *
404 * @param s the string to parse
405 * @return true if the resulting time value is in UTC time
406 * @throws android.util.TimeFormatException if s cannot be parsed.
407 */
408 public boolean parse(String s) {
Elliott Hughes5ef49422012-07-25 18:47:15 -0700409 if (s == null) {
410 throw new NullPointerException("time string is null");
411 }
Neil Fullerd7f08492014-06-25 11:13:25 +0100412 if (parseInternal(s)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 timezone = TIMEZONE_UTC;
414 return true;
415 }
416 return false;
417 }
418
419 /**
420 * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
421 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100422 private boolean parseInternal(String s) {
423 int len = s.length();
424 if (len < 8) {
425 throw new TimeFormatException("String is too short: \"" + s +
426 "\" Expected at least 8 characters.");
427 }
428
429 boolean inUtc = false;
430
431 // year
432 int n = getChar(s, 0, 1000);
433 n += getChar(s, 1, 100);
434 n += getChar(s, 2, 10);
435 n += getChar(s, 3, 1);
436 year = n;
437
438 // month
439 n = getChar(s, 4, 10);
440 n += getChar(s, 5, 1);
441 n--;
442 month = n;
443
444 // day of month
445 n = getChar(s, 6, 10);
446 n += getChar(s, 7, 1);
447 monthDay = n;
448
449 if (len > 8) {
450 if (len < 15) {
451 throw new TimeFormatException(
452 "String is too short: \"" + s
453 + "\" If there are more than 8 characters there must be at least"
454 + " 15.");
455 }
456 checkChar(s, 8, 'T');
457 allDay = false;
458
459 // hour
460 n = getChar(s, 9, 10);
461 n += getChar(s, 10, 1);
462 hour = n;
463
464 // min
465 n = getChar(s, 11, 10);
466 n += getChar(s, 12, 1);
467 minute = n;
468
469 // sec
470 n = getChar(s, 13, 10);
471 n += getChar(s, 14, 1);
472 second = n;
473
474 if (len > 15) {
475 // Z
476 checkChar(s, 15, 'Z');
477 inUtc = true;
478 }
479 } else {
480 allDay = true;
481 hour = 0;
482 minute = 0;
483 second = 0;
484 }
485
486 weekDay = 0;
487 yearDay = 0;
488 isDst = -1;
489 gmtoff = 0;
490 return inUtc;
491 }
492
493 private void checkChar(String s, int spos, char expected) {
494 char c = s.charAt(spos);
495 if (c != expected) {
496 throw new TimeFormatException(String.format(
497 "Unexpected character 0x%02d at pos=%d. Expected 0x%02d (\'%c\').",
498 (int) c, spos, (int) expected, expected));
499 }
500 }
501
502 private static int getChar(String s, int spos, int mul) {
503 char c = s.charAt(spos);
504 if (Character.isDigit(c)) {
505 return Character.getNumericValue(c) * mul;
506 } else {
507 throw new TimeFormatException("Parse error at pos=" + spos);
508 }
509 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510
511 /**
512 * Parse a time in RFC 3339 format. This method also parses simple dates
513 * (that is, strings that contain no time or time offset). For example,
514 * all of the following strings are valid:
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200515 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 * <ul>
517 * <li>"2008-10-13T16:00:00.000Z"</li>
518 * <li>"2008-10-13T16:00:00.000+07:00"</li>
519 * <li>"2008-10-13T16:00:00.000-07:00"</li>
520 * <li>"2008-10-13"</li>
521 * </ul>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200522 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523 * <p>
524 * If the string contains a time and time offset, then the time offset will
525 * be used to convert the time value to UTC.
526 * </p>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200527 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 * <p>
529 * If the given string contains just a date (with no time field), then
530 * the {@link #allDay} field is set to true and the {@link #hour},
531 * {@link #minute}, and {@link #second} fields are set to zero.
532 * </p>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200533 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 * <p>
535 * Returns true if the resulting time value is in UTC time.
536 * </p>
537 *
538 * @param s the string to parse
539 * @return true if the resulting time value is in UTC time
Ken Shirriff670bf112009-04-27 09:50:41 -0700540 * @throws android.util.TimeFormatException if s cannot be parsed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 */
542 public boolean parse3339(String s) {
Alon Albert11afa8a2012-01-26 14:25:19 -0800543 if (s == null) {
544 throw new NullPointerException("time string is null");
545 }
Neil Fullerd7f08492014-06-25 11:13:25 +0100546 if (parse3339Internal(s)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800547 timezone = TIMEZONE_UTC;
548 return true;
549 }
550 return false;
551 }
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200552
Neil Fullerd7f08492014-06-25 11:13:25 +0100553 private boolean parse3339Internal(String s) {
554 int len = s.length();
555 if (len < 10) {
556 throw new TimeFormatException("String too short --- expected at least 10 characters.");
557 }
558 boolean inUtc = false;
559
560 // year
561 int n = getChar(s, 0, 1000);
562 n += getChar(s, 1, 100);
563 n += getChar(s, 2, 10);
564 n += getChar(s, 3, 1);
565 year = n;
566
567 checkChar(s, 4, '-');
568
569 // month
570 n = getChar(s, 5, 10);
571 n += getChar(s, 6, 1);
572 --n;
573 month = n;
574
575 checkChar(s, 7, '-');
576
577 // day
578 n = getChar(s, 8, 10);
579 n += getChar(s, 9, 1);
580 monthDay = n;
581
582 if (len >= 19) {
583 // T
584 checkChar(s, 10, 'T');
585 allDay = false;
586
587 // hour
588 n = getChar(s, 11, 10);
589 n += getChar(s, 12, 1);
590
591 // Note that this.hour is not set here. It is set later.
592 int hour = n;
593
594 checkChar(s, 13, ':');
595
596 // minute
597 n = getChar(s, 14, 10);
598 n += getChar(s, 15, 1);
599 // Note that this.minute is not set here. It is set later.
600 int minute = n;
601
602 checkChar(s, 16, ':');
603
604 // second
605 n = getChar(s, 17, 10);
606 n += getChar(s, 18, 1);
607 second = n;
608
609 // skip the '.XYZ' -- we don't care about subsecond precision.
610
611 int tzIndex = 19;
612 if (tzIndex < len && s.charAt(tzIndex) == '.') {
613 do {
614 tzIndex++;
615 } while (tzIndex < len && Character.isDigit(s.charAt(tzIndex)));
616 }
617
618 int offset = 0;
619 if (len > tzIndex) {
620 char c = s.charAt(tzIndex);
621 // NOTE: the offset is meant to be subtracted to get from local time
622 // to UTC. we therefore use 1 for '-' and -1 for '+'.
623 switch (c) {
624 case 'Z':
625 // Zulu time -- UTC
626 offset = 0;
627 break;
628 case '-':
629 offset = 1;
630 break;
631 case '+':
632 offset = -1;
633 break;
634 default:
635 throw new TimeFormatException(String.format(
636 "Unexpected character 0x%02d at position %d. Expected + or -",
637 (int) c, tzIndex));
638 }
639 inUtc = true;
640
641 if (offset != 0) {
642 if (len < tzIndex + 6) {
643 throw new TimeFormatException(
644 String.format("Unexpected length; should be %d characters",
645 tzIndex + 6));
646 }
647
648 // hour
649 n = getChar(s, tzIndex + 1, 10);
650 n += getChar(s, tzIndex + 2, 1);
651 n *= offset;
652 hour += n;
653
654 // minute
655 n = getChar(s, tzIndex + 4, 10);
656 n += getChar(s, tzIndex + 5, 1);
657 n *= offset;
658 minute += n;
659 }
660 }
661 this.hour = hour;
662 this.minute = minute;
663
664 if (offset != 0) {
665 normalize(false);
666 }
667 } else {
668 allDay = true;
669 this.hour = 0;
670 this.minute = 0;
671 this.second = 0;
672 }
673
674 this.weekDay = 0;
675 this.yearDay = 0;
676 this.isDst = -1;
677 this.gmtoff = 0;
678 return inUtc;
679 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800680
681 /**
682 * Returns the timezone string that is currently set for the device.
683 */
684 public static String getCurrentTimezone() {
685 return TimeZone.getDefault().getID();
686 }
687
688 /**
689 * Sets the time of the given Time object to the current time.
690 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100691 public void setToNow() {
692 set(System.currentTimeMillis());
693 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694
695 /**
696 * Converts this time to milliseconds. Suitable for interacting with the
697 * standard java libraries. The time is in UTC milliseconds since the epoch.
698 * This does an implicit normalization to compute the milliseconds but does
699 * <em>not</em> change any of the fields in this Time object. If you want
700 * to normalize the fields in this Time object and also get the milliseconds
701 * then use {@link #normalize(boolean)}.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200702 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 * <p>
704 * If "ignoreDst" is false, then this method uses the current setting of the
705 * "isDst" field and will adjust the returned time if the "isDst" field is
706 * wrong for the given time. See the sample code below for an example of
707 * this.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200708 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 * <p>
710 * If "ignoreDst" is true, then this method ignores the current setting of
711 * the "isDst" field in this Time object and will instead figure out the
712 * correct value of "isDst" (as best it can) from the fields in this
713 * Time object. The only case where this method cannot figure out the
714 * correct value of the "isDst" field is when the time is inherently
715 * ambiguous because it falls in the hour that is repeated when switching
716 * from Daylight-Saving Time to Standard Time.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200717 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800718 * <p>
719 * Here is an example where <tt>toMillis(true)</tt> adjusts the time,
720 * assuming that DST changes at 2am on Sunday, Nov 4, 2007.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200721 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 * <pre>
723 * Time time = new Time();
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200724 * time.set(4, 10, 2007); // set the date to Nov 4, 2007, 12am
Scott Main688342f2013-12-17 13:47:55 -0800725 * time.normalize(false); // this sets isDst = 1
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 * time.monthDay += 1; // changes the date to Nov 5, 2007, 12am
727 * millis = time.toMillis(false); // millis is Nov 4, 2007, 11pm
728 * millis = time.toMillis(true); // millis is Nov 5, 2007, 12am
729 * </pre>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200730 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 * <p>
732 * To avoid this problem, use <tt>toMillis(true)</tt>
733 * after adding or subtracting days or explicitly setting the "monthDay"
734 * field. On the other hand, if you are adding
735 * or subtracting hours or minutes, then you should use
736 * <tt>toMillis(false)</tt>.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200737 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 * <p>
739 * You should also use <tt>toMillis(false)</tt> if you want
740 * to read back the same milliseconds that you set with {@link #set(long)}
Neil Fuller3d3d7f12018-01-17 21:13:21 +0000741 * or {@link #set(Time)} or after parsing a date string.
Neil Fuller7079f202014-09-30 11:47:19 +0100742 *
743 * <p>
744 * This method can return {@code -1} when the date / time fields have been
745 * set to a local time that conflicts with available timezone information.
746 * For example, when daylight savings transitions cause an hour to be
747 * skipped: times within that hour will return {@code -1} if isDst =
748 * {@code -1}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100750 public long toMillis(boolean ignoreDst) {
751 calculator.copyFieldsFromTime(this);
752 return calculator.toMillis(ignoreDst);
753 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754
755 /**
756 * Sets the fields in this Time object given the UTC milliseconds. After
757 * this method returns, all the fields are normalized.
758 * This also sets the "isDst" field to the correct value.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200759 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 * @param millis the time in UTC milliseconds since the epoch.
761 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100762 public void set(long millis) {
763 allDay = false;
764 calculator.timezone = timezone;
765 calculator.setTimeInMillis(millis);
766 calculator.copyFieldsToTime(this);
767 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800768
769 /**
Neil Fullerd7f08492014-06-25 11:13:25 +0100770 * Format according to RFC 2445 DATE-TIME type.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200771 *
Neil Fullerd7f08492014-06-25 11:13:25 +0100772 * <p>The same as format("%Y%m%dT%H%M%S"), or format("%Y%m%dT%H%M%SZ") for a Time with a
773 * timezone set to "UTC".
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 */
Neil Fullerd7f08492014-06-25 11:13:25 +0100775 public String format2445() {
776 calculator.copyFieldsFromTime(this);
777 return calculator.format2445(!allDay);
778 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800779
780 /**
781 * Copy the value of that to this Time object. No normalization happens.
782 */
783 public void set(Time that) {
784 this.timezone = that.timezone;
785 this.allDay = that.allDay;
786 this.second = that.second;
787 this.minute = that.minute;
788 this.hour = that.hour;
789 this.monthDay = that.monthDay;
790 this.month = that.month;
791 this.year = that.year;
792 this.weekDay = that.weekDay;
793 this.yearDay = that.yearDay;
794 this.isDst = that.isDst;
795 this.gmtoff = that.gmtoff;
796 }
797
798 /**
799 * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
800 * Call {@link #normalize(boolean)} if you need those.
801 */
802 public void set(int second, int minute, int hour, int monthDay, int month, int year) {
803 this.allDay = false;
804 this.second = second;
805 this.minute = minute;
806 this.hour = hour;
807 this.monthDay = monthDay;
808 this.month = month;
809 this.year = year;
810 this.weekDay = 0;
811 this.yearDay = 0;
812 this.isDst = -1;
813 this.gmtoff = 0;
814 }
815
816 /**
817 * Sets the date from the given fields. Also sets allDay to true.
818 * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
819 * Call {@link #normalize(boolean)} if you need those.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200820 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821 * @param monthDay the day of the month (in the range [1,31])
822 * @param month the zero-based month number (in the range [0,11])
823 * @param year the year
824 */
825 public void set(int monthDay, int month, int year) {
826 this.allDay = true;
827 this.second = 0;
828 this.minute = 0;
829 this.hour = 0;
830 this.monthDay = monthDay;
831 this.month = month;
832 this.year = year;
833 this.weekDay = 0;
834 this.yearDay = 0;
835 this.isDst = -1;
836 this.gmtoff = 0;
837 }
838
839 /**
840 * Returns true if the time represented by this Time object occurs before
841 * the given time.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200842 *
Neil Fuller7079f202014-09-30 11:47:19 +0100843 * <p>
Neil Fuller71fbb812015-11-30 09:51:33 +0000844 * Equivalent to {@code Time.compare(this, that) < 0}. See
Neil Fuller7079f202014-09-30 11:47:19 +0100845 * {@link #compare(Time, Time)} for details.
846 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800847 * @param that a given Time object to compare against
848 * @return true if this time is less than the given time
849 */
850 public boolean before(Time that) {
851 return Time.compare(this, that) < 0;
852 }
853
854
855 /**
856 * Returns true if the time represented by this Time object occurs after
857 * the given time.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200858 *
Neil Fuller7079f202014-09-30 11:47:19 +0100859 * <p>
Neil Fuller71fbb812015-11-30 09:51:33 +0000860 * Equivalent to {@code Time.compare(this, that) > 0}. See
Neil Fuller7079f202014-09-30 11:47:19 +0100861 * {@link #compare(Time, Time)} for details.
862 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 * @param that a given Time object to compare against
864 * @return true if this time is greater than the given time
865 */
866 public boolean after(Time that) {
867 return Time.compare(this, that) > 0;
868 }
869
870 /**
871 * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.)
872 * and gives a number that can be added to the yearDay to give the
873 * closest Thursday yearDay.
874 */
875 private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 };
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200876
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 /**
878 * Computes the week number according to ISO 8601. The current Time
879 * object must already be normalized because this method uses the
880 * yearDay and weekDay fields.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200881 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 * <p>
883 * In IS0 8601, weeks start on Monday.
884 * The first week of the year (week 1) is defined by ISO 8601 as the
885 * first week with four or more of its days in the starting year.
886 * Or equivalently, the week containing January 4. Or equivalently,
887 * the week with the year's first Thursday in it.
888 * </p>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200889 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890 * <p>
891 * The week number can be calculated by counting Thursdays. Week N
892 * contains the Nth Thursday of the year.
893 * </p>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200894 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895 * @return the ISO week number.
896 */
897 public int getWeekNumber() {
898 // Get the year day for the closest Thursday
899 int closestThursday = yearDay + sThursdayOffset[weekDay];
900
901 // Year days start at 0
902 if (closestThursday >= 0 && closestThursday <= 364) {
903 return closestThursday / 7 + 1;
904 }
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200905
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906 // The week crosses a year boundary.
907 Time temp = new Time(this);
908 temp.monthDay += sThursdayOffset[weekDay];
909 temp.normalize(true /* ignore isDst */);
910 return temp.yearDay / 7 + 1;
911 }
912
913 /**
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200914 * Return a string in the RFC 3339 format.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 * <p>
916 * If allDay is true, expresses the time as Y-M-D</p>
917 * <p>
918 * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p>
919 * <p>
920 * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800921 * @return string in the RFC 3339 format.
922 */
923 public String format3339(boolean allDay) {
924 if (allDay) {
925 return format(Y_M_D);
926 } else if (TIMEZONE_UTC.equals(timezone)) {
927 return format(Y_M_D_T_H_M_S_000_Z);
928 } else {
929 String base = format(Y_M_D_T_H_M_S_000);
930 String sign = (gmtoff < 0) ? "-" : "+";
Neil Fullerd7f08492014-06-25 11:13:25 +0100931 int offset = (int) Math.abs(gmtoff);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 int minutes = (offset % 3600) / 60;
933 int hours = offset / 3600;
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200934
Roozbeh Pournader315a7c02012-09-17 20:34:53 -0700935 return String.format(Locale.US, "%s%s%02d:%02d", base, sign, hours, minutes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800936 }
937 }
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200938
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 /**
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200940 * Returns true if the day of the given time is the epoch on the Julian Calendar
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 * (January 1, 1970 on the Gregorian calendar).
942 *
Neil Fuller7079f202014-09-30 11:47:19 +0100943 * <p>
944 * This method can return an incorrect answer when the date / time fields have
945 * been set to a local time that contradicts the available timezone information.
946 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 * @param time the time to test
948 * @return true if epoch.
949 */
950 public static boolean isEpoch(Time time) {
951 long millis = time.toMillis(true);
952 return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY;
953 }
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200954
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 /**
Neil Fullerd7f08492014-06-25 11:13:25 +0100956 * Computes the Julian day number for a point in time in a particular
957 * timezone. The Julian day for a given date is the same for every
958 * timezone. For example, the Julian day for July 1, 2008 is 2454649.
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200959 *
Neil Fullerd7f08492014-06-25 11:13:25 +0100960 * <p>Callers must pass the time in UTC millisecond (as can be returned
961 * by {@link #toMillis(boolean)} or {@link #normalize(boolean)})
962 * and the offset from UTC of the timezone in seconds (as might be in
963 * {@link #gmtoff}).
964 *
965 * <p>The Julian day is useful for testing if two events occur on the
966 * same calendar date and for determining the relative time of an event
967 * from the present ("yesterday", "3 days ago", etc.).
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200968 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 * @param millis the time in UTC milliseconds
970 * @param gmtoff the offset from UTC in seconds
971 * @return the Julian day
972 */
973 public static int getJulianDay(long millis, long gmtoff) {
974 long offsetMillis = gmtoff * 1000;
975 long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS;
976 return (int) julianDay + EPOCH_JULIAN_DAY;
977 }
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200978
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 /**
980 * <p>Sets the time from the given Julian day number, which must be based on
981 * the same timezone that is set in this Time object. The "gmtoff" field
982 * need not be initialized because the given Julian day may have a different
983 * GMT offset than whatever is currently stored in this Time object anyway.
984 * After this method returns all the fields will be normalized and the time
985 * will be set to 12am at the beginning of the given Julian day.
986 * </p>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200987 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 * <p>
989 * The only exception to this is if 12am does not exist for that day because
990 * of daylight saving time. For example, Cairo, Eqypt moves time ahead one
991 * hour at 12am on April 25, 2008 and there are a few other places that
992 * also change daylight saving time at 12am. In those cases, the time
993 * will be set to 1am.
994 * </p>
Christian Mehlmauer0df10e92010-07-06 20:42:22 +0200995 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996 * @param julianDay the Julian day in the timezone for this Time object
997 * @return the UTC milliseconds for the beginning of the Julian day
998 */
999 public long setJulianDay(int julianDay) {
1000 // Don't bother with the GMT offset since we don't know the correct
1001 // value for the given Julian day. Just get close and then adjust
1002 // the day.
1003 long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
1004 set(millis);
Christian Mehlmauer0df10e92010-07-06 20:42:22 +02001005
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 // Figure out how close we are to the requested Julian day.
1007 // We can't be off by more than a day.
1008 int approximateDay = getJulianDay(millis, gmtoff);
1009 int diff = julianDay - approximateDay;
1010 monthDay += diff;
Christian Mehlmauer0df10e92010-07-06 20:42:22 +02001011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 // Set the time to 12am and re-normalize.
1013 hour = 0;
1014 minute = 0;
1015 second = 0;
1016 millis = normalize(true);
1017 return millis;
1018 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001019
1020 /**
1021 * Returns the week since {@link #EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted
1022 * for first day of week. This takes a julian day and the week start day and
1023 * calculates which week since {@link #EPOCH_JULIAN_DAY} that day occurs in,
1024 * starting at 0. *Do not* use this to compute the ISO week number for the
1025 * year.
1026 *
1027 * @param julianDay The julian day to calculate the week number for
1028 * @param firstDayOfWeek Which week day is the first day of the week, see
1029 * {@link #SUNDAY}
1030 * @return Weeks since the epoch
1031 */
1032 public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
1033 int diff = THURSDAY - firstDayOfWeek;
1034 if (diff < 0) {
1035 diff += 7;
1036 }
1037 int refDay = EPOCH_JULIAN_DAY - diff;
1038 return (julianDay - refDay) / 7;
1039 }
1040
1041 /**
1042 * Takes a number of weeks since the epoch and calculates the Julian day of
1043 * the Monday for that week. This assumes that the week containing the
1044 * {@link #EPOCH_JULIAN_DAY} is considered week 0. It returns the Julian day
1045 * for the Monday week weeks after the Monday of the week containing the
1046 * epoch.
1047 *
1048 * @param week Number of weeks since the epoch
1049 * @return The julian day for the Monday of the given week since the epoch
1050 */
1051 public static int getJulianMondayFromWeeksSinceEpoch(int week) {
1052 return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
1053 }
Neil Fullerd7f08492014-06-25 11:13:25 +01001054
1055 /**
1056 * A class that handles date/time calculations.
1057 *
1058 * This class originated as a port of a native C++ class ("android.Time") to pure Java. It is
1059 * separate from the enclosing class because some methods copy the result of calculations back
1060 * to the enclosing object, but others do not: thus separate state is retained.
1061 */
1062 private static class TimeCalculator {
1063 public final ZoneInfo.WallTime wallTime;
1064 public String timezone;
1065
1066 // Information about the current timezone.
1067 private ZoneInfo zoneInfo;
1068
1069 public TimeCalculator(String timezoneId) {
1070 this.zoneInfo = lookupZoneInfo(timezoneId);
1071 this.wallTime = new ZoneInfo.WallTime();
1072 }
1073
1074 public long toMillis(boolean ignoreDst) {
1075 if (ignoreDst) {
1076 wallTime.setIsDst(-1);
1077 }
1078
1079 int r = wallTime.mktime(zoneInfo);
1080 if (r == -1) {
1081 return -1;
1082 }
1083 return r * 1000L;
1084 }
1085
1086 public void setTimeInMillis(long millis) {
1087 // Preserve old 32-bit Android behavior.
1088 int intSeconds = (int) (millis / 1000);
1089
1090 updateZoneInfoFromTimeZone();
1091 wallTime.localtime(intSeconds, zoneInfo);
1092 }
1093
1094 public String format(String format) {
1095 if (format == null) {
1096 format = "%c";
1097 }
1098 TimeFormatter formatter = new TimeFormatter();
1099 return formatter.format(format, wallTime, zoneInfo);
1100 }
1101
1102 private void updateZoneInfoFromTimeZone() {
1103 if (!zoneInfo.getID().equals(timezone)) {
1104 this.zoneInfo = lookupZoneInfo(timezone);
1105 }
1106 }
1107
1108 private static ZoneInfo lookupZoneInfo(String timezoneId) {
1109 try {
1110 ZoneInfo zoneInfo = ZoneInfoDB.getInstance().makeTimeZone(timezoneId);
1111 if (zoneInfo == null) {
1112 zoneInfo = ZoneInfoDB.getInstance().makeTimeZone("GMT");
1113 }
1114 if (zoneInfo == null) {
1115 throw new AssertionError("GMT not found: \"" + timezoneId + "\"");
1116 }
1117 return zoneInfo;
1118 } catch (IOException e) {
1119 // This should not ever be thrown.
1120 throw new AssertionError("Error loading timezone: \"" + timezoneId + "\"", e);
1121 }
1122 }
1123
1124 public void switchTimeZone(String timezone) {
1125 int seconds = wallTime.mktime(zoneInfo);
1126 this.timezone = timezone;
1127 updateZoneInfoFromTimeZone();
1128 wallTime.localtime(seconds, zoneInfo);
1129 }
1130
1131 public String format2445(boolean hasTime) {
1132 char[] buf = new char[hasTime ? 16 : 8];
1133 int n = wallTime.getYear();
1134
1135 buf[0] = toChar(n / 1000);
1136 n %= 1000;
1137 buf[1] = toChar(n / 100);
1138 n %= 100;
1139 buf[2] = toChar(n / 10);
1140 n %= 10;
1141 buf[3] = toChar(n);
1142
1143 n = wallTime.getMonth() + 1;
1144 buf[4] = toChar(n / 10);
1145 buf[5] = toChar(n % 10);
1146
1147 n = wallTime.getMonthDay();
1148 buf[6] = toChar(n / 10);
1149 buf[7] = toChar(n % 10);
1150
1151 if (!hasTime) {
1152 return new String(buf, 0, 8);
1153 }
1154
1155 buf[8] = 'T';
1156
1157 n = wallTime.getHour();
1158 buf[9] = toChar(n / 10);
1159 buf[10] = toChar(n % 10);
1160
1161 n = wallTime.getMinute();
1162 buf[11] = toChar(n / 10);
1163 buf[12] = toChar(n % 10);
1164
1165 n = wallTime.getSecond();
1166 buf[13] = toChar(n / 10);
1167 buf[14] = toChar(n % 10);
1168
1169 if (TIMEZONE_UTC.equals(timezone)) {
1170 // The letter 'Z' is appended to the end.
1171 buf[15] = 'Z';
1172 return new String(buf, 0, 16);
1173 } else {
1174 return new String(buf, 0, 15);
1175 }
1176 }
1177
1178 private char toChar(int n) {
1179 return (n >= 0 && n <= 9) ? (char) (n + '0') : ' ';
1180 }
1181
1182 /**
1183 * A method that will return the state of this object in string form. Note: it has side
1184 * effects and so has deliberately not been made the default {@link #toString()}.
1185 */
1186 public String toStringInternal() {
1187 // This implementation possibly displays the un-normalized fields because that is
1188 // what it has always done.
1189 return String.format("%04d%02d%02dT%02d%02d%02d%s(%d,%d,%d,%d,%d)",
1190 wallTime.getYear(),
1191 wallTime.getMonth() + 1,
1192 wallTime.getMonthDay(),
1193 wallTime.getHour(),
1194 wallTime.getMinute(),
1195 wallTime.getSecond(),
1196 timezone,
1197 wallTime.getWeekDay(),
1198 wallTime.getYearDay(),
1199 wallTime.getGmtOffset(),
1200 wallTime.getIsDst(),
1201 toMillis(false /* use isDst */) / 1000
1202 );
1203
1204 }
1205
1206 public static int compare(TimeCalculator aObject, TimeCalculator bObject) {
1207 if (aObject.timezone.equals(bObject.timezone)) {
1208 // If the timezones are the same, we can easily compare the two times.
1209 int diff = aObject.wallTime.getYear() - bObject.wallTime.getYear();
1210 if (diff != 0) {
1211 return diff;
1212 }
1213
1214 diff = aObject.wallTime.getMonth() - bObject.wallTime.getMonth();
1215 if (diff != 0) {
1216 return diff;
1217 }
1218
1219 diff = aObject.wallTime.getMonthDay() - bObject.wallTime.getMonthDay();
1220 if (diff != 0) {
1221 return diff;
1222 }
1223
1224 diff = aObject.wallTime.getHour() - bObject.wallTime.getHour();
1225 if (diff != 0) {
1226 return diff;
1227 }
1228
1229 diff = aObject.wallTime.getMinute() - bObject.wallTime.getMinute();
1230 if (diff != 0) {
1231 return diff;
1232 }
1233
1234 diff = aObject.wallTime.getSecond() - bObject.wallTime.getSecond();
1235 if (diff != 0) {
1236 return diff;
1237 }
1238
1239 return 0;
1240 } else {
1241 // Otherwise, convert to milliseconds and compare that. This requires that object be
1242 // normalized. Note: For dates that do not exist: toMillis() can return -1, which
1243 // can be confused with a valid time.
1244 long am = aObject.toMillis(false /* use isDst */);
1245 long bm = bObject.toMillis(false /* use isDst */);
1246 long diff = am - bm;
1247 return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
1248 }
1249
1250 }
1251
1252 public void copyFieldsToTime(Time time) {
1253 time.second = wallTime.getSecond();
1254 time.minute = wallTime.getMinute();
1255 time.hour = wallTime.getHour();
1256 time.monthDay = wallTime.getMonthDay();
1257 time.month = wallTime.getMonth();
1258 time.year = wallTime.getYear();
1259
1260 // Read-only fields that are derived from other information above.
1261 time.weekDay = wallTime.getWeekDay();
1262 time.yearDay = wallTime.getYearDay();
1263
1264 // < 0: DST status unknown, 0: is not in DST, 1: is in DST
1265 time.isDst = wallTime.getIsDst();
1266 // This is in seconds and includes any DST offset too.
1267 time.gmtoff = wallTime.getGmtOffset();
1268 }
1269
1270 public void copyFieldsFromTime(Time time) {
1271 wallTime.setSecond(time.second);
1272 wallTime.setMinute(time.minute);
1273 wallTime.setHour(time.hour);
1274 wallTime.setMonthDay(time.monthDay);
1275 wallTime.setMonth(time.month);
1276 wallTime.setYear(time.year);
1277 wallTime.setWeekDay(time.weekDay);
1278 wallTime.setYearDay(time.yearDay);
1279 wallTime.setIsDst(time.isDst);
1280 wallTime.setGmtOffset((int) time.gmtoff);
1281
1282 if (time.allDay && (time.second != 0 || time.minute != 0 || time.hour != 0)) {
1283 throw new IllegalArgumentException("allDay is true but sec, min, hour are not 0.");
1284 }
1285
1286 timezone = time.timezone;
1287 updateZoneInfoFromTimeZone();
1288 }
1289 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290}