Time.java revision 0df10e9b4e7719777ff9a389959c5c5deeef0eae
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
17package android.text.format;
18
19import android.content.res.Resources;
20
21import java.util.Locale;
22import 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 */
31public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
370    native private String format1(String format);
371
372    /**
373     * Return the current time in YYYYMMDDTHHMMSS<tz> format
374     */
375    @Override
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     */
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     */
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     */
458     public boolean parse3339(String s) {
459         if (nativeParse3339(s)) {
460             timezone = TIMEZONE_UTC;
461             return true;
462         }
463         return false;
464     }
465
466     native private boolean nativeParse3339(String s);
467
468    /**
469     * Returns the timezone string that is currently set for the device.
470     */
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     */
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(4, 10, 2007);  // 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     */
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     */
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     */
545    native public String format2445();
546
547    /**
548     * Copy the value of that to this Time object. No normalization happens.
549     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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