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