1297 lines
44 KiB
Java
1297 lines
44 KiB
Java
/*
|
|
* 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 com.android.i18n.timezone.WallTime;
|
|
import com.android.i18n.timezone.ZoneInfoData;
|
|
import com.android.i18n.timezone.ZoneInfoDb;
|
|
|
|
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
|
|
* strftime(3) manual page 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<tz> format
|
|
*/
|
|
@Override
|
|
public String toString() {
|
|
// toString() uses its own TimeCalculator rather than the shared one. Otherwise weird 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 instant of the supplied time would be for the
|
|
* Gregorian calendar date January 1, 1970 <em>for a user observing UTC
|
|
* </em>, i.e. the timezone of the time object is ignored.
|
|
* <p>
|
|
* See {@link #getJulianDay(long, long)} for how to determine the Julian day
|
|
* for the timezone of the time object.
|
|
* <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 /* UTC offset */) == EPOCH_JULIAN_DAY;
|
|
}
|
|
|
|
/**
|
|
* Computes the Julian day number for a point in time in a particular
|
|
* timezone. The Julian day for a given calendar 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 at that time (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 gmtoffSeconds the offset from UTC in seconds
|
|
* @return the Julian day
|
|
* @deprecated Use {@link java.time.temporal.JulianFields#JULIAN_DAY} instead.
|
|
*/
|
|
@Deprecated
|
|
public static int getJulianDay(long millis, long gmtoffSeconds) {
|
|
long offsetMillis = gmtoffSeconds * 1000;
|
|
long adjustedMillis = millis + offsetMillis;
|
|
long julianDay = adjustedMillis / DateUtils.DAY_IN_MILLIS;
|
|
// Negative adjustedMillis values must round towards Integer.MIN_VALUE.
|
|
if (adjustedMillis < 0 && adjustedMillis % DateUtils.DAY_IN_MILLIS != 0) {
|
|
julianDay--;
|
|
}
|
|
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 WallTime wallTime;
|
|
public String timezone;
|
|
|
|
// Information about the current timezone.
|
|
private ZoneInfoData mZoneInfoData;
|
|
|
|
public TimeCalculator(String timezoneId) {
|
|
this.mZoneInfoData = lookupZoneInfoData(timezoneId);
|
|
this.wallTime = new WallTime();
|
|
}
|
|
|
|
public long toMillis(boolean ignoreDst) {
|
|
if (ignoreDst) {
|
|
wallTime.setIsDst(-1);
|
|
}
|
|
|
|
int r = wallTime.mktime(mZoneInfoData);
|
|
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, mZoneInfoData);
|
|
}
|
|
|
|
public String format(String format) {
|
|
if (format == null) {
|
|
format = "%c";
|
|
}
|
|
TimeFormatter formatter = new TimeFormatter();
|
|
return formatter.format(format, wallTime, mZoneInfoData);
|
|
}
|
|
|
|
private void updateZoneInfoFromTimeZone() {
|
|
if (!mZoneInfoData.getID().equals(timezone)) {
|
|
this.mZoneInfoData = lookupZoneInfoData(timezone);
|
|
}
|
|
}
|
|
|
|
private static ZoneInfoData lookupZoneInfoData(String timezoneId) {
|
|
ZoneInfoData zoneInfoData = ZoneInfoDb.getInstance().makeZoneInfoData(timezoneId);
|
|
if (zoneInfoData == null) {
|
|
zoneInfoData = ZoneInfoDb.getInstance().makeZoneInfoData("GMT");
|
|
}
|
|
if (zoneInfoData == null) {
|
|
throw new AssertionError("GMT not found: \"" + timezoneId + "\"");
|
|
}
|
|
return zoneInfoData;
|
|
}
|
|
|
|
public void switchTimeZone(String timezone) {
|
|
int seconds = wallTime.mktime(mZoneInfoData);
|
|
this.timezone = timezone;
|
|
updateZoneInfoFromTimeZone();
|
|
wallTime.localtime(seconds, mZoneInfoData);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|