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