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