content: /*
   * Copyright (C) 2006 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.text.format;
  
  import android.util.TimeFormatException;
  
  import libcore.timezone.ZoneInfoDB;
  import libcore.util.ZoneInfo;
  
  import java.io.IOException;
  import java.util.Locale;
  import java.util.TimeZone;
  
  /**
   * An alternative to the {@link java.util.Calendar} and
   * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents
   * a moment in time, specified with second precision. It is modelled after
   * struct tm. This class is not thread-safe and does not consider leap seconds.
   *
   * <p>This class has a number of issues and it is recommended that
   * {@link java.util.GregorianCalendar} is used instead.
   *
   * <p>Known issues:
   * <ul>
   *     <li>For historical reasons when performing time calculations all arithmetic currently takes
   *     place using 32-bit integers. This limits the reliable time range representable from 1902
   *     until 2037.See the wikipedia article on the
   *     <a href="http://en.wikipedia.org/wiki/Year_2038_problem">Year 2038 problem</a> for details.
   *     Do not rely on this behavior; it may change in the future.
   *     </li>
   *     <li>Calling {@link #switchTimezone(String)} on a date that cannot exist, such as a wall time
   *     that was skipped due to a DST transition, will result in a date in 1969 (i.e. -1, or 1 second
   *     before 1st Jan 1970 UTC).</li>
   *     <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for
   *     use with non-ASCII scripts.</li>
   *     <li>No support for pseudo-zones like "GMT-07:00".</li>
   * </ul>
   *
   * @deprecated Use {@link java.util.GregorianCalendar} instead.
   */
  @Deprecated
  public class Time {
      private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
      private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
      private static final String Y_M_D = "%Y-%m-%d";
  
      public static final String TIMEZONE_UTC = "UTC";
  
      /**
       * The Julian day of the epoch, that is, January 1, 1970 on the Gregorian
       * calendar.
       */
      public static final int EPOCH_JULIAN_DAY = 2440588;
  
      /**
       * The Julian day of the Monday in the week of the epoch, December 29, 1969
       * on the Gregorian calendar.
       */
      public static final int MONDAY_BEFORE_JULIAN_EPOCH = EPOCH_JULIAN_DAY - 3;
  
      /**
       * True if this is an allDay event. The hour, minute, second fields are
       * all zero, and the date is displayed the same in all time zones.
       */
      public boolean allDay;
  
      /**
       * Seconds [0-61] (2 leap seconds allowed)
       */
      public int second;
  
      /**
       * Minute [0-59]
       */
      public int minute;
  
      /**
       * Hour of day [0-23]
       */
      public int hour;
  
      /**
       * Day of month [1-31]
       */
      public int monthDay;
  
      /**
       * Month [0-11]
       */
      public int month;
  
      /**
       * Year. For example, 1970.
       */
      public int year;
  
      /**
       * Day of week [0-6]
       */
      public int weekDay;
  
      /**
       * Day of year [0-365]
       */
      public int yearDay;
  
      /**
       * This time is in daylight savings time. One of:
       * <ul>
       * <li><b>positive</b> - in dst</li>
       * <li><b>0</b> - not in dst</li>
       * <li><b>negative</b> - unknown</li>
       * </ul>
       */
      public int isDst;
  
      /**
       * Offset in seconds from UTC including any DST offset.
       */
      public long gmtoff;
  
      /**
       * The timezone for this Time.  Should not be null.
       */
      public String timezone;
  
      /*
       * Define symbolic constants for accessing the fields in this class. Used in
       * getActualMaximum().
       */
      public static final int SECOND = 1;
      public static final int MINUTE = 2;
      public static final int HOUR = 3;
      public static final int MONTH_DAY = 4;
      public static final int MONTH = 5;
      public static final int YEAR = 6;
      public static final int WEEK_DAY = 7;
      public static final int YEAR_DAY = 8;
      public static final int WEEK_NUM = 9;
  
      public static final int SUNDAY = 0;
      public static final int MONDAY = 1;
      public static final int TUESDAY = 2;
      public static final int WEDNESDAY = 3;
      public static final int THURSDAY = 4;
      public static final int FRIDAY = 5;
      public static final int SATURDAY = 6;
  
      // An object that is reused for date calculations.
      private TimeCalculator calculator;
  
      /**
       * Construct a Time object in the timezone named by the string
       * argument "timezone". The time is initialized to Jan 1, 1970.
       * @param timezoneId string containing the timezone to use.
       * @see TimeZone
       */
      public Time(String timezoneId) {
          if (timezoneId == null) {
              throw new NullPointerException("timezoneId is null!");
          }
          initialize(timezoneId);
      }
  
      /**
       * Construct a Time object in the default timezone. The time is initialized to
       * Jan 1, 1970.
       */
      public Time() {
          initialize(TimeZone.getDefault().getID());
      }
  
      /**
       * A copy constructor.  Construct a Time object by copying the given
       * Time object.  No normalization occurs.
       *
       * @param other
       */
      public Time(Time other) {
          initialize(other.timezone);
          set(other);
      }
  
      /** Initialize the Time to 00:00:00 1/1/1970 in the specified timezone. */
      private void initialize(String timezoneId) {
          this.timezone = timezoneId;
          this.year = 1970;
          this.monthDay = 1;
          // Set the daylight-saving indicator to the unknown value -1 so that
          // it will be recomputed.
          this.isDst = -1;
  
          // A reusable object that performs the date/time calculations.
          calculator = new TimeCalculator(timezoneId);
      }
  
      /**
       * Ensures the values in each field are in range. For example if the
       * current value of this calendar is March 32, normalize() will convert it
       * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff.
       *
       * <p>
       * If "ignoreDst" is true, then this method sets the "isDst" field to -1
       * (the "unknown" value) before normalizing.  It then computes the
       * time in milliseconds and sets the correct value for "isDst" if the
       * fields resolve to a valid date / time.
       *
       * <p>
       * See {@link #toMillis(boolean)} for more information about when to
       * use <tt>true</tt> or <tt>false</tt> for "ignoreDst" and when {@code -1}
       * might be returned.
       *
       * @return the UTC milliseconds since the epoch, or {@code -1}
       */
      public long normalize(boolean ignoreDst) {
          calculator.copyFieldsFromTime(this);
          long timeInMillis = calculator.toMillis(ignoreDst);
          calculator.copyFieldsToTime(this);
          return timeInMillis;
      }
  
      /**
       * Convert this time object so the time represented remains the same, but is
       * instead located in a different timezone. This method automatically calls
       * normalize() in some cases.
       *
       * <p>This method can return incorrect results if the date / time cannot be normalized.
       */
      public void switchTimezone(String timezone) {
          calculator.copyFieldsFromTime(this);
          calculator.switchTimeZone(timezone);
          calculator.copyFieldsToTime(this);
          this.timezone = timezone;
      }
  
      private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31,
              31, 30, 31, 30, 31 };
  
      /**
       * Return the maximum possible value for the given field given the value of
       * the other fields. Requires that it be normalized for MONTH_DAY and
       * YEAR_DAY.
       * @param field one of the constants for HOUR, MINUTE, SECOND, etc.
       * @return the maximum value for the field.
       */
      public int getActualMaximum(int field) {
          switch (field) {
          case SECOND:
              return 59; // leap seconds, bah humbug
          case MINUTE:
              return 59;
          case HOUR:
              return 23;
          case MONTH_DAY: {
              int n = DAYS_PER_MONTH[this.month];
              if (n != 28) {
                  return n;
              } else {
                  int y = this.year;
                  return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29 : 28;
              }
          }
          case MONTH:
              return 11;
          case YEAR:
              return 2037;
          case WEEK_DAY:
              return 6;
          case YEAR_DAY: {
              int y = this.year;
              // Year days are numbered from 0, so the last one is usually 364.
              return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 365 : 364;
          }
          case WEEK_NUM:
              throw new RuntimeException("WEEK_NUM not implemented");
          default:
              throw new RuntimeException("bad field=" + field);
          }
      }
  
      /**
       * Clears all values, setting the timezone to the given timezone. Sets isDst
       * to a negative value to mean "unknown".
       * @param timezoneId the timezone to use.
       */
      public void clear(String timezoneId) {
          if (timezoneId == null) {
              throw new NullPointerException("timezone is null!");
          }
          this.timezone = timezoneId;
          this.allDay = false;
          this.second = 0;
          this.minute = 0;
          this.hour = 0;
          this.monthDay = 0;
          this.month = 0;
          this.year = 0;
          this.weekDay = 0;
          this.yearDay = 0;
          this.gmtoff = 0;
          this.isDst = -1;
      }
  
      /**
       * Compare two {@code Time} objects and return a negative number if {@code
       * a} is less than {@code b}, a positive number if {@code a} is greater than
       * {@code b}, or 0 if they are equal.
       *
       * <p>
       * This method can return an incorrect answer when the date / time fields of
       * either {@code Time} have been set to a local time that contradicts the
       * available timezone information.
       *
       * @param a first {@code Time} instance to compare
       * @param b second {@code Time} instance to compare
       * @throws NullPointerException if either argument is {@code null}
       * @throws IllegalArgumentException if {@link #allDay} is true but {@code
       *             hour}, {@code minute}, and {@code second} are not 0.
       * @return a negative result if {@code a} is earlier, a positive result if
       *         {@code b} is earlier, or 0 if they are equal.
       */
      public static int compare(Time a, Time b) {
          if (a == null) {
              throw new NullPointerException("a == null");
          } else if (b == null) {
              throw new NullPointerException("b == null");
          }
          a.calculator.copyFieldsFromTime(a);
          b.calculator.copyFieldsFromTime(b);
  
          return TimeCalculator.compare(a.calculator, b.calculator);
      }
  
      /**
       * Print the current value given the format string provided. See man
       * strftime for what means what. The final string must be less than 256
       * characters.
       * @param format a string containing the desired format.
       * @return a String containing the current time expressed in the current locale.
       */
      public String format(String format) {
          calculator.copyFieldsFromTime(this);
          return calculator.format(format);
      }
  
      /**
       * Return the current time in YYYYMMDDTHHMMSS&lt;tz&gt; format
       */
      @Override
      public String toString() {
          // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff
          // happens during debugging when the debugger calls toString().
          TimeCalculator calculator = new TimeCalculator(this.timezone);
          calculator.copyFieldsFromTime(this);
          return calculator.toStringInternal();
      }
  
      /**
       * Parses a date-time string in either the RFC 2445 format or an abbreviated
       * format that does not include the "time" field.  For example, all of the
       * following strings are valid:
       *
       * <ul>
       *   <li>"20081013T160000Z"</li>
       *   <li>"20081013T160000"</li>
       *   <li>"20081013"</li>
       * </ul>
       *
       * Returns whether or not the time is in UTC (ends with Z).  If the string
       * ends with "Z" then the timezone is set to UTC.  If the date-time string
       * included only a date and no time field, then the <code>allDay</code>
       * field of this Time class is set to true and the <code>hour</code>,
       * <code>minute</code>, and <code>second</code> fields are set to zero;
       * otherwise (a time field was included in the date-time string)
       * <code>allDay</code> is set to false. The fields <code>weekDay</code>,
       * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero,
       * and the field <code>isDst</code> is set to -1 (unknown).  To set those
       * fields, call {@link #normalize(boolean)} after parsing.
       *
       * To parse a date-time string and convert it to UTC milliseconds, do
       * something like this:
       *
       * <pre>
       *   Time time = new Time();
       *   String date = "20081013T160000Z";
       *   time.parse(date);
       *   long millis = time.normalize(false);
       * </pre>
       *
       * @param s the string to parse
       * @return true if the resulting time value is in UTC time
       * @throws android.util.TimeFormatException if s cannot be parsed.
       */
      public boolean parse(String s) {
          if (s == null) {
              throw new NullPointerException("time string is null");
          }
          if (parseInternal(s)) {
              timezone = TIMEZONE_UTC;
              return true;
          }
          return false;
      }
  
      /**
       * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
       */
      private boolean parseInternal(String s) {
          int len = s.length();
          if (len < 8) {
              throw new TimeFormatException("String is too short: \"" + s +
                      "\" Expected at least 8 characters.");
          }
  
          boolean inUtc = false;
  
          // year
          int n = getChar(s, 0, 1000);
          n += getChar(s, 1, 100);
          n += getChar(s, 2, 10);
          n += getChar(s, 3, 1);
          year = n;
  
          // month
          n = getChar(s, 4, 10);
          n += getChar(s, 5, 1);
          n--;
          month = n;
  
          // day of month
          n = getChar(s, 6, 10);
          n += getChar(s, 7, 1);
          monthDay = n;
  
          if (len > 8) {
              if (len < 15) {
                  throw new TimeFormatException(
                          "String is too short: \"" + s
                                  + "\" If there are more than 8 characters there must be at least"
                                  + " 15.");
              }
              checkChar(s, 8, 'T');
              allDay = false;
  
              // hour
              n = getChar(s, 9, 10);
              n += getChar(s, 10, 1);
              hour = n;
  
              // min
              n = getChar(s, 11, 10);
              n += getChar(s, 12, 1);
              minute = n;
  
              // sec
              n = getChar(s, 13, 10);
              n += getChar(s, 14, 1);
              second = n;
  
              if (len > 15) {
                  // Z
                  checkChar(s, 15, 'Z');
                  inUtc = true;
              }
          } else {
              allDay = true;
              hour = 0;
              minute = 0;
              second = 0;
          }
  
          weekDay = 0;
          yearDay = 0;
          isDst = -1;
          gmtoff = 0;
          return inUtc;
      }
  
      private void checkChar(String s, int spos, char expected) {
          char c = s.charAt(spos);
          if (c != expected) {
              throw new TimeFormatException(String.format(
                      "Unexpected character 0x%02d at pos=%d.  Expected 0x%02d (\'%c\').",
                      (int) c, spos, (int) expected, expected));
          }
      }
  
      private static int getChar(String s, int spos, int mul) {
          char c = s.charAt(spos);
          if (Character.isDigit(c)) {
              return Character.getNumericValue(c) * mul;
          } else {
              throw new TimeFormatException("Parse error at pos=" + spos);
          }
      }
  
      /**
       * Parse a time in RFC 3339 format.  This method also parses simple dates
       * (that is, strings that contain no time or time offset).  For example,
       * all of the following strings are valid:
       *
       * <ul>
       *   <li>"2008-10-13T16:00:00.000Z"</li>
       *   <li>"2008-10-13T16:00:00.000+07:00"</li>
       *   <li>"2008-10-13T16:00:00.000-07:00"</li>
       *   <li>"2008-10-13"</li>
       * </ul>
       *
       * <p>
       * If the string contains a time and time offset, then the time offset will
       * be used to convert the time value to UTC.
       * </p>
       *
       * <p>
       * If the given string contains just a date (with no time field), then
       * the {@link #allDay} field is set to true and the {@link #hour},
       * {@link #minute}, and  {@link #second} fields are set to zero.
       * </p>
       *
       * <p>
       * Returns true if the resulting time value is in UTC time.
       * </p>
       *
       * @param s the string to parse
       * @return true if the resulting time value is in UTC time
       * @throws android.util.TimeFormatException if s cannot be parsed.
       */
       public boolean parse3339(String s) {
           if (s == null) {
               throw new NullPointerException("time string is null");
           }
           if (parse3339Internal(s)) {
               timezone = TIMEZONE_UTC;
               return true;
           }
           return false;
       }
  
       private boolean parse3339Internal(String s) {
           int len = s.length();
           if (len < 10) {
               throw new TimeFormatException("String too short --- expected at least 10 characters.");
           }
           boolean inUtc = false;
  
           // year
           int n = getChar(s, 0, 1000);
           n += getChar(s, 1, 100);
           n += getChar(s, 2, 10);
           n += getChar(s, 3, 1);
           year = n;
  
           checkChar(s, 4, '-');
  
           // month
           n = getChar(s, 5, 10);
           n += getChar(s, 6, 1);
           --n;
           month = n;
  
           checkChar(s, 7, '-');
  
           // day
           n = getChar(s, 8, 10);
           n += getChar(s, 9, 1);
           monthDay = n;
  
           if (len >= 19) {
               // T
               checkChar(s, 10, 'T');
               allDay = false;
  
               // hour
               n = getChar(s, 11, 10);
               n += getChar(s, 12, 1);
  
               // Note that this.hour is not set here. It is set later.
               int hour = n;
  
               checkChar(s, 13, ':');
  
               // minute
               n = getChar(s, 14, 10);
               n += getChar(s, 15, 1);
               // Note that this.minute is not set here. It is set later.
               int minute = n;
  
               checkChar(s, 16, ':');
  
               // second
               n = getChar(s, 17, 10);
               n += getChar(s, 18, 1);
               second = n;
  
               // skip the '.XYZ' -- we don't care about subsecond precision.
  
               int tzIndex = 19;
               if (tzIndex < len && s.charAt(tzIndex) == '.') {
                   do {
                       tzIndex++;
                   } while (tzIndex < len && Character.isDigit(s.charAt(tzIndex)));
               }
  
               int offset = 0;
               if (len > tzIndex) {
                   char c = s.charAt(tzIndex);
                   // NOTE: the offset is meant to be subtracted to get from local time
                   // to UTC.  we therefore use 1 for '-' and -1 for '+'.
                   switch (c) {
                       case 'Z':
                           // Zulu time -- UTC
                           offset = 0;
                           break;
                       case '-':
                           offset = 1;
                           break;
                       case '+':
                           offset = -1;
                           break;
                       default:
                           throw new TimeFormatException(String.format(
                                   "Unexpected character 0x%02d at position %d.  Expected + or -",
                                   (int) c, tzIndex));
                   }
                   inUtc = true;
  
                   if (offset != 0) {
                       if (len < tzIndex + 6) {
                           throw new TimeFormatException(
                                   String.format("Unexpected length; should be %d characters",
                                           tzIndex + 6));
                       }
  
                       // hour
                       n = getChar(s, tzIndex + 1, 10);
                       n += getChar(s, tzIndex + 2, 1);
                       n *= offset;
                       hour += n;
  
                       // minute
                       n = getChar(s, tzIndex + 4, 10);
                       n += getChar(s, tzIndex + 5, 1);
                       n *= offset;
                       minute += n;
                   }
               }
               this.hour = hour;
               this.minute = minute;
  
               if (offset != 0) {
                   normalize(false);
               }
           } else {
               allDay = true;
               this.hour = 0;
               this.minute = 0;
               this.second = 0;
           }
  
           this.weekDay = 0;
           this.yearDay = 0;
           this.isDst = -1;
           this.gmtoff = 0;
           return inUtc;
       }
  
      /**
       * Returns the timezone string that is currently set for the device.
       */
      public static String getCurrentTimezone() {
          return TimeZone.getDefault().getID();
      }
  
      /**
       * Sets the time of the given Time object to the current time.
       */
      public void setToNow() {
          set(System.currentTimeMillis());
      }
  
      /**
       * Converts this time to milliseconds. Suitable for interacting with the
       * standard java libraries. The time is in UTC milliseconds since the epoch.
       * This does an implicit normalization to compute the milliseconds but does
       * <em>not</em> change any of the fields in this Time object.  If you want
       * to normalize the fields in this Time object and also get the milliseconds
       * then use {@link #normalize(boolean)}.
       *
       * <p>
       * If "ignoreDst" is false, then this method uses the current setting of the
       * "isDst" field and will adjust the returned time if the "isDst" field is
       * wrong for the given time.  See the sample code below for an example of
       * this.
       *
       * <p>
       * If "ignoreDst" is true, then this method ignores the current setting of
       * the "isDst" field in this Time object and will instead figure out the
       * correct value of "isDst" (as best it can) from the fields in this
       * Time object.  The only case where this method cannot figure out the
       * correct value of the "isDst" field is when the time is inherently
       * ambiguous because it falls in the hour that is repeated when switching
       * from Daylight-Saving Time to Standard Time.
       *
       * <p>
       * Here is an example where <tt>toMillis(true)</tt> adjusts the time,
       * assuming that DST changes at 2am on Sunday, Nov 4, 2007.
       *
       * <pre>
       * Time time = new Time();
       * time.set(4, 10, 2007);  // set the date to Nov 4, 2007, 12am
       * time.normalize(false);       // this sets isDst = 1
       * time.monthDay += 1;     // changes the date to Nov 5, 2007, 12am
       * millis = time.toMillis(false);   // millis is Nov 4, 2007, 11pm
       * millis = time.toMillis(true);    // millis is Nov 5, 2007, 12am
       * </pre>
       *
       * <p>
       * To avoid this problem, use <tt>toMillis(true)</tt>
       * after adding or subtracting days or explicitly setting the "monthDay"
       * field.  On the other hand, if you are adding
       * or subtracting hours or minutes, then you should use
       * <tt>toMillis(false)</tt>.
       *
       * <p>
       * You should also use <tt>toMillis(false)</tt> if you want
       * to read back the same milliseconds that you set with {@link #set(long)}
       * or {@link #set(Time)} or after parsing a date string.
       *
       * <p>
       * This method can return {@code -1} when the date / time fields have been
       * set to a local time that conflicts with available timezone information.
       * For example, when daylight savings transitions cause an hour to be
       * skipped: times within that hour will return {@code -1} if isDst =
       * {@code -1}.
       */
      public long toMillis(boolean ignoreDst) {
          calculator.copyFieldsFromTime(this);
          return calculator.toMillis(ignoreDst);
      }
  
      /**
       * Sets the fields in this Time object given the UTC milliseconds.  After
       * this method returns, all the fields are normalized.
       * This also sets the "isDst" field to the correct value.
       *
       * @param millis the time in UTC milliseconds since the epoch.
       */
      public void set(long millis) {
          allDay = false;
          calculator.timezone = timezone;
          calculator.setTimeInMillis(millis);
          calculator.copyFieldsToTime(this);
      }
  
      /**
       * Format according to RFC 2445 DATE-TIME type.
       *
       * <p>The same as format("%Y%m%dT%H%M%S"), or format("%Y%m%dT%H%M%SZ") for a Time with a
       * timezone set to "UTC".
       */
      public String format2445() {
          calculator.copyFieldsFromTime(this);
          return calculator.format2445(!allDay);
      }
  
      /**
       * Copy the value of that to this Time object. No normalization happens.
       */
      public void set(Time that) {
          this.timezone = that.timezone;
          this.allDay = that.allDay;
          this.second = that.second;
          this.minute = that.minute;
          this.hour = that.hour;
          this.monthDay = that.monthDay;
          this.month = that.month;
          this.year = that.year;
          this.weekDay = that.weekDay;
          this.yearDay = that.yearDay;
          this.isDst = that.isDst;
          this.gmtoff = that.gmtoff;
      }
  
      /**
       * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
       * Call {@link #normalize(boolean)} if you need those.
       */
      public void set(int second, int minute, int hour, int monthDay, int month, int year) {
          this.allDay = false;
          this.second = second;
          this.minute = minute;
          this.hour = hour;
          this.monthDay = monthDay;
          this.month = month;
          this.year = year;
          this.weekDay = 0;
          this.yearDay = 0;
          this.isDst = -1;
          this.gmtoff = 0;
      }
  
      /**
       * Sets the date from the given fields.  Also sets allDay to true.
       * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
       * Call {@link #normalize(boolean)} if you need those.
       *
       * @param monthDay the day of the month (in the range [1,31])
       * @param month the zero-based month number (in the range [0,11])
       * @param year the year
       */
      public void set(int monthDay, int month, int year) {
          this.allDay = true;
          this.second = 0;
          this.minute = 0;
          this.hour = 0;
          this.monthDay = monthDay;
          this.month = month;
          this.year = year;
          this.weekDay = 0;
          this.yearDay = 0;
          this.isDst = -1;
          this.gmtoff = 0;
      }
  
      /**
       * Returns true if the time represented by this Time object occurs before
       * the given time.
       *
       * <p>
       * Equivalent to {@code Time.compare(this, that) < 0}. See
       * {@link #compare(Time, Time)} for details.
       *
       * @param that a given Time object to compare against
       * @return true if this time is less than the given time
       */
      public boolean before(Time that) {
          return Time.compare(this, that) < 0;
      }
  
  
      /**
       * Returns true if the time represented by this Time object occurs after
       * the given time.
       *
       * <p>
       * Equivalent to {@code Time.compare(this, that) > 0}. See
       * {@link #compare(Time, Time)} for details.
       *
       * @param that a given Time object to compare against
       * @return true if this time is greater than the given time
       */
      public boolean after(Time that) {
          return Time.compare(this, that) > 0;
      }
  
      /**
       * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.)
       * and gives a number that can be added to the yearDay to give the
       * closest Thursday yearDay.
       */
      private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 };
  
      /**
       * Computes the week number according to ISO 8601.  The current Time
       * object must already be normalized because this method uses the
       * yearDay and weekDay fields.
       *
       * <p>
       * In IS0 8601, weeks start on Monday.
       * The first week of the year (week 1) is defined by ISO 8601 as the
       * first week with four or more of its days in the starting year.
       * Or equivalently, the week containing January 4.  Or equivalently,
       * the week with the year's first Thursday in it.
       * </p>
       *
       * <p>
       * The week number can be calculated by counting Thursdays.  Week N
       * contains the Nth Thursday of the year.
       * </p>
       *
       * @return the ISO week number.
       */
      public int getWeekNumber() {
          // Get the year day for the closest Thursday
          int closestThursday = yearDay + sThursdayOffset[weekDay];
  
          // Year days start at 0
          if (closestThursday >= 0 && closestThursday <= 364) {
              return closestThursday / 7 + 1;
          }
  
          // The week crosses a year boundary.
          Time temp = new Time(this);
          temp.monthDay += sThursdayOffset[weekDay];
          temp.normalize(true /* ignore isDst */);
          return temp.yearDay / 7 + 1;
      }
  
      /**
       * Return a string in the RFC 3339 format.
       * <p>
       * If allDay is true, expresses the time as Y-M-D</p>
       * <p>
       * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p>
       * <p>
       * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p>
       * @return string in the RFC 3339 format.
       */
      public String format3339(boolean allDay) {
          if (allDay) {
              return format(Y_M_D);
          } else if (TIMEZONE_UTC.equals(timezone)) {
              return format(Y_M_D_T_H_M_S_000_Z);
          } else {
              String base = format(Y_M_D_T_H_M_S_000);
              String sign = (gmtoff < 0) ? "-" : "+";
              int offset = (int) Math.abs(gmtoff);
              int minutes = (offset % 3600) / 60;
              int hours = offset / 3600;
  
              return String.format(Locale.US, "%s%s%02d:%02d", base, sign, hours, minutes);
          }
      }
  
      /**
       * Returns true if the day of the given time is the epoch on the Julian Calendar
       * (January 1, 1970 on the Gregorian calendar).
       *
       * <p>
       * This method can return an incorrect answer when the date / time fields have
       * been set to a local time that contradicts the available timezone information.
       *
       * @param time the time to test
       * @return true if epoch.
       */
      public static boolean isEpoch(Time time) {
          long millis = time.toMillis(true);
          return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY;
      }
  
      /**
       * Computes the Julian day number for a point in time in a particular
       * timezone. The Julian day for a given date is the same for every
       * timezone. For example, the Julian day for July 1, 2008 is 2454649.
       *
       * <p>Callers must pass the time in UTC millisecond (as can be returned
       * by {@link #toMillis(boolean)} or {@link #normalize(boolean)})
       * and the offset from UTC of the timezone in seconds (as might be in
       * {@link #gmtoff}).
       *
       * <p>The Julian day is useful for testing if two events occur on the
       * same calendar date and for determining the relative time of an event
       * from the present ("yesterday", "3 days ago", etc.).
       *
       * @param millis the time in UTC milliseconds
       * @param gmtoff the offset from UTC in seconds
       * @return the Julian day
       */
      public static int getJulianDay(long millis, long gmtoff) {
          long offsetMillis = gmtoff * 1000;
          long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS;
          return (int) julianDay + EPOCH_JULIAN_DAY;
      }
  
      /**
       * <p>Sets the time from the given Julian day number, which must be based on
       * the same timezone that is set in this Time object.  The "gmtoff" field
       * need not be initialized because the given Julian day may have a different
       * GMT offset than whatever is currently stored in this Time object anyway.
       * After this method returns all the fields will be normalized and the time
       * will be set to 12am at the beginning of the given Julian day.
       * </p>
       *
       * <p>
       * The only exception to this is if 12am does not exist for that day because
       * of daylight saving time.  For example, Cairo, Eqypt moves time ahead one
       * hour at 12am on April 25, 2008 and there are a few other places that
       * also change daylight saving time at 12am.  In those cases, the time
       * will be set to 1am.
       * </p>
       *
       * @param julianDay the Julian day in the timezone for this Time object
       * @return the UTC milliseconds for the beginning of the Julian day
       */
      public long setJulianDay(int julianDay) {
          // Don't bother with the GMT offset since we don't know the correct
          // value for the given Julian day.  Just get close and then adjust
          // the day.
          long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
          set(millis);
  
          // Figure out how close we are to the requested Julian day.
          // We can't be off by more than a day.
          int approximateDay = getJulianDay(millis, gmtoff);
          int diff = julianDay - approximateDay;
          monthDay += diff;
  
          // Set the time to 12am and re-normalize.
          hour = 0;
          minute = 0;
          second = 0;
          millis = normalize(true);
          return millis;
      }
  
      /**
       * Returns the week since {@link #EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted
       * for first day of week. This takes a julian day and the week start day and
       * calculates which week since {@link #EPOCH_JULIAN_DAY} that day occurs in,
       * starting at 0. *Do not* use this to compute the ISO week number for the
       * year.
       *
       * @param julianDay The julian day to calculate the week number for
       * @param firstDayOfWeek Which week day is the first day of the week, see
       *            {@link #SUNDAY}
       * @return Weeks since the epoch
       */
      public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
          int diff = THURSDAY - firstDayOfWeek;
          if (diff < 0) {
              diff += 7;
          }
          int refDay = EPOCH_JULIAN_DAY - diff;
          return (julianDay - refDay) / 7;
      }
  
      /**
       * Takes a number of weeks since the epoch and calculates the Julian day of
       * the Monday for that week. This assumes that the week containing the
       * {@link #EPOCH_JULIAN_DAY} is considered week 0. It returns the Julian day
       * for the Monday week weeks after the Monday of the week containing the
       * epoch.
       *
       * @param week Number of weeks since the epoch
       * @return The julian day for the Monday of the given week since the epoch
       */
      public static int getJulianMondayFromWeeksSinceEpoch(int week) {
          return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
      }
  
      /**
       * A class that handles date/time calculations.
       *
       * This class originated as a port of a native C++ class ("android.Time") to pure Java. It is
       * separate from the enclosing class because some methods copy the result of calculations back
       * to the enclosing object, but others do not: thus separate state is retained.
       */
      private static class TimeCalculator {
          public final ZoneInfo.WallTime wallTime;
          public String timezone;
  
          // Information about the current timezone.
          private ZoneInfo zoneInfo;
  
          public TimeCalculator(String timezoneId) {
              this.zoneInfo = lookupZoneInfo(timezoneId);
              this.wallTime = new ZoneInfo.WallTime();
          }
  
          public long toMillis(boolean ignoreDst) {
              if (ignoreDst) {
                  wallTime.setIsDst(-1);
              }
  
              int r = wallTime.mktime(zoneInfo);
              if (r == -1) {
                  return -1;
              }
              return r * 1000L;
          }
  
          public void setTimeInMillis(long millis) {
              // Preserve old 32-bit Android behavior.
              int intSeconds = (int) (millis / 1000);
  
              updateZoneInfoFromTimeZone();
              wallTime.localtime(intSeconds, zoneInfo);
          }
  
          public String format(String format) {
              if (format == null) {
                  format = "%c";
              }
              TimeFormatter formatter = new TimeFormatter();
              return formatter.format(format, wallTime, zoneInfo);
          }
  
          private void updateZoneInfoFromTimeZone() {
              if (!zoneInfo.getID().equals(timezone)) {
                  this.zoneInfo = lookupZoneInfo(timezone);
              }
          }
  
          private static ZoneInfo lookupZoneInfo(String timezoneId) {
              try {
                  ZoneInfo zoneInfo = ZoneInfoDB.getInstance().makeTimeZone(timezoneId);
                  if (zoneInfo == null) {
                      zoneInfo = ZoneInfoDB.getInstance().makeTimeZone("GMT");
                  }
                  if (zoneInfo == null) {
                      throw new AssertionError("GMT not found: \"" + timezoneId + "\"");
                  }
                  return zoneInfo;
              } catch (IOException e) {
                  // This should not ever be thrown.
                  throw new AssertionError("Error loading timezone: \"" + timezoneId + "\"", e);
              }
          }
  
          public void switchTimeZone(String timezone) {
              int seconds = wallTime.mktime(zoneInfo);
              this.timezone = timezone;
              updateZoneInfoFromTimeZone();
              wallTime.localtime(seconds, zoneInfo);
          }
  
          public String format2445(boolean hasTime) {
              char[] buf = new char[hasTime ? 16 : 8];
              int n = wallTime.getYear();
  
              buf[0] = toChar(n / 1000);
              n %= 1000;
              buf[1] = toChar(n / 100);
              n %= 100;
              buf[2] = toChar(n / 10);
              n %= 10;
              buf[3] = toChar(n);
  
              n = wallTime.getMonth() + 1;
              buf[4] = toChar(n / 10);
              buf[5] = toChar(n % 10);
  
              n = wallTime.getMonthDay();
              buf[6] = toChar(n / 10);
              buf[7] = toChar(n % 10);
  
              if (!hasTime) {
                  return new String(buf, 0, 8);
              }
  
              buf[8] = 'T';
  
              n = wallTime.getHour();
              buf[9] = toChar(n / 10);
              buf[10] = toChar(n % 10);
  
              n = wallTime.getMinute();
              buf[11] = toChar(n / 10);
              buf[12] = toChar(n % 10);
  
              n = wallTime.getSecond();
              buf[13] = toChar(n / 10);
              buf[14] = toChar(n % 10);
  
              if (TIMEZONE_UTC.equals(timezone)) {
                  // The letter 'Z' is appended to the end.
                  buf[15] = 'Z';
                  return new String(buf, 0, 16);
              } else {
                  return new String(buf, 0, 15);
              }
          }
  
          private char toChar(int n) {
              return (n >= 0 && n <= 9) ? (char) (n + '0') : ' ';
          }
  
          /**
           * A method that will return the state of this object in string form. Note: it has side
           * effects and so has deliberately not been made the default {@link #toString()}.
           */
          public String toStringInternal() {
              // This implementation possibly displays the un-normalized fields because that is
              // what it has always done.
              return String.format("%04d%02d%02dT%02d%02d%02d%s(%d,%d,%d,%d,%d)",
                      wallTime.getYear(),
                      wallTime.getMonth() + 1,
                      wallTime.getMonthDay(),
                      wallTime.getHour(),
                      wallTime.getMinute(),
                      wallTime.getSecond(),
                      timezone,
                      wallTime.getWeekDay(),
                      wallTime.getYearDay(),
                      wallTime.getGmtOffset(),
                      wallTime.getIsDst(),
                      toMillis(false /* use isDst */) / 1000
              );
  
          }
  
          public static int compare(TimeCalculator aObject, TimeCalculator bObject) {
              if (aObject.timezone.equals(bObject.timezone)) {
                  // If the timezones are the same, we can easily compare the two times.
                  int diff = aObject.wallTime.getYear() - bObject.wallTime.getYear();
                  if (diff != 0) {
                      return diff;
                  }
  
                  diff = aObject.wallTime.getMonth() - bObject.wallTime.getMonth();
                  if (diff != 0) {
                      return diff;
                  }
  
                  diff = aObject.wallTime.getMonthDay() - bObject.wallTime.getMonthDay();
                  if (diff != 0) {
                      return diff;
                  }
  
                  diff = aObject.wallTime.getHour() - bObject.wallTime.getHour();
                  if (diff != 0) {
                      return diff;
                  }
  
                  diff = aObject.wallTime.getMinute() - bObject.wallTime.getMinute();
                  if (diff != 0) {
                      return diff;
                  }
  
                  diff = aObject.wallTime.getSecond() - bObject.wallTime.getSecond();
                  if (diff != 0) {
                      return diff;
                  }
  
                  return 0;
              } else {
                  // Otherwise, convert to milliseconds and compare that. This requires that object be
                  // normalized. Note: For dates that do not exist: toMillis() can return -1, which
                  // can be confused with a valid time.
                  long am = aObject.toMillis(false /* use isDst */);
                  long bm = bObject.toMillis(false /* use isDst */);
                  long diff = am - bm;
                  return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
              }
  
          }
  
          public void copyFieldsToTime(Time time) {
              time.second = wallTime.getSecond();
              time.minute = wallTime.getMinute();
              time.hour = wallTime.getHour();
              time.monthDay = wallTime.getMonthDay();
              time.month = wallTime.getMonth();
              time.year = wallTime.getYear();
  
              // Read-only fields that are derived from other information above.
              time.weekDay = wallTime.getWeekDay();
              time.yearDay = wallTime.getYearDay();
  
              // < 0: DST status unknown, 0: is not in DST, 1: is in DST
              time.isDst = wallTime.getIsDst();
              // This is in seconds and includes any DST offset too.
              time.gmtoff = wallTime.getGmtOffset();
          }
  
          public void copyFieldsFromTime(Time time) {
              wallTime.setSecond(time.second);
              wallTime.setMinute(time.minute);
              wallTime.setHour(time.hour);
              wallTime.setMonthDay(time.monthDay);
              wallTime.setMonth(time.month);
              wallTime.setYear(time.year);
              wallTime.setWeekDay(time.weekDay);
              wallTime.setYearDay(time.yearDay);
              wallTime.setIsDst(time.isDst);
              wallTime.setGmtOffset((int) time.gmtoff);
  
              if (time.allDay && (time.second != 0 || time.minute != 0 || time.hour != 0)) {
                  throw new IllegalArgumentException("allDay is true but sec, min, hour are not 0.");
              }
  
              timezone = time.timezone;
              updateZoneInfoFromTimeZone();
          }
      }
  }
  