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