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