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