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