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.util.TimeFormatException;
     20 
     21 import libcore.util.ZoneInfo;
     22 import libcore.util.ZoneInfoDB;
     23 
     24 import java.io.IOException;
     25 import java.util.Locale;
     26 import java.util.TimeZone;
     27 
     28 /**
     29  * An alternative to the {@link java.util.Calendar} and
     30  * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents
     31  * a moment in time, specified with second precision. It is modelled after
     32  * struct tm. This class is not thread-safe and does not consider leap seconds.
     33  *
     34  * <p>This class has a number of issues and it is recommended that
     35  * {@link java.util.GregorianCalendar} is used instead.
     36  *
     37  * <p>Known issues:
     38  * <ul>
     39  *     <li>For historical reasons when performing time calculations all arithmetic currently takes
     40  *     place using 32-bit integers. This limits the reliable time range representable from 1902
     41  *     until 2037.See the wikipedia article on the
     42  *     <a href="http://en.wikipedia.org/wiki/Year_2038_problem">Year 2038 problem</a> for details.
     43  *     Do not rely on this behavior; it may change in the future.
     44  *     </li>
     45  *     <li>Calling {@link #switchTimezone(String)} on a date that cannot exist, such as a wall time
     46  *     that was skipped due to a DST transition, will result in a date in 1969 (i.e. -1, or 1 second
     47  *     before 1st Jan 1970 UTC).</li>
     48  *     <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for
     49  *     use with non-ASCII scripts.</li>
     50  *     <li>No support for pseudo-zones like "GMT-07:00".</li>
     51  * </ul>
     52  *
     53  * @deprecated Use {@link java.util.GregorianCalendar} instead.
     54  */
     55 @Deprecated
     56 public class Time {
     57     private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
     58     private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
     59     private static final String Y_M_D = "%Y-%m-%d";
     60 
     61     public static final String TIMEZONE_UTC = "UTC";
     62 
     63     /**
     64      * The Julian day of the epoch, that is, January 1, 1970 on the Gregorian
     65      * calendar.
     66      */
     67     public static final int EPOCH_JULIAN_DAY = 2440588;
     68 
     69     /**
     70      * The Julian day of the Monday in the week of the epoch, December 29, 1969
     71      * on the Gregorian calendar.
     72      */
     73     public static final int MONDAY_BEFORE_JULIAN_EPOCH = EPOCH_JULIAN_DAY - 3;
     74 
     75     /**
     76      * True if this is an allDay event. The hour, minute, second fields are
     77      * all zero, and the date is displayed the same in all time zones.
     78      */
     79     public boolean allDay;
     80 
     81     /**
     82      * Seconds [0-61] (2 leap seconds allowed)
     83      */
     84     public int second;
     85 
     86     /**
     87      * Minute [0-59]
     88      */
     89     public int minute;
     90 
     91     /**
     92      * Hour of day [0-23]
     93      */
     94     public int hour;
     95 
     96     /**
     97      * Day of month [1-31]
     98      */
     99     public int monthDay;
    100 
    101     /**
    102      * Month [0-11]
    103      */
    104     public int month;
    105 
    106     /**
    107      * Year. For example, 1970.
    108      */
    109     public int year;
    110 
    111     /**
    112      * Day of week [0-6]
    113      */
    114     public int weekDay;
    115 
    116     /**
    117      * Day of year [0-365]
    118      */
    119     public int yearDay;
    120 
    121     /**
    122      * This time is in daylight savings time. One of:
    123      * <ul>
    124      * <li><b>positive</b> - in dst</li>
    125      * <li><b>0</b> - not in dst</li>
    126      * <li><b>negative</b> - unknown</li>
    127      * </ul>
    128      */
    129     public int isDst;
    130 
    131     /**
    132      * Offset in seconds from UTC including any DST offset.
    133      */
    134     public long gmtoff;
    135 
    136     /**
    137      * The timezone for this Time.  Should not be null.
    138      */
    139     public String timezone;
    140 
    141     /*
    142      * Define symbolic constants for accessing the fields in this class. Used in
    143      * getActualMaximum().
    144      */
    145     public static final int SECOND = 1;
    146     public static final int MINUTE = 2;
    147     public static final int HOUR = 3;
    148     public static final int MONTH_DAY = 4;
    149     public static final int MONTH = 5;
    150     public static final int YEAR = 6;
    151     public static final int WEEK_DAY = 7;
    152     public static final int YEAR_DAY = 8;
    153     public static final int WEEK_NUM = 9;
    154 
    155     public static final int SUNDAY = 0;
    156     public static final int MONDAY = 1;
    157     public static final int TUESDAY = 2;
    158     public static final int WEDNESDAY = 3;
    159     public static final int THURSDAY = 4;
    160     public static final int FRIDAY = 5;
    161     public static final int SATURDAY = 6;
    162 
    163     // An object that is reused for date calculations.
    164     private TimeCalculator calculator;
    165 
    166     /**
    167      * Construct a Time object in the timezone named by the string
    168      * argument "timezone". The time is initialized to Jan 1, 1970.
    169      * @param timezoneId string containing the timezone to use.
    170      * @see TimeZone
    171      */
    172     public Time(String timezoneId) {
    173         if (timezoneId == null) {
    174             throw new NullPointerException("timezoneId is null!");
    175         }
    176         initialize(timezoneId);
    177     }
    178 
    179     /**
    180      * Construct a Time object in the default timezone. The time is initialized to
    181      * Jan 1, 1970.
    182      */
    183     public Time() {
    184         initialize(TimeZone.getDefault().getID());
    185     }
    186 
    187     /**
    188      * A copy constructor.  Construct a Time object by copying the given
    189      * Time object.  No normalization occurs.
    190      *
    191      * @param other
    192      */
    193     public Time(Time other) {
    194         initialize(other.timezone);
    195         set(other);
    196     }
    197 
    198     /** Initialize the Time to 00:00:00 1/1/1970 in the specified timezone. */
    199     private void initialize(String timezoneId) {
    200         this.timezone = timezoneId;
    201         this.year = 1970;
    202         this.monthDay = 1;
    203         // Set the daylight-saving indicator to the unknown value -1 so that
    204         // it will be recomputed.
    205         this.isDst = -1;
    206 
    207         // A reusable object that performs the date/time calculations.
    208         calculator = new TimeCalculator(timezoneId);
    209     }
    210 
    211     /**
    212      * Ensures the values in each field are in range. For example if the
    213      * current value of this calendar is March 32, normalize() will convert it
    214      * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff.
    215      *
    216      * <p>
    217      * If "ignoreDst" is true, then this method sets the "isDst" field to -1
    218      * (the "unknown" value) before normalizing.  It then computes the
    219      * time in milliseconds and sets the correct value for "isDst" if the
    220      * fields resolve to a valid date / time.
    221      *
    222      * <p>
    223      * See {@link #toMillis(boolean)} for more information about when to
    224      * use <tt>true</tt> or <tt>false</tt> for "ignoreDst" and when {@code -1}
    225      * might be returned.
    226      *
    227      * @return the UTC milliseconds since the epoch, or {@code -1}
    228      */
    229     public long normalize(boolean ignoreDst) {
    230         calculator.copyFieldsFromTime(this);
    231         long timeInMillis = calculator.toMillis(ignoreDst);
    232         calculator.copyFieldsToTime(this);
    233         return timeInMillis;
    234     }
    235 
    236     /**
    237      * Convert this time object so the time represented remains the same, but is
    238      * instead located in a different timezone. This method automatically calls
    239      * normalize() in some cases.
    240      *
    241      * <p>This method can return incorrect results if the date / time cannot be normalized.
    242      */
    243     public void switchTimezone(String timezone) {
    244         calculator.copyFieldsFromTime(this);
    245         calculator.switchTimeZone(timezone);
    246         calculator.copyFieldsToTime(this);
    247         this.timezone = timezone;
    248     }
    249 
    250     private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31,
    251             31, 30, 31, 30, 31 };
    252 
    253     /**
    254      * Return the maximum possible value for the given field given the value of
    255      * the other fields. Requires that it be normalized for MONTH_DAY and
    256      * YEAR_DAY.
    257      * @param field one of the constants for HOUR, MINUTE, SECOND, etc.
    258      * @return the maximum value for the field.
    259      */
    260     public int getActualMaximum(int field) {
    261         switch (field) {
    262         case SECOND:
    263             return 59; // leap seconds, bah humbug
    264         case MINUTE:
    265             return 59;
    266         case HOUR:
    267             return 23;
    268         case MONTH_DAY: {
    269             int n = DAYS_PER_MONTH[this.month];
    270             if (n != 28) {
    271                 return n;
    272             } else {
    273                 int y = this.year;
    274                 return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29 : 28;
    275             }
    276         }
    277         case MONTH:
    278             return 11;
    279         case YEAR:
    280             return 2037;
    281         case WEEK_DAY:
    282             return 6;
    283         case YEAR_DAY: {
    284             int y = this.year;
    285             // Year days are numbered from 0, so the last one is usually 364.
    286             return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 365 : 364;
    287         }
    288         case WEEK_NUM:
    289             throw new RuntimeException("WEEK_NUM not implemented");
    290         default:
    291             throw new RuntimeException("bad field=" + field);
    292         }
    293     }
    294 
    295     /**
    296      * Clears all values, setting the timezone to the given timezone. Sets isDst
    297      * to a negative value to mean "unknown".
    298      * @param timezoneId the timezone to use.
    299      */
    300     public void clear(String timezoneId) {
    301         if (timezoneId == null) {
    302             throw new NullPointerException("timezone is null!");
    303         }
    304         this.timezone = timezoneId;
    305         this.allDay = false;
    306         this.second = 0;
    307         this.minute = 0;
    308         this.hour = 0;
    309         this.monthDay = 0;
    310         this.month = 0;
    311         this.year = 0;
    312         this.weekDay = 0;
    313         this.yearDay = 0;
    314         this.gmtoff = 0;
    315         this.isDst = -1;
    316     }
    317 
    318     /**
    319      * Compare two {@code Time} objects and return a negative number if {@code
    320      * a} is less than {@code b}, a positive number if {@code a} is greater than
    321      * {@code b}, or 0 if they are equal.
    322      *
    323      * <p>
    324      * This method can return an incorrect answer when the date / time fields of
    325      * either {@code Time} have been set to a local time that contradicts the
    326      * available timezone information.
    327      *
    328      * @param a first {@code Time} instance to compare
    329      * @param b second {@code Time} instance to compare
    330      * @throws NullPointerException if either argument is {@code null}
    331      * @throws IllegalArgumentException if {@link #allDay} is true but {@code
    332      *             hour}, {@code minute}, and {@code second} are not 0.
    333      * @return a negative result if {@code a} is earlier, a positive result if
    334      *         {@code b} is earlier, or 0 if they are equal.
    335      */
    336     public static int compare(Time a, Time b) {
    337         if (a == null) {
    338             throw new NullPointerException("a == null");
    339         } else if (b == null) {
    340             throw new NullPointerException("b == null");
    341         }
    342         a.calculator.copyFieldsFromTime(a);
    343         b.calculator.copyFieldsFromTime(b);
    344 
    345         return TimeCalculator.compare(a.calculator, b.calculator);
    346     }
    347 
    348     /**
    349      * Print the current value given the format string provided. See man
    350      * strftime for what means what. The final string must be less than 256
    351      * characters.
    352      * @param format a string containing the desired format.
    353      * @return a String containing the current time expressed in the current locale.
    354      */
    355     public String format(String format) {
    356         calculator.copyFieldsFromTime(this);
    357         return calculator.format(format);
    358     }
    359 
    360     /**
    361      * Return the current time in YYYYMMDDTHHMMSS<tz> format
    362      */
    363     @Override
    364     public String toString() {
    365         // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff
    366         // happens during debugging when the debugger calls toString().
    367         TimeCalculator calculator = new TimeCalculator(this.timezone);
    368         calculator.copyFieldsFromTime(this);
    369         return calculator.toStringInternal();
    370     }
    371 
    372     /**
    373      * Parses a date-time string in either the RFC 2445 format or an abbreviated
    374      * format that does not include the "time" field.  For example, all of the
    375      * following strings are valid:
    376      *
    377      * <ul>
    378      *   <li>"20081013T160000Z"</li>
    379      *   <li>"20081013T160000"</li>
    380      *   <li>"20081013"</li>
    381      * </ul>
    382      *
    383      * Returns whether or not the time is in UTC (ends with Z).  If the string
    384      * ends with "Z" then the timezone is set to UTC.  If the date-time string
    385      * included only a date and no time field, then the <code>allDay</code>
    386      * field of this Time class is set to true and the <code>hour</code>,
    387      * <code>minute</code>, and <code>second</code> fields are set to zero;
    388      * otherwise (a time field was included in the date-time string)
    389      * <code>allDay</code> is set to false. The fields <code>weekDay</code>,
    390      * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero,
    391      * and the field <code>isDst</code> is set to -1 (unknown).  To set those
    392      * fields, call {@link #normalize(boolean)} after parsing.
    393      *
    394      * To parse a date-time string and convert it to UTC milliseconds, do
    395      * something like this:
    396      *
    397      * <pre>
    398      *   Time time = new Time();
    399      *   String date = "20081013T160000Z";
    400      *   time.parse(date);
    401      *   long millis = time.normalize(false);
    402      * </pre>
    403      *
    404      * @param s the string to parse
    405      * @return true if the resulting time value is in UTC time
    406      * @throws android.util.TimeFormatException if s cannot be parsed.
    407      */
    408     public boolean parse(String s) {
    409         if (s == null) {
    410             throw new NullPointerException("time string is null");
    411         }
    412         if (parseInternal(s)) {
    413             timezone = TIMEZONE_UTC;
    414             return true;
    415         }
    416         return false;
    417     }
    418 
    419     /**
    420      * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
    421      */
    422     private boolean parseInternal(String s) {
    423         int len = s.length();
    424         if (len < 8) {
    425             throw new TimeFormatException("String is too short: \"" + s +
    426                     "\" Expected at least 8 characters.");
    427         }
    428 
    429         boolean inUtc = false;
    430 
    431         // year
    432         int n = getChar(s, 0, 1000);
    433         n += getChar(s, 1, 100);
    434         n += getChar(s, 2, 10);
    435         n += getChar(s, 3, 1);
    436         year = n;
    437 
    438         // month
    439         n = getChar(s, 4, 10);
    440         n += getChar(s, 5, 1);
    441         n--;
    442         month = n;
    443 
    444         // day of month
    445         n = getChar(s, 6, 10);
    446         n += getChar(s, 7, 1);
    447         monthDay = n;
    448 
    449         if (len > 8) {
    450             if (len < 15) {
    451                 throw new TimeFormatException(
    452                         "String is too short: \"" + s
    453                                 + "\" If there are more than 8 characters there must be at least"
    454                                 + " 15.");
    455             }
    456             checkChar(s, 8, 'T');
    457             allDay = false;
    458 
    459             // hour
    460             n = getChar(s, 9, 10);
    461             n += getChar(s, 10, 1);
    462             hour = n;
    463 
    464             // min
    465             n = getChar(s, 11, 10);
    466             n += getChar(s, 12, 1);
    467             minute = n;
    468 
    469             // sec
    470             n = getChar(s, 13, 10);
    471             n += getChar(s, 14, 1);
    472             second = n;
    473 
    474             if (len > 15) {
    475                 // Z
    476                 checkChar(s, 15, 'Z');
    477                 inUtc = true;
    478             }
    479         } else {
    480             allDay = true;
    481             hour = 0;
    482             minute = 0;
    483             second = 0;
    484         }
    485 
    486         weekDay = 0;
    487         yearDay = 0;
    488         isDst = -1;
    489         gmtoff = 0;
    490         return inUtc;
    491     }
    492 
    493     private void checkChar(String s, int spos, char expected) {
    494         char c = s.charAt(spos);
    495         if (c != expected) {
    496             throw new TimeFormatException(String.format(
    497                     "Unexpected character 0x%02d at pos=%d.  Expected 0x%02d (\'%c\').",
    498                     (int) c, spos, (int) expected, expected));
    499         }
    500     }
    501 
    502     private static int getChar(String s, int spos, int mul) {
    503         char c = s.charAt(spos);
    504         if (Character.isDigit(c)) {
    505             return Character.getNumericValue(c) * mul;
    506         } else {
    507             throw new TimeFormatException("Parse error at pos=" + spos);
    508         }
    509     }
    510 
    511     /**
    512      * Parse a time in RFC 3339 format.  This method also parses simple dates
    513      * (that is, strings that contain no time or time offset).  For example,
    514      * all of the following strings are valid:
    515      *
    516      * <ul>
    517      *   <li>"2008-10-13T16:00:00.000Z"</li>
    518      *   <li>"2008-10-13T16:00:00.000+07:00"</li>
    519      *   <li>"2008-10-13T16:00:00.000-07:00"</li>
    520      *   <li>"2008-10-13"</li>
    521      * </ul>
    522      *
    523      * <p>
    524      * If the string contains a time and time offset, then the time offset will
    525      * be used to convert the time value to UTC.
    526      * </p>
    527      *
    528      * <p>
    529      * If the given string contains just a date (with no time field), then
    530      * the {@link #allDay} field is set to true and the {@link #hour},
    531      * {@link #minute}, and  {@link #second} fields are set to zero.
    532      * </p>
    533      *
    534      * <p>
    535      * Returns true if the resulting time value is in UTC time.
    536      * </p>
    537      *
    538      * @param s the string to parse
    539      * @return true if the resulting time value is in UTC time
    540      * @throws android.util.TimeFormatException if s cannot be parsed.
    541      */
    542      public boolean parse3339(String s) {
    543          if (s == null) {
    544              throw new NullPointerException("time string is null");
    545          }
    546          if (parse3339Internal(s)) {
    547              timezone = TIMEZONE_UTC;
    548              return true;
    549          }
    550          return false;
    551      }
    552 
    553      private boolean parse3339Internal(String s) {
    554          int len = s.length();
    555          if (len < 10) {
    556              throw new TimeFormatException("String too short --- expected at least 10 characters.");
    557          }
    558          boolean inUtc = false;
    559 
    560          // year
    561          int n = getChar(s, 0, 1000);
    562          n += getChar(s, 1, 100);
    563          n += getChar(s, 2, 10);
    564          n += getChar(s, 3, 1);
    565          year = n;
    566 
    567          checkChar(s, 4, '-');
    568 
    569          // month
    570          n = getChar(s, 5, 10);
    571          n += getChar(s, 6, 1);
    572          --n;
    573          month = n;
    574 
    575          checkChar(s, 7, '-');
    576 
    577          // day
    578          n = getChar(s, 8, 10);
    579          n += getChar(s, 9, 1);
    580          monthDay = n;
    581 
    582          if (len >= 19) {
    583              // T
    584              checkChar(s, 10, 'T');
    585              allDay = false;
    586 
    587              // hour
    588              n = getChar(s, 11, 10);
    589              n += getChar(s, 12, 1);
    590 
    591              // Note that this.hour is not set here. It is set later.
    592              int hour = n;
    593 
    594              checkChar(s, 13, ':');
    595 
    596              // minute
    597              n = getChar(s, 14, 10);
    598              n += getChar(s, 15, 1);
    599              // Note that this.minute is not set here. It is set later.
    600              int minute = n;
    601 
    602              checkChar(s, 16, ':');
    603 
    604              // second
    605              n = getChar(s, 17, 10);
    606              n += getChar(s, 18, 1);
    607              second = n;
    608 
    609              // skip the '.XYZ' -- we don't care about subsecond precision.
    610 
    611              int tzIndex = 19;
    612              if (tzIndex < len && s.charAt(tzIndex) == '.') {
    613                  do {
    614                      tzIndex++;
    615                  } while (tzIndex < len && Character.isDigit(s.charAt(tzIndex)));
    616              }
    617 
    618              int offset = 0;
    619              if (len > tzIndex) {
    620                  char c = s.charAt(tzIndex);
    621                  // NOTE: the offset is meant to be subtracted to get from local time
    622                  // to UTC.  we therefore use 1 for '-' and -1 for '+'.
    623                  switch (c) {
    624                      case 'Z':
    625                          // Zulu time -- UTC
    626                          offset = 0;
    627                          break;
    628                      case '-':
    629                          offset = 1;
    630                          break;
    631                      case '+':
    632                          offset = -1;
    633                          break;
    634                      default:
    635                          throw new TimeFormatException(String.format(
    636                                  "Unexpected character 0x%02d at position %d.  Expected + or -",
    637                                  (int) c, tzIndex));
    638                  }
    639                  inUtc = true;
    640 
    641                  if (offset != 0) {
    642                      if (len < tzIndex + 6) {
    643                          throw new TimeFormatException(
    644                                  String.format("Unexpected length; should be %d characters",
    645                                          tzIndex + 6));
    646                      }
    647 
    648                      // hour
    649                      n = getChar(s, tzIndex + 1, 10);
    650                      n += getChar(s, tzIndex + 2, 1);
    651                      n *= offset;
    652                      hour += n;
    653 
    654                      // minute
    655                      n = getChar(s, tzIndex + 4, 10);
    656                      n += getChar(s, tzIndex + 5, 1);
    657                      n *= offset;
    658                      minute += n;
    659                  }
    660              }
    661              this.hour = hour;
    662              this.minute = minute;
    663 
    664              if (offset != 0) {
    665                  normalize(false);
    666              }
    667          } else {
    668              allDay = true;
    669              this.hour = 0;
    670              this.minute = 0;
    671              this.second = 0;
    672          }
    673 
    674          this.weekDay = 0;
    675          this.yearDay = 0;
    676          this.isDst = -1;
    677          this.gmtoff = 0;
    678          return inUtc;
    679      }
    680 
    681     /**
    682      * Returns the timezone string that is currently set for the device.
    683      */
    684     public static String getCurrentTimezone() {
    685         return TimeZone.getDefault().getID();
    686     }
    687 
    688     /**
    689      * Sets the time of the given Time object to the current time.
    690      */
    691     public void setToNow() {
    692         set(System.currentTimeMillis());
    693     }
    694 
    695     /**
    696      * Converts this time to milliseconds. Suitable for interacting with the
    697      * standard java libraries. The time is in UTC milliseconds since the epoch.
    698      * This does an implicit normalization to compute the milliseconds but does
    699      * <em>not</em> change any of the fields in this Time object.  If you want
    700      * to normalize the fields in this Time object and also get the milliseconds
    701      * then use {@link #normalize(boolean)}.
    702      *
    703      * <p>
    704      * If "ignoreDst" is false, then this method uses the current setting of the
    705      * "isDst" field and will adjust the returned time if the "isDst" field is
    706      * wrong for the given time.  See the sample code below for an example of
    707      * this.
    708      *
    709      * <p>
    710      * If "ignoreDst" is true, then this method ignores the current setting of
    711      * the "isDst" field in this Time object and will instead figure out the
    712      * correct value of "isDst" (as best it can) from the fields in this
    713      * Time object.  The only case where this method cannot figure out the
    714      * correct value of the "isDst" field is when the time is inherently
    715      * ambiguous because it falls in the hour that is repeated when switching
    716      * from Daylight-Saving Time to Standard Time.
    717      *
    718      * <p>
    719      * Here is an example where <tt>toMillis(true)</tt> adjusts the time,
    720      * assuming that DST changes at 2am on Sunday, Nov 4, 2007.
    721      *
    722      * <pre>
    723      * Time time = new Time();
    724      * time.set(4, 10, 2007);  // set the date to Nov 4, 2007, 12am
    725      * time.normalize(false);       // this sets isDst = 1
    726      * time.monthDay += 1;     // changes the date to Nov 5, 2007, 12am
    727      * millis = time.toMillis(false);   // millis is Nov 4, 2007, 11pm
    728      * millis = time.toMillis(true);    // millis is Nov 5, 2007, 12am
    729      * </pre>
    730      *
    731      * <p>
    732      * To avoid this problem, use <tt>toMillis(true)</tt>
    733      * after adding or subtracting days or explicitly setting the "monthDay"
    734      * field.  On the other hand, if you are adding
    735      * or subtracting hours or minutes, then you should use
    736      * <tt>toMillis(false)</tt>.
    737      *
    738      * <p>
    739      * You should also use <tt>toMillis(false)</tt> if you want
    740      * to read back the same milliseconds that you set with {@link #set(long)}
    741      *
    742      * <p>
    743      * This method can return {@code -1} when the date / time fields have been
    744      * set to a local time that conflicts with available timezone information.
    745      * For example, when daylight savings transitions cause an hour to be
    746      * skipped: times within that hour will return {@code -1} if isDst =
    747      * {@code -1}.
    748      *
    749      * or {@link #set(Time)} or after parsing a date string.
    750      */
    751     public long toMillis(boolean ignoreDst) {
    752         calculator.copyFieldsFromTime(this);
    753         return calculator.toMillis(ignoreDst);
    754     }
    755 
    756     /**
    757      * Sets the fields in this Time object given the UTC milliseconds.  After
    758      * this method returns, all the fields are normalized.
    759      * This also sets the "isDst" field to the correct value.
    760      *
    761      * @param millis the time in UTC milliseconds since the epoch.
    762      */
    763     public void set(long millis) {
    764         allDay = false;
    765         calculator.timezone = timezone;
    766         calculator.setTimeInMillis(millis);
    767         calculator.copyFieldsToTime(this);
    768     }
    769 
    770     /**
    771      * Format according to RFC 2445 DATE-TIME type.
    772      *
    773      * <p>The same as format("%Y%m%dT%H%M%S"), or format("%Y%m%dT%H%M%SZ") for a Time with a
    774      * timezone set to "UTC".
    775      */
    776     public String format2445() {
    777         calculator.copyFieldsFromTime(this);
    778         return calculator.format2445(!allDay);
    779     }
    780 
    781     /**
    782      * Copy the value of that to this Time object. No normalization happens.
    783      */
    784     public void set(Time that) {
    785         this.timezone = that.timezone;
    786         this.allDay = that.allDay;
    787         this.second = that.second;
    788         this.minute = that.minute;
    789         this.hour = that.hour;
    790         this.monthDay = that.monthDay;
    791         this.month = that.month;
    792         this.year = that.year;
    793         this.weekDay = that.weekDay;
    794         this.yearDay = that.yearDay;
    795         this.isDst = that.isDst;
    796         this.gmtoff = that.gmtoff;
    797     }
    798 
    799     /**
    800      * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
    801      * Call {@link #normalize(boolean)} if you need those.
    802      */
    803     public void set(int second, int minute, int hour, int monthDay, int month, int year) {
    804         this.allDay = false;
    805         this.second = second;
    806         this.minute = minute;
    807         this.hour = hour;
    808         this.monthDay = monthDay;
    809         this.month = month;
    810         this.year = year;
    811         this.weekDay = 0;
    812         this.yearDay = 0;
    813         this.isDst = -1;
    814         this.gmtoff = 0;
    815     }
    816 
    817     /**
    818      * Sets the date from the given fields.  Also sets allDay to true.
    819      * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
    820      * Call {@link #normalize(boolean)} if you need those.
    821      *
    822      * @param monthDay the day of the month (in the range [1,31])
    823      * @param month the zero-based month number (in the range [0,11])
    824      * @param year the year
    825      */
    826     public void set(int monthDay, int month, int year) {
    827         this.allDay = true;
    828         this.second = 0;
    829         this.minute = 0;
    830         this.hour = 0;
    831         this.monthDay = monthDay;
    832         this.month = month;
    833         this.year = year;
    834         this.weekDay = 0;
    835         this.yearDay = 0;
    836         this.isDst = -1;
    837         this.gmtoff = 0;
    838     }
    839 
    840     /**
    841      * Returns true if the time represented by this Time object occurs before
    842      * the given time.
    843      *
    844      * <p>
    845      * Equivalent to {@code Time.compare(this, that) < 0}. See
    846      * {@link #compare(Time, Time)} for details.
    847      *
    848      * @param that a given Time object to compare against
    849      * @return true if this time is less than the given time
    850      */
    851     public boolean before(Time that) {
    852         return Time.compare(this, that) < 0;
    853     }
    854 
    855 
    856     /**
    857      * Returns true if the time represented by this Time object occurs after
    858      * the given time.
    859      *
    860      * <p>
    861      * Equivalent to {@code Time.compare(this, that) > 0}. See
    862      * {@link #compare(Time, Time)} for details.
    863      *
    864      * @param that a given Time object to compare against
    865      * @return true if this time is greater than the given time
    866      */
    867     public boolean after(Time that) {
    868         return Time.compare(this, that) > 0;
    869     }
    870 
    871     /**
    872      * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.)
    873      * and gives a number that can be added to the yearDay to give the
    874      * closest Thursday yearDay.
    875      */
    876     private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 };
    877 
    878     /**
    879      * Computes the week number according to ISO 8601.  The current Time
    880      * object must already be normalized because this method uses the
    881      * yearDay and weekDay fields.
    882      *
    883      * <p>
    884      * In IS0 8601, weeks start on Monday.
    885      * The first week of the year (week 1) is defined by ISO 8601 as the
    886      * first week with four or more of its days in the starting year.
    887      * Or equivalently, the week containing January 4.  Or equivalently,
    888      * the week with the year's first Thursday in it.
    889      * </p>
    890      *
    891      * <p>
    892      * The week number can be calculated by counting Thursdays.  Week N
    893      * contains the Nth Thursday of the year.
    894      * </p>
    895      *
    896      * @return the ISO week number.
    897      */
    898     public int getWeekNumber() {
    899         // Get the year day for the closest Thursday
    900         int closestThursday = yearDay + sThursdayOffset[weekDay];
    901 
    902         // Year days start at 0
    903         if (closestThursday >= 0 && closestThursday <= 364) {
    904             return closestThursday / 7 + 1;
    905         }
    906 
    907         // The week crosses a year boundary.
    908         Time temp = new Time(this);
    909         temp.monthDay += sThursdayOffset[weekDay];
    910         temp.normalize(true /* ignore isDst */);
    911         return temp.yearDay / 7 + 1;
    912     }
    913 
    914     /**
    915      * Return a string in the RFC 3339 format.
    916      * <p>
    917      * If allDay is true, expresses the time as Y-M-D</p>
    918      * <p>
    919      * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p>
    920      * <p>
    921      * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p>
    922      * @return string in the RFC 3339 format.
    923      */
    924     public String format3339(boolean allDay) {
    925         if (allDay) {
    926             return format(Y_M_D);
    927         } else if (TIMEZONE_UTC.equals(timezone)) {
    928             return format(Y_M_D_T_H_M_S_000_Z);
    929         } else {
    930             String base = format(Y_M_D_T_H_M_S_000);
    931             String sign = (gmtoff < 0) ? "-" : "+";
    932             int offset = (int) Math.abs(gmtoff);
    933             int minutes = (offset % 3600) / 60;
    934             int hours = offset / 3600;
    935 
    936             return String.format(Locale.US, "%s%s%02d:%02d", base, sign, hours, minutes);
    937         }
    938     }
    939 
    940     /**
    941      * Returns true if the day of the given time is the epoch on the Julian Calendar
    942      * (January 1, 1970 on the Gregorian calendar).
    943      *
    944      * <p>
    945      * This method can return an incorrect answer when the date / time fields have
    946      * been set to a local time that contradicts the available timezone information.
    947      *
    948      * @param time the time to test
    949      * @return true if epoch.
    950      */
    951     public static boolean isEpoch(Time time) {
    952         long millis = time.toMillis(true);
    953         return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY;
    954     }
    955 
    956     /**
    957      * Computes the Julian day number for a point in time in a particular
    958      * timezone. The Julian day for a given date is the same for every
    959      * timezone. For example, the Julian day for July 1, 2008 is 2454649.
    960      *
    961      * <p>Callers must pass the time in UTC millisecond (as can be returned
    962      * by {@link #toMillis(boolean)} or {@link #normalize(boolean)})
    963      * and the offset from UTC of the timezone in seconds (as might be in
    964      * {@link #gmtoff}).
    965      *
    966      * <p>The Julian day is useful for testing if two events occur on the
    967      * same calendar date and for determining the relative time of an event
    968      * from the present ("yesterday", "3 days ago", etc.).
    969      *
    970      * @param millis the time in UTC milliseconds
    971      * @param gmtoff the offset from UTC in seconds
    972      * @return the Julian day
    973      */
    974     public static int getJulianDay(long millis, long gmtoff) {
    975         long offsetMillis = gmtoff * 1000;
    976         long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS;
    977         return (int) julianDay + EPOCH_JULIAN_DAY;
    978     }
    979 
    980     /**
    981      * <p>Sets the time from the given Julian day number, which must be based on
    982      * the same timezone that is set in this Time object.  The "gmtoff" field
    983      * need not be initialized because the given Julian day may have a different
    984      * GMT offset than whatever is currently stored in this Time object anyway.
    985      * After this method returns all the fields will be normalized and the time
    986      * will be set to 12am at the beginning of the given Julian day.
    987      * </p>
    988      *
    989      * <p>
    990      * The only exception to this is if 12am does not exist for that day because
    991      * of daylight saving time.  For example, Cairo, Eqypt moves time ahead one
    992      * hour at 12am on April 25, 2008 and there are a few other places that
    993      * also change daylight saving time at 12am.  In those cases, the time
    994      * will be set to 1am.
    995      * </p>
    996      *
    997      * @param julianDay the Julian day in the timezone for this Time object
    998      * @return the UTC milliseconds for the beginning of the Julian day
    999      */
   1000     public long setJulianDay(int julianDay) {
   1001         // Don't bother with the GMT offset since we don't know the correct
   1002         // value for the given Julian day.  Just get close and then adjust
   1003         // the day.
   1004         long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
   1005         set(millis);
   1006 
   1007         // Figure out how close we are to the requested Julian day.
   1008         // We can't be off by more than a day.
   1009         int approximateDay = getJulianDay(millis, gmtoff);
   1010         int diff = julianDay - approximateDay;
   1011         monthDay += diff;
   1012 
   1013         // Set the time to 12am and re-normalize.
   1014         hour = 0;
   1015         minute = 0;
   1016         second = 0;
   1017         millis = normalize(true);
   1018         return millis;
   1019     }
   1020 
   1021     /**
   1022      * Returns the week since {@link #EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted
   1023      * for first day of week. This takes a julian day and the week start day and
   1024      * calculates which week since {@link #EPOCH_JULIAN_DAY} that day occurs in,
   1025      * starting at 0. *Do not* use this to compute the ISO week number for the
   1026      * year.
   1027      *
   1028      * @param julianDay The julian day to calculate the week number for
   1029      * @param firstDayOfWeek Which week day is the first day of the week, see
   1030      *            {@link #SUNDAY}
   1031      * @return Weeks since the epoch
   1032      */
   1033     public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
   1034         int diff = THURSDAY - firstDayOfWeek;
   1035         if (diff < 0) {
   1036             diff += 7;
   1037         }
   1038         int refDay = EPOCH_JULIAN_DAY - diff;
   1039         return (julianDay - refDay) / 7;
   1040     }
   1041 
   1042     /**
   1043      * Takes a number of weeks since the epoch and calculates the Julian day of
   1044      * the Monday for that week. This assumes that the week containing the
   1045      * {@link #EPOCH_JULIAN_DAY} is considered week 0. It returns the Julian day
   1046      * for the Monday week weeks after the Monday of the week containing the
   1047      * epoch.
   1048      *
   1049      * @param week Number of weeks since the epoch
   1050      * @return The julian day for the Monday of the given week since the epoch
   1051      */
   1052     public static int getJulianMondayFromWeeksSinceEpoch(int week) {
   1053         return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
   1054     }
   1055 
   1056     /**
   1057      * A class that handles date/time calculations.
   1058      *
   1059      * This class originated as a port of a native C++ class ("android.Time") to pure Java. It is
   1060      * separate from the enclosing class because some methods copy the result of calculations back
   1061      * to the enclosing object, but others do not: thus separate state is retained.
   1062      */
   1063     private static class TimeCalculator {
   1064         public final ZoneInfo.WallTime wallTime;
   1065         public String timezone;
   1066 
   1067         // Information about the current timezone.
   1068         private ZoneInfo zoneInfo;
   1069 
   1070         public TimeCalculator(String timezoneId) {
   1071             this.zoneInfo = lookupZoneInfo(timezoneId);
   1072             this.wallTime = new ZoneInfo.WallTime();
   1073         }
   1074 
   1075         public long toMillis(boolean ignoreDst) {
   1076             if (ignoreDst) {
   1077                 wallTime.setIsDst(-1);
   1078             }
   1079 
   1080             int r = wallTime.mktime(zoneInfo);
   1081             if (r == -1) {
   1082                 return -1;
   1083             }
   1084             return r * 1000L;
   1085         }
   1086 
   1087         public void setTimeInMillis(long millis) {
   1088             // Preserve old 32-bit Android behavior.
   1089             int intSeconds = (int) (millis / 1000);
   1090 
   1091             updateZoneInfoFromTimeZone();
   1092             wallTime.localtime(intSeconds, zoneInfo);
   1093         }
   1094 
   1095         public String format(String format) {
   1096             if (format == null) {
   1097                 format = "%c";
   1098             }
   1099             TimeFormatter formatter = new TimeFormatter();
   1100             return formatter.format(format, wallTime, zoneInfo);
   1101         }
   1102 
   1103         private void updateZoneInfoFromTimeZone() {
   1104             if (!zoneInfo.getID().equals(timezone)) {
   1105                 this.zoneInfo = lookupZoneInfo(timezone);
   1106             }
   1107         }
   1108 
   1109         private static ZoneInfo lookupZoneInfo(String timezoneId) {
   1110             try {
   1111                 ZoneInfo zoneInfo = ZoneInfoDB.getInstance().makeTimeZone(timezoneId);
   1112                 if (zoneInfo == null) {
   1113                     zoneInfo = ZoneInfoDB.getInstance().makeTimeZone("GMT");
   1114                 }
   1115                 if (zoneInfo == null) {
   1116                     throw new AssertionError("GMT not found: \"" + timezoneId + "\"");
   1117                 }
   1118                 return zoneInfo;
   1119             } catch (IOException e) {
   1120                 // This should not ever be thrown.
   1121                 throw new AssertionError("Error loading timezone: \"" + timezoneId + "\"", e);
   1122             }
   1123         }
   1124 
   1125         public void switchTimeZone(String timezone) {
   1126             int seconds = wallTime.mktime(zoneInfo);
   1127             this.timezone = timezone;
   1128             updateZoneInfoFromTimeZone();
   1129             wallTime.localtime(seconds, zoneInfo);
   1130         }
   1131 
   1132         public String format2445(boolean hasTime) {
   1133             char[] buf = new char[hasTime ? 16 : 8];
   1134             int n = wallTime.getYear();
   1135 
   1136             buf[0] = toChar(n / 1000);
   1137             n %= 1000;
   1138             buf[1] = toChar(n / 100);
   1139             n %= 100;
   1140             buf[2] = toChar(n / 10);
   1141             n %= 10;
   1142             buf[3] = toChar(n);
   1143 
   1144             n = wallTime.getMonth() + 1;
   1145             buf[4] = toChar(n / 10);
   1146             buf[5] = toChar(n % 10);
   1147 
   1148             n = wallTime.getMonthDay();
   1149             buf[6] = toChar(n / 10);
   1150             buf[7] = toChar(n % 10);
   1151 
   1152             if (!hasTime) {
   1153                 return new String(buf, 0, 8);
   1154             }
   1155 
   1156             buf[8] = 'T';
   1157 
   1158             n = wallTime.getHour();
   1159             buf[9] = toChar(n / 10);
   1160             buf[10] = toChar(n % 10);
   1161 
   1162             n = wallTime.getMinute();
   1163             buf[11] = toChar(n / 10);
   1164             buf[12] = toChar(n % 10);
   1165 
   1166             n = wallTime.getSecond();
   1167             buf[13] = toChar(n / 10);
   1168             buf[14] = toChar(n % 10);
   1169 
   1170             if (TIMEZONE_UTC.equals(timezone)) {
   1171                 // The letter 'Z' is appended to the end.
   1172                 buf[15] = 'Z';
   1173                 return new String(buf, 0, 16);
   1174             } else {
   1175                 return new String(buf, 0, 15);
   1176             }
   1177         }
   1178 
   1179         private char toChar(int n) {
   1180             return (n >= 0 && n <= 9) ? (char) (n + '0') : ' ';
   1181         }
   1182 
   1183         /**
   1184          * A method that will return the state of this object in string form. Note: it has side
   1185          * effects and so has deliberately not been made the default {@link #toString()}.
   1186          */
   1187         public String toStringInternal() {
   1188             // This implementation possibly displays the un-normalized fields because that is
   1189             // what it has always done.
   1190             return String.format("%04d%02d%02dT%02d%02d%02d%s(%d,%d,%d,%d,%d)",
   1191                     wallTime.getYear(),
   1192                     wallTime.getMonth() + 1,
   1193                     wallTime.getMonthDay(),
   1194                     wallTime.getHour(),
   1195                     wallTime.getMinute(),
   1196                     wallTime.getSecond(),
   1197                     timezone,
   1198                     wallTime.getWeekDay(),
   1199                     wallTime.getYearDay(),
   1200                     wallTime.getGmtOffset(),
   1201                     wallTime.getIsDst(),
   1202                     toMillis(false /* use isDst */) / 1000
   1203             );
   1204 
   1205         }
   1206 
   1207         public static int compare(TimeCalculator aObject, TimeCalculator bObject) {
   1208             if (aObject.timezone.equals(bObject.timezone)) {
   1209                 // If the timezones are the same, we can easily compare the two times.
   1210                 int diff = aObject.wallTime.getYear() - bObject.wallTime.getYear();
   1211                 if (diff != 0) {
   1212                     return diff;
   1213                 }
   1214 
   1215                 diff = aObject.wallTime.getMonth() - bObject.wallTime.getMonth();
   1216                 if (diff != 0) {
   1217                     return diff;
   1218                 }
   1219 
   1220                 diff = aObject.wallTime.getMonthDay() - bObject.wallTime.getMonthDay();
   1221                 if (diff != 0) {
   1222                     return diff;
   1223                 }
   1224 
   1225                 diff = aObject.wallTime.getHour() - bObject.wallTime.getHour();
   1226                 if (diff != 0) {
   1227                     return diff;
   1228                 }
   1229 
   1230                 diff = aObject.wallTime.getMinute() - bObject.wallTime.getMinute();
   1231                 if (diff != 0) {
   1232                     return diff;
   1233                 }
   1234 
   1235                 diff = aObject.wallTime.getSecond() - bObject.wallTime.getSecond();
   1236                 if (diff != 0) {
   1237                     return diff;
   1238                 }
   1239 
   1240                 return 0;
   1241             } else {
   1242                 // Otherwise, convert to milliseconds and compare that. This requires that object be
   1243                 // normalized. Note: For dates that do not exist: toMillis() can return -1, which
   1244                 // can be confused with a valid time.
   1245                 long am = aObject.toMillis(false /* use isDst */);
   1246                 long bm = bObject.toMillis(false /* use isDst */);
   1247                 long diff = am - bm;
   1248                 return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
   1249             }
   1250 
   1251         }
   1252 
   1253         public void copyFieldsToTime(Time time) {
   1254             time.second = wallTime.getSecond();
   1255             time.minute = wallTime.getMinute();
   1256             time.hour = wallTime.getHour();
   1257             time.monthDay = wallTime.getMonthDay();
   1258             time.month = wallTime.getMonth();
   1259             time.year = wallTime.getYear();
   1260 
   1261             // Read-only fields that are derived from other information above.
   1262             time.weekDay = wallTime.getWeekDay();
   1263             time.yearDay = wallTime.getYearDay();
   1264 
   1265             // < 0: DST status unknown, 0: is not in DST, 1: is in DST
   1266             time.isDst = wallTime.getIsDst();
   1267             // This is in seconds and includes any DST offset too.
   1268             time.gmtoff = wallTime.getGmtOffset();
   1269         }
   1270 
   1271         public void copyFieldsFromTime(Time time) {
   1272             wallTime.setSecond(time.second);
   1273             wallTime.setMinute(time.minute);
   1274             wallTime.setHour(time.hour);
   1275             wallTime.setMonthDay(time.monthDay);
   1276             wallTime.setMonth(time.month);
   1277             wallTime.setYear(time.year);
   1278             wallTime.setWeekDay(time.weekDay);
   1279             wallTime.setYearDay(time.yearDay);
   1280             wallTime.setIsDst(time.isDst);
   1281             wallTime.setGmtOffset((int) time.gmtoff);
   1282 
   1283             if (time.allDay && (time.second != 0 || time.minute != 0 || time.hour != 0)) {
   1284                 throw new IllegalArgumentException("allDay is true but sec, min, hour are not 0.");
   1285             }
   1286 
   1287             timezone = time.timezone;
   1288             updateZoneInfoFromTimeZone();
   1289         }
   1290     }
   1291 }
   1292