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