• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.text.format;
18 
19 import android.content.res.Resources;
20 
21 import java.util.Locale;
22 import java.util.TimeZone;
23 
24 /**
25  * An alternative to the {@link java.util.Calendar} and
26  * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents
27  * a moment in time, specified with second precision. It is modelled after
28  * struct tm, and in fact, uses struct tm to implement most of the
29  * functionality.
30  */
31 public class Time {
32     private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
33     private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
34     private static final String Y_M_D = "%Y-%m-%d";
35 
36     public static final String TIMEZONE_UTC = "UTC";
37 
38     /**
39      * The Julian day of the epoch, that is, January 1, 1970 on the Gregorian
40      * calendar.
41      */
42     public static final int EPOCH_JULIAN_DAY = 2440588;
43 
44     /**
45      * The Julian day of the Monday in the week of the epoch, December 29, 1969
46      * on the Gregorian calendar.
47      */
48     public static final int MONDAY_BEFORE_JULIAN_EPOCH = EPOCH_JULIAN_DAY - 3;
49 
50     /**
51      * True if this is an allDay event. The hour, minute, second fields are
52      * all zero, and the date is displayed the same in all time zones.
53      */
54     public boolean allDay;
55 
56     /**
57      * Seconds [0-61] (2 leap seconds allowed)
58      */
59     public int second;
60 
61     /**
62      * Minute [0-59]
63      */
64     public int minute;
65 
66     /**
67      * Hour of day [0-23]
68      */
69     public int hour;
70 
71     /**
72      * Day of month [1-31]
73      */
74     public int monthDay;
75 
76     /**
77      * Month [0-11]
78      */
79     public int month;
80 
81     /**
82      * Year. For example, 1970.
83      */
84     public int year;
85 
86     /**
87      * Day of week [0-6]
88      */
89     public int weekDay;
90 
91     /**
92      * Day of year [0-365]
93      */
94     public int yearDay;
95 
96     /**
97      * This time is in daylight savings time. One of:
98      * <ul>
99      * <li><b>positive</b> - in dst</li>
100      * <li><b>0</b> - not in dst</li>
101      * <li><b>negative</b> - unknown</li>
102      * </ul>
103      */
104     public int isDst;
105 
106     /**
107      * Offset from UTC (in seconds).
108      */
109     public long gmtoff;
110 
111     /**
112      * The timezone for this Time.  Should not be null.
113      */
114     public String timezone;
115 
116     /*
117      * Define symbolic constants for accessing the fields in this class. Used in
118      * getActualMaximum().
119      */
120     public static final int SECOND = 1;
121     public static final int MINUTE = 2;
122     public static final int HOUR = 3;
123     public static final int MONTH_DAY = 4;
124     public static final int MONTH = 5;
125     public static final int YEAR = 6;
126     public static final int WEEK_DAY = 7;
127     public static final int YEAR_DAY = 8;
128     public static final int WEEK_NUM = 9;
129 
130     public static final int SUNDAY = 0;
131     public static final int MONDAY = 1;
132     public static final int TUESDAY = 2;
133     public static final int WEDNESDAY = 3;
134     public static final int THURSDAY = 4;
135     public static final int FRIDAY = 5;
136     public static final int SATURDAY = 6;
137 
138     /*
139      * The Locale for which date formatting strings have been loaded.
140      */
141     private static Locale sLocale;
142     private static String[] sShortMonths;
143     private static String[] sLongMonths;
144     private static String[] sLongStandaloneMonths;
145     private static String[] sShortWeekdays;
146     private static String[] sLongWeekdays;
147     private static String sTimeOnlyFormat;
148     private static String sDateOnlyFormat;
149     private static String sDateTimeFormat;
150     private static String sAm;
151     private static String sPm;
152     private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y";
153 
154     /**
155      * Construct a Time object in the timezone named by the string
156      * argument "timezone". The time is initialized to Jan 1, 1970.
157      * @param timezone string containing the timezone to use.
158      * @see TimeZone
159      */
Time(String timezone)160     public Time(String timezone) {
161         if (timezone == null) {
162             throw new NullPointerException("timezone is null!");
163         }
164         this.timezone = timezone;
165         this.year = 1970;
166         this.monthDay = 1;
167         // Set the daylight-saving indicator to the unknown value -1 so that
168         // it will be recomputed.
169         this.isDst = -1;
170     }
171 
172     /**
173      * Construct a Time object in the default timezone. The time is initialized to
174      * Jan 1, 1970.
175      */
Time()176     public Time() {
177         this(TimeZone.getDefault().getID());
178     }
179 
180     /**
181      * A copy constructor.  Construct a Time object by copying the given
182      * Time object.  No normalization occurs.
183      *
184      * @param other
185      */
Time(Time other)186     public Time(Time other) {
187         set(other);
188     }
189 
190     /**
191      * Ensures the values in each field are in range. For example if the
192      * current value of this calendar is March 32, normalize() will convert it
193      * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff.
194      *
195      * <p>
196      * If "ignoreDst" is true, then this method sets the "isDst" field to -1
197      * (the "unknown" value) before normalizing.  It then computes the
198      * correct value for "isDst".
199      *
200      * <p>
201      * See {@link #toMillis(boolean)} for more information about when to
202      * use <tt>true</tt> or <tt>false</tt> for "ignoreDst".
203      *
204      * @return the UTC milliseconds since the epoch
205      */
normalize(boolean ignoreDst)206     native public long normalize(boolean ignoreDst);
207 
208     /**
209      * Convert this time object so the time represented remains the same, but is
210      * instead located in a different timezone. This method automatically calls
211      * normalize() in some cases
212      */
switchTimezone(String timezone)213     native public void switchTimezone(String timezone);
214 
215     private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31,
216             31, 30, 31, 30, 31 };
217 
218     /**
219      * Return the maximum possible value for the given field given the value of
220      * the other fields. Requires that it be normalized for MONTH_DAY and
221      * YEAR_DAY.
222      * @param field one of the constants for HOUR, MINUTE, SECOND, etc.
223      * @return the maximum value for the field.
224      */
getActualMaximum(int field)225     public int getActualMaximum(int field) {
226         switch (field) {
227         case SECOND:
228             return 59; // leap seconds, bah humbug
229         case MINUTE:
230             return 59;
231         case HOUR:
232             return 23;
233         case MONTH_DAY: {
234             int n = DAYS_PER_MONTH[this.month];
235             if (n != 28) {
236                 return n;
237             } else {
238                 int y = this.year;
239                 return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29 : 28;
240             }
241         }
242         case MONTH:
243             return 11;
244         case YEAR:
245             return 2037;
246         case WEEK_DAY:
247             return 6;
248         case YEAR_DAY: {
249             int y = this.year;
250             // Year days are numbered from 0, so the last one is usually 364.
251             return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 365 : 364;
252         }
253         case WEEK_NUM:
254             throw new RuntimeException("WEEK_NUM not implemented");
255         default:
256             throw new RuntimeException("bad field=" + field);
257         }
258     }
259 
260     /**
261      * Clears all values, setting the timezone to the given timezone. Sets isDst
262      * to a negative value to mean "unknown".
263      * @param timezone the timezone to use.
264      */
clear(String timezone)265     public void clear(String timezone) {
266         if (timezone == null) {
267             throw new NullPointerException("timezone is null!");
268         }
269         this.timezone = timezone;
270         this.allDay = false;
271         this.second = 0;
272         this.minute = 0;
273         this.hour = 0;
274         this.monthDay = 0;
275         this.month = 0;
276         this.year = 0;
277         this.weekDay = 0;
278         this.yearDay = 0;
279         this.gmtoff = 0;
280         this.isDst = -1;
281     }
282 
283     /**
284      * Compare two {@code Time} objects and return a negative number if {@code
285      * a} is less than {@code b}, a positive number if {@code a} is greater than
286      * {@code b}, or 0 if they are equal.
287      *
288      * @param a first {@code Time} instance to compare
289      * @param b second {@code Time} instance to compare
290      * @throws NullPointerException if either argument is {@code null}
291      * @throws IllegalArgumentException if {@link #allDay} is true but {@code
292      *             hour}, {@code minute}, and {@code second} are not 0.
293      * @return a negative result if {@code a} is earlier, a positive result if
294      *         {@code a} is earlier, or 0 if they are equal.
295      */
compare(Time a, Time b)296     public static int compare(Time a, Time b) {
297         if (a == null) {
298             throw new NullPointerException("a == null");
299         } else if (b == null) {
300             throw new NullPointerException("b == null");
301         }
302 
303         return nativeCompare(a, b);
304     }
305 
nativeCompare(Time a, Time b)306     private static native int nativeCompare(Time a, Time b);
307 
308     /**
309      * Print the current value given the format string provided. See man
310      * strftime for what means what. The final string must be less than 256
311      * characters.
312      * @param format a string containing the desired format.
313      * @return a String containing the current time expressed in the current locale.
314      */
format(String format)315     public String format(String format) {
316         synchronized (Time.class) {
317             Locale locale = Locale.getDefault();
318 
319             if (sLocale == null || locale == null || !(locale.equals(sLocale))) {
320                 Resources r = Resources.getSystem();
321 
322                 sShortMonths = new String[] {
323                     r.getString(com.android.internal.R.string.month_medium_january),
324                     r.getString(com.android.internal.R.string.month_medium_february),
325                     r.getString(com.android.internal.R.string.month_medium_march),
326                     r.getString(com.android.internal.R.string.month_medium_april),
327                     r.getString(com.android.internal.R.string.month_medium_may),
328                     r.getString(com.android.internal.R.string.month_medium_june),
329                     r.getString(com.android.internal.R.string.month_medium_july),
330                     r.getString(com.android.internal.R.string.month_medium_august),
331                     r.getString(com.android.internal.R.string.month_medium_september),
332                     r.getString(com.android.internal.R.string.month_medium_october),
333                     r.getString(com.android.internal.R.string.month_medium_november),
334                     r.getString(com.android.internal.R.string.month_medium_december),
335                 };
336                 sLongMonths = new String[] {
337                     r.getString(com.android.internal.R.string.month_long_january),
338                     r.getString(com.android.internal.R.string.month_long_february),
339                     r.getString(com.android.internal.R.string.month_long_march),
340                     r.getString(com.android.internal.R.string.month_long_april),
341                     r.getString(com.android.internal.R.string.month_long_may),
342                     r.getString(com.android.internal.R.string.month_long_june),
343                     r.getString(com.android.internal.R.string.month_long_july),
344                     r.getString(com.android.internal.R.string.month_long_august),
345                     r.getString(com.android.internal.R.string.month_long_september),
346                     r.getString(com.android.internal.R.string.month_long_october),
347                     r.getString(com.android.internal.R.string.month_long_november),
348                     r.getString(com.android.internal.R.string.month_long_december),
349                 };
350                 sLongStandaloneMonths = new String[] {
351                     r.getString(com.android.internal.R.string.month_long_standalone_january),
352                     r.getString(com.android.internal.R.string.month_long_standalone_february),
353                     r.getString(com.android.internal.R.string.month_long_standalone_march),
354                     r.getString(com.android.internal.R.string.month_long_standalone_april),
355                     r.getString(com.android.internal.R.string.month_long_standalone_may),
356                     r.getString(com.android.internal.R.string.month_long_standalone_june),
357                     r.getString(com.android.internal.R.string.month_long_standalone_july),
358                     r.getString(com.android.internal.R.string.month_long_standalone_august),
359                     r.getString(com.android.internal.R.string.month_long_standalone_september),
360                     r.getString(com.android.internal.R.string.month_long_standalone_october),
361                     r.getString(com.android.internal.R.string.month_long_standalone_november),
362                     r.getString(com.android.internal.R.string.month_long_standalone_december),
363                 };
364                 sShortWeekdays = new String[] {
365                     r.getString(com.android.internal.R.string.day_of_week_medium_sunday),
366                     r.getString(com.android.internal.R.string.day_of_week_medium_monday),
367                     r.getString(com.android.internal.R.string.day_of_week_medium_tuesday),
368                     r.getString(com.android.internal.R.string.day_of_week_medium_wednesday),
369                     r.getString(com.android.internal.R.string.day_of_week_medium_thursday),
370                     r.getString(com.android.internal.R.string.day_of_week_medium_friday),
371                     r.getString(com.android.internal.R.string.day_of_week_medium_saturday),
372                 };
373                 sLongWeekdays = new String[] {
374                     r.getString(com.android.internal.R.string.day_of_week_long_sunday),
375                     r.getString(com.android.internal.R.string.day_of_week_long_monday),
376                     r.getString(com.android.internal.R.string.day_of_week_long_tuesday),
377                     r.getString(com.android.internal.R.string.day_of_week_long_wednesday),
378                     r.getString(com.android.internal.R.string.day_of_week_long_thursday),
379                     r.getString(com.android.internal.R.string.day_of_week_long_friday),
380                     r.getString(com.android.internal.R.string.day_of_week_long_saturday),
381                 };
382                 sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day);
383                 sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year);
384                 sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time);
385                 sAm = r.getString(com.android.internal.R.string.am);
386                 sPm = r.getString(com.android.internal.R.string.pm);
387 
388                 sLocale = locale;
389             }
390 
391             return format1(format);
392         }
393     }
394 
format1(String format)395     native private String format1(String format);
396 
397     /**
398      * Return the current time in YYYYMMDDTHHMMSS<tz> format
399      */
400     @Override
toString()401     native public String toString();
402 
403     /**
404      * Parses a date-time string in either the RFC 2445 format or an abbreviated
405      * format that does not include the "time" field.  For example, all of the
406      * following strings are valid:
407      *
408      * <ul>
409      *   <li>"20081013T160000Z"</li>
410      *   <li>"20081013T160000"</li>
411      *   <li>"20081013"</li>
412      * </ul>
413      *
414      * Returns whether or not the time is in UTC (ends with Z).  If the string
415      * ends with "Z" then the timezone is set to UTC.  If the date-time string
416      * included only a date and no time field, then the <code>allDay</code>
417      * field of this Time class is set to true and the <code>hour</code>,
418      * <code>minute</code>, and <code>second</code> fields are set to zero;
419      * otherwise (a time field was included in the date-time string)
420      * <code>allDay</code> is set to false. The fields <code>weekDay</code>,
421      * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero,
422      * and the field <code>isDst</code> is set to -1 (unknown).  To set those
423      * fields, call {@link #normalize(boolean)} after parsing.
424      *
425      * To parse a date-time string and convert it to UTC milliseconds, do
426      * something like this:
427      *
428      * <pre>
429      *   Time time = new Time();
430      *   String date = "20081013T160000Z";
431      *   time.parse(date);
432      *   long millis = time.normalize(false);
433      * </pre>
434      *
435      * @param s the string to parse
436      * @return true if the resulting time value is in UTC time
437      * @throws android.util.TimeFormatException if s cannot be parsed.
438      */
parse(String s)439     public boolean parse(String s) {
440         if (nativeParse(s)) {
441             timezone = TIMEZONE_UTC;
442             return true;
443         }
444         return false;
445     }
446 
447     /**
448      * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
449      */
nativeParse(String s)450     native private boolean nativeParse(String s);
451 
452     /**
453      * Parse a time in RFC 3339 format.  This method also parses simple dates
454      * (that is, strings that contain no time or time offset).  For example,
455      * all of the following strings are valid:
456      *
457      * <ul>
458      *   <li>"2008-10-13T16:00:00.000Z"</li>
459      *   <li>"2008-10-13T16:00:00.000+07:00"</li>
460      *   <li>"2008-10-13T16:00:00.000-07:00"</li>
461      *   <li>"2008-10-13"</li>
462      * </ul>
463      *
464      * <p>
465      * If the string contains a time and time offset, then the time offset will
466      * be used to convert the time value to UTC.
467      * </p>
468      *
469      * <p>
470      * If the given string contains just a date (with no time field), then
471      * the {@link #allDay} field is set to true and the {@link #hour},
472      * {@link #minute}, and  {@link #second} fields are set to zero.
473      * </p>
474      *
475      * <p>
476      * Returns true if the resulting time value is in UTC time.
477      * </p>
478      *
479      * @param s the string to parse
480      * @return true if the resulting time value is in UTC time
481      * @throws android.util.TimeFormatException if s cannot be parsed.
482      */
parse3339(String s)483      public boolean parse3339(String s) {
484          if (s == null) {
485              throw new NullPointerException("time string is null");
486          }
487          if (nativeParse3339(s)) {
488              timezone = TIMEZONE_UTC;
489              return true;
490          }
491          return false;
492      }
493 
nativeParse3339(String s)494      native private boolean nativeParse3339(String s);
495 
496     /**
497      * Returns the timezone string that is currently set for the device.
498      */
getCurrentTimezone()499     public static String getCurrentTimezone() {
500         return TimeZone.getDefault().getID();
501     }
502 
503     /**
504      * Sets the time of the given Time object to the current time.
505      */
setToNow()506     native public void setToNow();
507 
508     /**
509      * Converts this time to milliseconds. Suitable for interacting with the
510      * standard java libraries. The time is in UTC milliseconds since the epoch.
511      * This does an implicit normalization to compute the milliseconds but does
512      * <em>not</em> change any of the fields in this Time object.  If you want
513      * to normalize the fields in this Time object and also get the milliseconds
514      * then use {@link #normalize(boolean)}.
515      *
516      * <p>
517      * If "ignoreDst" is false, then this method uses the current setting of the
518      * "isDst" field and will adjust the returned time if the "isDst" field is
519      * wrong for the given time.  See the sample code below for an example of
520      * this.
521      *
522      * <p>
523      * If "ignoreDst" is true, then this method ignores the current setting of
524      * the "isDst" field in this Time object and will instead figure out the
525      * correct value of "isDst" (as best it can) from the fields in this
526      * Time object.  The only case where this method cannot figure out the
527      * correct value of the "isDst" field is when the time is inherently
528      * ambiguous because it falls in the hour that is repeated when switching
529      * from Daylight-Saving Time to Standard Time.
530      *
531      * <p>
532      * Here is an example where <tt>toMillis(true)</tt> adjusts the time,
533      * assuming that DST changes at 2am on Sunday, Nov 4, 2007.
534      *
535      * <pre>
536      * Time time = new Time();
537      * time.set(4, 10, 2007);  // set the date to Nov 4, 2007, 12am
538      * time.normalize();       // this sets isDst = 1
539      * time.monthDay += 1;     // changes the date to Nov 5, 2007, 12am
540      * millis = time.toMillis(false);   // millis is Nov 4, 2007, 11pm
541      * millis = time.toMillis(true);    // millis is Nov 5, 2007, 12am
542      * </pre>
543      *
544      * <p>
545      * To avoid this problem, use <tt>toMillis(true)</tt>
546      * after adding or subtracting days or explicitly setting the "monthDay"
547      * field.  On the other hand, if you are adding
548      * or subtracting hours or minutes, then you should use
549      * <tt>toMillis(false)</tt>.
550      *
551      * <p>
552      * You should also use <tt>toMillis(false)</tt> if you want
553      * to read back the same milliseconds that you set with {@link #set(long)}
554      * or {@link #set(Time)} or after parsing a date string.
555      */
toMillis(boolean ignoreDst)556     native public long toMillis(boolean ignoreDst);
557 
558     /**
559      * Sets the fields in this Time object given the UTC milliseconds.  After
560      * this method returns, all the fields are normalized.
561      * This also sets the "isDst" field to the correct value.
562      *
563      * @param millis the time in UTC milliseconds since the epoch.
564      */
set(long millis)565     native public void set(long millis);
566 
567     /**
568      * Format according to RFC 2445 DATETIME type.
569      *
570      * <p>
571      * The same as format("%Y%m%dT%H%M%S").
572      */
format2445()573     native public String format2445();
574 
575     /**
576      * Copy the value of that to this Time object. No normalization happens.
577      */
set(Time that)578     public void set(Time that) {
579         this.timezone = that.timezone;
580         this.allDay = that.allDay;
581         this.second = that.second;
582         this.minute = that.minute;
583         this.hour = that.hour;
584         this.monthDay = that.monthDay;
585         this.month = that.month;
586         this.year = that.year;
587         this.weekDay = that.weekDay;
588         this.yearDay = that.yearDay;
589         this.isDst = that.isDst;
590         this.gmtoff = that.gmtoff;
591     }
592 
593     /**
594      * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
595      * Call {@link #normalize(boolean)} if you need those.
596      */
set(int second, int minute, int hour, int monthDay, int month, int year)597     public void set(int second, int minute, int hour, int monthDay, int month, int year) {
598         this.allDay = false;
599         this.second = second;
600         this.minute = minute;
601         this.hour = hour;
602         this.monthDay = monthDay;
603         this.month = month;
604         this.year = year;
605         this.weekDay = 0;
606         this.yearDay = 0;
607         this.isDst = -1;
608         this.gmtoff = 0;
609     }
610 
611     /**
612      * Sets the date from the given fields.  Also sets allDay to true.
613      * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
614      * Call {@link #normalize(boolean)} if you need those.
615      *
616      * @param monthDay the day of the month (in the range [1,31])
617      * @param month the zero-based month number (in the range [0,11])
618      * @param year the year
619      */
set(int monthDay, int month, int year)620     public void set(int monthDay, int month, int year) {
621         this.allDay = true;
622         this.second = 0;
623         this.minute = 0;
624         this.hour = 0;
625         this.monthDay = monthDay;
626         this.month = month;
627         this.year = year;
628         this.weekDay = 0;
629         this.yearDay = 0;
630         this.isDst = -1;
631         this.gmtoff = 0;
632     }
633 
634     /**
635      * Returns true if the time represented by this Time object occurs before
636      * the given time.
637      *
638      * @param that a given Time object to compare against
639      * @return true if this time is less than the given time
640      */
before(Time that)641     public boolean before(Time that) {
642         return Time.compare(this, that) < 0;
643     }
644 
645 
646     /**
647      * Returns true if the time represented by this Time object occurs after
648      * the given time.
649      *
650      * @param that a given Time object to compare against
651      * @return true if this time is greater than the given time
652      */
after(Time that)653     public boolean after(Time that) {
654         return Time.compare(this, that) > 0;
655     }
656 
657     /**
658      * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.)
659      * and gives a number that can be added to the yearDay to give the
660      * closest Thursday yearDay.
661      */
662     private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 };
663 
664     /**
665      * Computes the week number according to ISO 8601.  The current Time
666      * object must already be normalized because this method uses the
667      * yearDay and weekDay fields.
668      *
669      * <p>
670      * In IS0 8601, weeks start on Monday.
671      * The first week of the year (week 1) is defined by ISO 8601 as the
672      * first week with four or more of its days in the starting year.
673      * Or equivalently, the week containing January 4.  Or equivalently,
674      * the week with the year's first Thursday in it.
675      * </p>
676      *
677      * <p>
678      * The week number can be calculated by counting Thursdays.  Week N
679      * contains the Nth Thursday of the year.
680      * </p>
681      *
682      * @return the ISO week number.
683      */
getWeekNumber()684     public int getWeekNumber() {
685         // Get the year day for the closest Thursday
686         int closestThursday = yearDay + sThursdayOffset[weekDay];
687 
688         // Year days start at 0
689         if (closestThursday >= 0 && closestThursday <= 364) {
690             return closestThursday / 7 + 1;
691         }
692 
693         // The week crosses a year boundary.
694         Time temp = new Time(this);
695         temp.monthDay += sThursdayOffset[weekDay];
696         temp.normalize(true /* ignore isDst */);
697         return temp.yearDay / 7 + 1;
698     }
699 
700     /**
701      * Return a string in the RFC 3339 format.
702      * <p>
703      * If allDay is true, expresses the time as Y-M-D</p>
704      * <p>
705      * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p>
706      * <p>
707      * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p>
708      * @param allDay
709      * @return string in the RFC 3339 format.
710      */
format3339(boolean allDay)711     public String format3339(boolean allDay) {
712         if (allDay) {
713             return format(Y_M_D);
714         } else if (TIMEZONE_UTC.equals(timezone)) {
715             return format(Y_M_D_T_H_M_S_000_Z);
716         } else {
717             String base = format(Y_M_D_T_H_M_S_000);
718             String sign = (gmtoff < 0) ? "-" : "+";
719             int offset = (int)Math.abs(gmtoff);
720             int minutes = (offset % 3600) / 60;
721             int hours = offset / 3600;
722 
723             return String.format("%s%s%02d:%02d", base, sign, hours, minutes);
724         }
725     }
726 
727     /**
728      * Returns true if the day of the given time is the epoch on the Julian Calendar
729      * (January 1, 1970 on the Gregorian calendar).
730      *
731      * @param time the time to test
732      * @return true if epoch.
733      */
isEpoch(Time time)734     public static boolean isEpoch(Time time) {
735         long millis = time.toMillis(true);
736         return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY;
737     }
738 
739     /**
740      * Computes the Julian day number, given the UTC milliseconds
741      * and the offset (in seconds) from UTC.  The Julian day for a given
742      * date will be the same for every timezone.  For example, the Julian
743      * day for July 1, 2008 is 2454649.  This is the same value no matter
744      * what timezone is being used.  The Julian day is useful for testing
745      * if two events occur on the same day and for determining the relative
746      * time of an event from the present ("yesterday", "3 days ago", etc.).
747      *
748      * <p>
749      * Use {@link #toMillis(boolean)} to get the milliseconds.
750      *
751      * @param millis the time in UTC milliseconds
752      * @param gmtoff the offset from UTC in seconds
753      * @return the Julian day
754      */
getJulianDay(long millis, long gmtoff)755     public static int getJulianDay(long millis, long gmtoff) {
756         long offsetMillis = gmtoff * 1000;
757         long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS;
758         return (int) julianDay + EPOCH_JULIAN_DAY;
759     }
760 
761     /**
762      * <p>Sets the time from the given Julian day number, which must be based on
763      * the same timezone that is set in this Time object.  The "gmtoff" field
764      * need not be initialized because the given Julian day may have a different
765      * GMT offset than whatever is currently stored in this Time object anyway.
766      * After this method returns all the fields will be normalized and the time
767      * will be set to 12am at the beginning of the given Julian day.
768      * </p>
769      *
770      * <p>
771      * The only exception to this is if 12am does not exist for that day because
772      * of daylight saving time.  For example, Cairo, Eqypt moves time ahead one
773      * hour at 12am on April 25, 2008 and there are a few other places that
774      * also change daylight saving time at 12am.  In those cases, the time
775      * will be set to 1am.
776      * </p>
777      *
778      * @param julianDay the Julian day in the timezone for this Time object
779      * @return the UTC milliseconds for the beginning of the Julian day
780      */
setJulianDay(int julianDay)781     public long setJulianDay(int julianDay) {
782         // Don't bother with the GMT offset since we don't know the correct
783         // value for the given Julian day.  Just get close and then adjust
784         // the day.
785         long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
786         set(millis);
787 
788         // Figure out how close we are to the requested Julian day.
789         // We can't be off by more than a day.
790         int approximateDay = getJulianDay(millis, gmtoff);
791         int diff = julianDay - approximateDay;
792         monthDay += diff;
793 
794         // Set the time to 12am and re-normalize.
795         hour = 0;
796         minute = 0;
797         second = 0;
798         millis = normalize(true);
799         return millis;
800     }
801 
802     /**
803      * Returns the week since {@link #EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted
804      * for first day of week. This takes a julian day and the week start day and
805      * calculates which week since {@link #EPOCH_JULIAN_DAY} that day occurs in,
806      * starting at 0. *Do not* use this to compute the ISO week number for the
807      * year.
808      *
809      * @param julianDay The julian day to calculate the week number for
810      * @param firstDayOfWeek Which week day is the first day of the week, see
811      *            {@link #SUNDAY}
812      * @return Weeks since the epoch
813      */
getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek)814     public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
815         int diff = THURSDAY - firstDayOfWeek;
816         if (diff < 0) {
817             diff += 7;
818         }
819         int refDay = EPOCH_JULIAN_DAY - diff;
820         return (julianDay - refDay) / 7;
821     }
822 
823     /**
824      * Takes a number of weeks since the epoch and calculates the Julian day of
825      * the Monday for that week. This assumes that the week containing the
826      * {@link #EPOCH_JULIAN_DAY} is considered week 0. It returns the Julian day
827      * for the Monday week weeks after the Monday of the week containing the
828      * epoch.
829      *
830      * @param week Number of weeks since the epoch
831      * @return The julian day for the Monday of the given week since the epoch
832      */
getJulianMondayFromWeeksSinceEpoch(int week)833     public static int getJulianMondayFromWeeksSinceEpoch(int week) {
834         return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
835     }
836 }
837