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