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