• 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 (nativeParse3339(s)) {
485              timezone = TIMEZONE_UTC;
486              return true;
487          }
488          return false;
489      }
490 
nativeParse3339(String s)491      native private boolean nativeParse3339(String s);
492 
493     /**
494      * Returns the timezone string that is currently set for the device.
495      */
getCurrentTimezone()496     public static String getCurrentTimezone() {
497         return TimeZone.getDefault().getID();
498     }
499 
500     /**
501      * Sets the time of the given Time object to the current time.
502      */
setToNow()503     native public void setToNow();
504 
505     /**
506      * Converts this time to milliseconds. Suitable for interacting with the
507      * standard java libraries. The time is in UTC milliseconds since the epoch.
508      * This does an implicit normalization to compute the milliseconds but does
509      * <em>not</em> change any of the fields in this Time object.  If you want
510      * to normalize the fields in this Time object and also get the milliseconds
511      * then use {@link #normalize(boolean)}.
512      *
513      * <p>
514      * If "ignoreDst" is false, then this method uses the current setting of the
515      * "isDst" field and will adjust the returned time if the "isDst" field is
516      * wrong for the given time.  See the sample code below for an example of
517      * this.
518      *
519      * <p>
520      * If "ignoreDst" is true, then this method ignores the current setting of
521      * the "isDst" field in this Time object and will instead figure out the
522      * correct value of "isDst" (as best it can) from the fields in this
523      * Time object.  The only case where this method cannot figure out the
524      * correct value of the "isDst" field is when the time is inherently
525      * ambiguous because it falls in the hour that is repeated when switching
526      * from Daylight-Saving Time to Standard Time.
527      *
528      * <p>
529      * Here is an example where <tt>toMillis(true)</tt> adjusts the time,
530      * assuming that DST changes at 2am on Sunday, Nov 4, 2007.
531      *
532      * <pre>
533      * Time time = new Time();
534      * time.set(4, 10, 2007);  // set the date to Nov 4, 2007, 12am
535      * time.normalize();       // this sets isDst = 1
536      * time.monthDay += 1;     // changes the date to Nov 5, 2007, 12am
537      * millis = time.toMillis(false);   // millis is Nov 4, 2007, 11pm
538      * millis = time.toMillis(true);    // millis is Nov 5, 2007, 12am
539      * </pre>
540      *
541      * <p>
542      * To avoid this problem, use <tt>toMillis(true)</tt>
543      * after adding or subtracting days or explicitly setting the "monthDay"
544      * field.  On the other hand, if you are adding
545      * or subtracting hours or minutes, then you should use
546      * <tt>toMillis(false)</tt>.
547      *
548      * <p>
549      * You should also use <tt>toMillis(false)</tt> if you want
550      * to read back the same milliseconds that you set with {@link #set(long)}
551      * or {@link #set(Time)} or after parsing a date string.
552      */
toMillis(boolean ignoreDst)553     native public long toMillis(boolean ignoreDst);
554 
555     /**
556      * Sets the fields in this Time object given the UTC milliseconds.  After
557      * this method returns, all the fields are normalized.
558      * This also sets the "isDst" field to the correct value.
559      *
560      * @param millis the time in UTC milliseconds since the epoch.
561      */
set(long millis)562     native public void set(long millis);
563 
564     /**
565      * Format according to RFC 2445 DATETIME type.
566      *
567      * <p>
568      * The same as format("%Y%m%dT%H%M%S").
569      */
format2445()570     native public String format2445();
571 
572     /**
573      * Copy the value of that to this Time object. No normalization happens.
574      */
set(Time that)575     public void set(Time that) {
576         this.timezone = that.timezone;
577         this.allDay = that.allDay;
578         this.second = that.second;
579         this.minute = that.minute;
580         this.hour = that.hour;
581         this.monthDay = that.monthDay;
582         this.month = that.month;
583         this.year = that.year;
584         this.weekDay = that.weekDay;
585         this.yearDay = that.yearDay;
586         this.isDst = that.isDst;
587         this.gmtoff = that.gmtoff;
588     }
589 
590     /**
591      * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
592      * Call {@link #normalize(boolean)} if you need those.
593      */
set(int second, int minute, int hour, int monthDay, int month, int year)594     public void set(int second, int minute, int hour, int monthDay, int month, int year) {
595         this.allDay = false;
596         this.second = second;
597         this.minute = minute;
598         this.hour = hour;
599         this.monthDay = monthDay;
600         this.month = month;
601         this.year = year;
602         this.weekDay = 0;
603         this.yearDay = 0;
604         this.isDst = -1;
605         this.gmtoff = 0;
606     }
607 
608     /**
609      * Sets the date from the given fields.  Also sets allDay to true.
610      * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
611      * Call {@link #normalize(boolean)} if you need those.
612      *
613      * @param monthDay the day of the month (in the range [1,31])
614      * @param month the zero-based month number (in the range [0,11])
615      * @param year the year
616      */
set(int monthDay, int month, int year)617     public void set(int monthDay, int month, int year) {
618         this.allDay = true;
619         this.second = 0;
620         this.minute = 0;
621         this.hour = 0;
622         this.monthDay = monthDay;
623         this.month = month;
624         this.year = year;
625         this.weekDay = 0;
626         this.yearDay = 0;
627         this.isDst = -1;
628         this.gmtoff = 0;
629     }
630 
631     /**
632      * Returns true if the time represented by this Time object occurs before
633      * the given time.
634      *
635      * @param that a given Time object to compare against
636      * @return true if this time is less than the given time
637      */
before(Time that)638     public boolean before(Time that) {
639         return Time.compare(this, that) < 0;
640     }
641 
642 
643     /**
644      * Returns true if the time represented by this Time object occurs after
645      * the given time.
646      *
647      * @param that a given Time object to compare against
648      * @return true if this time is greater than the given time
649      */
after(Time that)650     public boolean after(Time that) {
651         return Time.compare(this, that) > 0;
652     }
653 
654     /**
655      * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.)
656      * and gives a number that can be added to the yearDay to give the
657      * closest Thursday yearDay.
658      */
659     private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 };
660 
661     /**
662      * Computes the week number according to ISO 8601.  The current Time
663      * object must already be normalized because this method uses the
664      * yearDay and weekDay fields.
665      *
666      * <p>
667      * In IS0 8601, weeks start on Monday.
668      * The first week of the year (week 1) is defined by ISO 8601 as the
669      * first week with four or more of its days in the starting year.
670      * Or equivalently, the week containing January 4.  Or equivalently,
671      * the week with the year's first Thursday in it.
672      * </p>
673      *
674      * <p>
675      * The week number can be calculated by counting Thursdays.  Week N
676      * contains the Nth Thursday of the year.
677      * </p>
678      *
679      * @return the ISO week number.
680      */
getWeekNumber()681     public int getWeekNumber() {
682         // Get the year day for the closest Thursday
683         int closestThursday = yearDay + sThursdayOffset[weekDay];
684 
685         // Year days start at 0
686         if (closestThursday >= 0 && closestThursday <= 364) {
687             return closestThursday / 7 + 1;
688         }
689 
690         // The week crosses a year boundary.
691         Time temp = new Time(this);
692         temp.monthDay += sThursdayOffset[weekDay];
693         temp.normalize(true /* ignore isDst */);
694         return temp.yearDay / 7 + 1;
695     }
696 
697     /**
698      * Return a string in the RFC 3339 format.
699      * <p>
700      * If allDay is true, expresses the time as Y-M-D</p>
701      * <p>
702      * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p>
703      * <p>
704      * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p>
705      * @param allDay
706      * @return string in the RFC 3339 format.
707      */
format3339(boolean allDay)708     public String format3339(boolean allDay) {
709         if (allDay) {
710             return format(Y_M_D);
711         } else if (TIMEZONE_UTC.equals(timezone)) {
712             return format(Y_M_D_T_H_M_S_000_Z);
713         } else {
714             String base = format(Y_M_D_T_H_M_S_000);
715             String sign = (gmtoff < 0) ? "-" : "+";
716             int offset = (int)Math.abs(gmtoff);
717             int minutes = (offset % 3600) / 60;
718             int hours = offset / 3600;
719 
720             return String.format("%s%s%02d:%02d", base, sign, hours, minutes);
721         }
722     }
723 
724     /**
725      * Returns true if the day of the given time is the epoch on the Julian Calendar
726      * (January 1, 1970 on the Gregorian calendar).
727      *
728      * @param time the time to test
729      * @return true if epoch.
730      */
isEpoch(Time time)731     public static boolean isEpoch(Time time) {
732         long millis = time.toMillis(true);
733         return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY;
734     }
735 
736     /**
737      * Computes the Julian day number, given the UTC milliseconds
738      * and the offset (in seconds) from UTC.  The Julian day for a given
739      * date will be the same for every timezone.  For example, the Julian
740      * day for July 1, 2008 is 2454649.  This is the same value no matter
741      * what timezone is being used.  The Julian day is useful for testing
742      * if two events occur on the same day and for determining the relative
743      * time of an event from the present ("yesterday", "3 days ago", etc.).
744      *
745      * <p>
746      * Use {@link #toMillis(boolean)} to get the milliseconds.
747      *
748      * @param millis the time in UTC milliseconds
749      * @param gmtoff the offset from UTC in seconds
750      * @return the Julian day
751      */
getJulianDay(long millis, long gmtoff)752     public static int getJulianDay(long millis, long gmtoff) {
753         long offsetMillis = gmtoff * 1000;
754         long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS;
755         return (int) julianDay + EPOCH_JULIAN_DAY;
756     }
757 
758     /**
759      * <p>Sets the time from the given Julian day number, which must be based on
760      * the same timezone that is set in this Time object.  The "gmtoff" field
761      * need not be initialized because the given Julian day may have a different
762      * GMT offset than whatever is currently stored in this Time object anyway.
763      * After this method returns all the fields will be normalized and the time
764      * will be set to 12am at the beginning of the given Julian day.
765      * </p>
766      *
767      * <p>
768      * The only exception to this is if 12am does not exist for that day because
769      * of daylight saving time.  For example, Cairo, Eqypt moves time ahead one
770      * hour at 12am on April 25, 2008 and there are a few other places that
771      * also change daylight saving time at 12am.  In those cases, the time
772      * will be set to 1am.
773      * </p>
774      *
775      * @param julianDay the Julian day in the timezone for this Time object
776      * @return the UTC milliseconds for the beginning of the Julian day
777      */
setJulianDay(int julianDay)778     public long setJulianDay(int julianDay) {
779         // Don't bother with the GMT offset since we don't know the correct
780         // value for the given Julian day.  Just get close and then adjust
781         // the day.
782         long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
783         set(millis);
784 
785         // Figure out how close we are to the requested Julian day.
786         // We can't be off by more than a day.
787         int approximateDay = getJulianDay(millis, gmtoff);
788         int diff = julianDay - approximateDay;
789         monthDay += diff;
790 
791         // Set the time to 12am and re-normalize.
792         hour = 0;
793         minute = 0;
794         second = 0;
795         millis = normalize(true);
796         return millis;
797     }
798 
799     /**
800      * Returns the week since {@link #EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted
801      * for first day of week. This takes a julian day and the week start day and
802      * calculates which week since {@link #EPOCH_JULIAN_DAY} that day occurs in,
803      * starting at 0. *Do not* use this to compute the ISO week number for the
804      * year.
805      *
806      * @param julianDay The julian day to calculate the week number for
807      * @param firstDayOfWeek Which week day is the first day of the week, see
808      *            {@link #SUNDAY}
809      * @return Weeks since the epoch
810      */
getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek)811     public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
812         int diff = THURSDAY - firstDayOfWeek;
813         if (diff < 0) {
814             diff += 7;
815         }
816         int refDay = EPOCH_JULIAN_DAY - diff;
817         return (julianDay - refDay) / 7;
818     }
819 
820     /**
821      * Takes a number of weeks since the epoch and calculates the Julian day of
822      * the Monday for that week. This assumes that the week containing the
823      * {@link #EPOCH_JULIAN_DAY} is considered week 0. It returns the Julian day
824      * for the Monday week weeks after the Monday of the week containing the
825      * epoch.
826      *
827      * @param week Number of weeks since the epoch
828      * @return The julian day for the Monday of the given week since the epoch
829      */
getJulianMondayFromWeeksSinceEpoch(int week)830     public static int getJulianMondayFromWeeksSinceEpoch(int week) {
831         return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
832     }
833 }
834