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