/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import libcore.icu.LocaleData;
/**
* A specific moment in time, with millisecond precision. Values typically come
* from {@link System#currentTimeMillis}, and are always UTC, regardless of the
* system's time zone. This is often called "Unix time" or "epoch time".
*
* <p>Instances of this class are suitable for comparison, but little else.
* Use {@link java.text.DateFormat} to format a {@code Date} for display to a human.
* Use {@link Calendar} to break down a {@code Date} if you need to extract fields such
* as the current month or day of week, or to construct a {@code Date} from a broken-down
* time. That is: this class' deprecated display-related functionality is now provided
* by {@code DateFormat}, and this class' deprecated computational functionality is
* now provided by {@code Calendar}. Both of these other classes (and their subclasses)
* allow you to interpret a {@code Date} in a given time zone.
*
* <p>Note that, surprisingly, instances of this class are mutable.
*/
public class Date implements Serializable, Cloneable, Comparable<Date> {
private static final long serialVersionUID = 7523967970034938905L;
// Used by parse()
// Keep in a static inner class to allow compile-time initialization of Date.
private static class CreationYear {
private static final int VALUE = new Date().getYear();
}
private transient long milliseconds;
/**
* Initializes this {@code Date} instance to the current time.
*/
public Date() {
this(System.currentTimeMillis());
}
/**
* Constructs a new {@code Date} initialized to midnight in the default {@code TimeZone} on
* the specified date.
*
* @param year
* the year, 0 is 1900.
* @param month
* the month, 0 - 11.
* @param day
* the day of the month, 1 - 31.
*
* @deprecated Use {@link GregorianCalendar#GregorianCalendar(int, int, int)} instead.
*/
@Deprecated
public Date(int year, int month, int day) {
GregorianCalendar cal = new GregorianCalendar(false);
cal.set(1900 + year, month, day);
milliseconds = cal.getTimeInMillis();
}
/**
* Constructs a new {@code Date} initialized to the specified date and time in the
* default {@code TimeZone}.
*
* @param year
* the year, 0 is 1900.
* @param month
* the month, 0 - 11.
* @param day
* the day of the month, 1 - 31.
* @param hour
* the hour of day, 0 - 23.
* @param minute
* the minute of the hour, 0 - 59.
*
* @deprecated Use {@link GregorianCalendar#GregorianCalendar(int, int, int, int, int)} instead.
*/
@Deprecated
public Date(int year, int month, int day, int hour, int minute) {
GregorianCalendar cal = new GregorianCalendar(false);
cal.set(1900 + year, month, day, hour, minute);
milliseconds = cal.getTimeInMillis();
}
/**
* Constructs a new {@code Date} initialized to the specified date and time in the
* default {@code TimeZone}.
*
* @param year
* the year, 0 is 1900.
* @param month
* the month, 0 - 11.
* @param day
* the day of the month, 1 - 31.
* @param hour
* the hour of day, 0 - 23.
* @param minute
* the minute of the hour, 0 - 59.
* @param second
* the second of the minute, 0 - 59.
*
* @deprecated Use {@link GregorianCalendar#GregorianCalendar(int, int, int, int, int, int)}
* instead.
*/
@Deprecated
public Date(int year, int month, int day, int hour, int minute, int second) {
GregorianCalendar cal = new GregorianCalendar(false);
cal.set(1900 + year, month, day, hour, minute, second);
milliseconds = cal.getTimeInMillis();
}
/**
* Initializes this {@code Date} instance using the specified millisecond value. The
* value is the number of milliseconds since Jan. 1, 1970 GMT.
*
* @param milliseconds
* the number of milliseconds since Jan. 1, 1970 GMT.
*/
public Date(long milliseconds) {
this.milliseconds = milliseconds;
}
/**
* Constructs a new {@code Date} initialized to the date and time parsed from the
* specified String.
*
* @param string
* the String to parse.
*
* @deprecated Use {@link DateFormat} instead.
*/
@Deprecated
public Date(String string) {
milliseconds = parse(string);
}
/**
* Returns if this {@code Date} is after the specified Date.
*
* @param date
* a Date instance to compare.
* @return {@code true} if this {@code Date} is after the specified {@code Date},
* {@code false} otherwise.
*/
public boolean after(Date date) {
return milliseconds > date.milliseconds;
}
/**
* Returns if this {@code Date} is before the specified Date.
*
* @param date
* a {@code Date} instance to compare.
* @return {@code true} if this {@code Date} is before the specified {@code Date},
* {@code false} otherwise.
*/
public boolean before(Date date) {
return milliseconds < date.milliseconds;
}
/**
* Returns a new {@code Date} with the same millisecond value as this {@code Date}.
*
* @return a shallow copy of this {@code Date}.
*
* @see java.lang.Cloneable
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
/**
* Compare the receiver to the specified {@code Date} to determine the relative
* ordering.
*
* @param date
* a {@code Date} to compare against.
* @return an {@code int < 0} if this {@code Date} is less than the specified {@code Date}, {@code 0} if
* they are equal, and an {@code int > 0} if this {@code Date} is greater.
*/
public int compareTo(Date date) {
if (milliseconds < date.milliseconds) {
return -1;
}
if (milliseconds == date.milliseconds) {
return 0;
}
return 1;
}
/**
* Compares the specified object to this {@code Date} and returns if they are equal.
* To be equal, the object must be an instance of {@code Date} and have the same millisecond
* value.
*
* @param object
* the object to compare with this object.
* @return {@code true} if the specified object is equal to this {@code Date}, {@code false}
* otherwise.
*
* @see #hashCode
*/
@Override
public boolean equals(Object object) {
return (object == this) || (object instanceof Date)
&& (milliseconds == ((Date) object).milliseconds);
}
/**
* Returns the gregorian calendar day of the month for this {@code Date} object.
*
* @return the day of the month.
*
* @deprecated Use {@code Calendar.get(Calendar.DATE)} instead.
*/
@Deprecated
public int getDate() {
return new GregorianCalendar(milliseconds).get(Calendar.DATE);
}
/**
* Returns the gregorian calendar day of the week for this {@code Date} object.
*
* @return the day of the week.
*
* @deprecated Use {@code Calendar.get(Calendar.DAY_OF_WEEK)} instead.
*/
@Deprecated
public int getDay() {
return new GregorianCalendar(milliseconds).get(Calendar.DAY_OF_WEEK) - 1;
}
/**
* Returns the gregorian calendar hour of the day for this {@code Date} object.
*
* @return the hour of the day.
*
* @deprecated Use {@code Calendar.get(Calendar.HOUR_OF_DAY)} instead.
*/
@Deprecated
public int getHours() {
return new GregorianCalendar(milliseconds).get(Calendar.HOUR_OF_DAY);
}
/**
* Returns the gregorian calendar minute of the hour for this {@code Date} object.
*
* @return the minutes.
*
* @deprecated Use {@code Calendar.get(Calendar.MINUTE)} instead.
*/
@Deprecated
public int getMinutes() {
return new GregorianCalendar(milliseconds).get(Calendar.MINUTE);
}
/**
* Returns the gregorian calendar month for this {@code Date} object.
*
* @return the month.
*
* @deprecated Use {@code Calendar.get(Calendar.MONTH)} instead.
*/
@Deprecated
public int getMonth() {
return new GregorianCalendar(milliseconds).get(Calendar.MONTH);
}
/**
* Returns the gregorian calendar second of the minute for this {@code Date} object.
*
* @return the seconds.
*
* @deprecated Use {@code Calendar.get(Calendar.SECOND)} instead.
*/
@Deprecated
public int getSeconds() {
return new GregorianCalendar(milliseconds).get(Calendar.SECOND);
}
/**
* Returns this {@code Date} as a millisecond value. The value is the number of
* milliseconds since Jan. 1, 1970, midnight GMT.
*
* @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
*/
public long getTime() {
return milliseconds;
}
/**
* Returns the timezone offset in minutes of the default {@code TimeZone}.
*
* @return the timezone offset in minutes of the default {@code TimeZone}.
*
* @deprecated Use {@code (Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / 60000} instead.
*/
@Deprecated
public int getTimezoneOffset() {
GregorianCalendar cal = new GregorianCalendar(milliseconds);
return -(cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 60000;
}
/**
* Returns the gregorian calendar year since 1900 for this {@code Date} object.
*
* @return the year - 1900.
*
* @deprecated Use {@code Calendar.get(Calendar.YEAR) - 1900} instead.
*/
@Deprecated
public int getYear() {
return new GregorianCalendar(milliseconds).get(Calendar.YEAR) - 1900;
}
/**
* Returns an integer hash code for the receiver. Objects which are equal
* return the same value for this method.
*
* @return this {@code Date}'s hash.
*
* @see #equals
*/
@Override
public int hashCode() {
return (int) (milliseconds >>> 32) ^ (int) milliseconds;
}
private static int parse(String string, String[] array) {
for (int i = 0, alength = array.length, slength = string.length(); i < alength; i++) {
if (string.regionMatches(true, 0, array[i], 0, slength)) {
return i;
}
}
return -1;
}
private static IllegalArgumentException parseError(String string) {
throw new IllegalArgumentException("Parse error: " + string);
}
/**
* Returns the millisecond value of the date and time parsed from the
* specified {@code String}. Many date/time formats are recognized, including IETF
* standard syntax, i.e. Tue, 22 Jun 1999 12:16:00 GMT-0500
*
* @param string
* the String to parse.
* @return the millisecond value parsed from the String.
*
* @deprecated Use {@link DateFormat} instead.
*/
@Deprecated
public static long parse(String string) {
if (string == null) {
throw new IllegalArgumentException("The string argument is null");
}
char sign = 0;
int commentLevel = 0;
int offset = 0, length = string.length(), state = 0;
int year = -1, month = -1, date = -1;
int hour = -1, minute = -1, second = -1, zoneOffset = 0, minutesOffset = 0;
boolean zone = false;
final int PAD = 0, LETTERS = 1, NUMBERS = 2;
StringBuilder buffer = new StringBuilder();
while (offset <= length) {
char next = offset < length ? string.charAt(offset) : '\r';
offset++;
if (next == '(') {
commentLevel++;
}
if (commentLevel > 0) {
if (next == ')') {
commentLevel--;
}
if (commentLevel == 0) {
next = ' ';
} else {
continue;
}
}
int nextState = PAD;
if ('a' <= next && next <= 'z' || 'A' <= next && next <= 'Z') {
nextState = LETTERS;
} else if ('0' <= next && next <= '9') {
nextState = NUMBERS;
} else if (!Character.isSpace(next) && ",+-:/".indexOf(next) == -1) {
throw parseError(string);
}
if (state == NUMBERS && nextState != NUMBERS) {
int digit = Integer.parseInt(buffer.toString());
buffer.setLength(0);
if (sign == '+' || sign == '-') {
if (zoneOffset == 0) {
zone = true;
if (next == ':') {
minutesOffset = sign == '-' ? -Integer
.parseInt(string.substring(offset,
offset + 2)) : Integer
.parseInt(string.substring(offset,
offset + 2));
offset += 2;
}
zoneOffset = sign == '-' ? -digit : digit;
sign = 0;
} else {
throw parseError(string);
}
} else if (digit >= 70) {
if (year == -1
&& (Character.isSpace(next) || next == ','
|| next == '/' || next == '\r')) {
year = digit;
} else {
throw parseError(string);
}
} else if (next == ':') {
if (hour == -1) {
hour = digit;
} else if (minute == -1) {
minute = digit;
} else {
throw parseError(string);
}
} else if (next == '/') {
if (month == -1) {
month = digit - 1;
} else if (date == -1) {
date = digit;
} else {
throw parseError(string);
}
} else if (Character.isSpace(next) || next == ','
|| next == '-' || next == '\r') {
if (hour != -1 && minute == -1) {
minute = digit;
} else if (minute != -1 && second == -1) {
second = digit;
} else if (date == -1) {
date = digit;
} else if (year == -1) {
year = digit;
} else {
throw parseError(string);
}
} else if (year == -1 && month != -1 && date != -1) {
year = digit;
} else {
throw parseError(string);
}
} else if (state == LETTERS && nextState != LETTERS) {
String text = buffer.toString().toUpperCase(Locale.US);
buffer.setLength(0);
if (text.length() == 1) {
throw parseError(string);
}
if (text.equals("AM")) {
if (hour == 12) {
hour = 0;
} else if (hour < 1 || hour > 12) {
throw parseError(string);
}
} else if (text.equals("PM")) {
if (hour == 12) {
hour = 0;
} else if (hour < 1 || hour > 12) {
throw parseError(string);
}
hour += 12;
} else {
DateFormatSymbols symbols = new DateFormatSymbols(Locale.US);
String[] weekdays = symbols.getWeekdays(), months = symbols
.getMonths();
int value;
if (parse(text, weekdays) != -1) {/* empty */
} else if (month == -1 && (month = parse(text, months)) != -1) {/* empty */
} else if (text.equals("GMT") || text.equals("UT") || text.equals("UTC")) {
zone = true;
zoneOffset = 0;
} else if ((value = zone(text)) != 0) {
zone = true;
zoneOffset = value;
} else {
throw parseError(string);
}
}
}
if (next == '+' || (year != -1 && next == '-')) {
sign = next;
} else if (!Character.isSpace(next) && next != ','
&& nextState != NUMBERS) {
sign = 0;
}
if (nextState == LETTERS || nextState == NUMBERS) {
buffer.append(next);
}
state = nextState;
}
if (year != -1 && month != -1 && date != -1) {
if (hour == -1) {
hour = 0;
}
if (minute == -1) {
minute = 0;
}
if (second == -1) {
second = 0;
}
if (year < (CreationYear.VALUE - 80)) {
year += 2000;
} else if (year < 100) {
year += 1900;
}
minute -= minutesOffset;
if (zone) {
if (zoneOffset >= 24 || zoneOffset <= -24) {
hour -= zoneOffset / 100;
minute -= zoneOffset % 100;
} else {
hour -= zoneOffset;
}
return UTC(year - 1900, month, date, hour, minute, second);
}
return new Date(year - 1900, month, date, hour, minute, second)
.getTime();
}
throw parseError(string);
}
/**
* Sets the gregorian calendar day of the month for this {@code Date} object.
*
* @param day
* the day of the month.
*
* @deprecated Use {@code Calendar.set(Calendar.DATE, day)} instead.
*/
@Deprecated
public void setDate(int day) {
GregorianCalendar cal = new GregorianCalendar(milliseconds);
cal.set(Calendar.DATE, day);
milliseconds = cal.getTimeInMillis();
}
/**
* Sets the gregorian calendar hour of the day for this {@code Date} object.
*
* @param hour
* the hour of the day.
*
* @deprecated Use {@code Calendar.set(Calendar.HOUR_OF_DAY, hour)} instead.
*/
@Deprecated
public void setHours(int hour) {
GregorianCalendar cal = new GregorianCalendar(milliseconds);
cal.set(Calendar.HOUR_OF_DAY, hour);
milliseconds = cal.getTimeInMillis();
}
/**
* Sets the gregorian calendar minute of the hour for this {@code Date} object.
*
* @param minute
* the minutes.
*
* @deprecated Use {@code Calendar.set(Calendar.MINUTE, minute)} instead.
*/
@Deprecated
public void setMinutes(int minute) {
GregorianCalendar cal = new GregorianCalendar(milliseconds);
cal.set(Calendar.MINUTE, minute);
milliseconds = cal.getTimeInMillis();
}
/**
* Sets the gregorian calendar month for this {@code Date} object.
*
* @param month
* the month.
*
* @deprecated Use {@code Calendar.set(Calendar.MONTH, month)} instead.
*/
@Deprecated
public void setMonth(int month) {
GregorianCalendar cal = new GregorianCalendar(milliseconds);
cal.set(Calendar.MONTH, month);
milliseconds = cal.getTimeInMillis();
}
/**
* Sets the gregorian calendar second of the minute for this {@code Date} object.
*
* @param second
* the seconds.
*
* @deprecated Use {@code Calendar.set(Calendar.SECOND, second)} instead.
*/
@Deprecated
public void setSeconds(int second) {
GregorianCalendar cal = new GregorianCalendar(milliseconds);
cal.set(Calendar.SECOND, second);
milliseconds = cal.getTimeInMillis();
}
/**
* Sets this {@code Date} to the specified millisecond value. The value is the
* number of milliseconds since Jan. 1, 1970 GMT.
*
* @param milliseconds
* the number of milliseconds since Jan. 1, 1970 GMT.
*/
public void setTime(long milliseconds) {
this.milliseconds = milliseconds;
}
/**
* Sets the gregorian calendar year since 1900 for this {@code Date} object.
*
* @param year
* the year since 1900.
*
* @deprecated Use {@code Calendar.set(Calendar.YEAR, year + 1900)} instead.
*/
@Deprecated
public void setYear(int year) {
GregorianCalendar cal = new GregorianCalendar(milliseconds);
cal.set(Calendar.YEAR, year + 1900);
milliseconds = cal.getTimeInMillis();
}
/**
* Returns the string representation of this {@code Date} in GMT in the format
* {@code "22 Jun 1999 13:02:00 GMT"}.
*
* @deprecated Use {@link DateFormat} instead.
*/
@Deprecated
public String toGMTString() {
SimpleDateFormat sdf = new SimpleDateFormat("d MMM y HH:mm:ss 'GMT'", Locale.US);
TimeZone gmtZone = TimeZone.getTimeZone("GMT");
sdf.setTimeZone(gmtZone);
GregorianCalendar gc = new GregorianCalendar(gmtZone);
gc.setTimeInMillis(milliseconds);
return sdf.format(this);
}
/**
* Returns the string representation of this {@code Date} for the default {@code Locale}.
*
* @deprecated Use {@link DateFormat} instead.
*/
@Deprecated
public String toLocaleString() {
return DateFormat.getDateTimeInstance().format(this);
}
/**
* Returns a string representation of this {@code Date}. The formatting is equivalent to
* using a {@code SimpleDateFormat} with the format string "EEE MMM dd HH:mm:ss zzz yyyy",
* which looks something like "Tue Jun 22 13:07:00 PDT 1999". While the current default time
* zone is used, all formatting and timezone names follow {@code Locale.US}. If you need control
* over the time zone or locale, use {@code SimpleDateFormat} instead.
*/
@Override
public String toString() {
// TODO: equivalent to the following one-liner, though that's slower on stingray
// at 476us versus 69us...
// return new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").format(d, Locale.US);
LocaleData localeData = LocaleData.get(Locale.US);
TimeZone tz = TimeZone.getDefault();
Calendar cal = new GregorianCalendar(tz, Locale.US);
cal.setTimeInMillis(milliseconds);
StringBuilder result = new StringBuilder();
result.append(localeData.shortWeekdayNames[cal.get(Calendar.DAY_OF_WEEK)]);
result.append(' ');
result.append(localeData.shortMonthNames[cal.get(Calendar.MONTH)]);
result.append(' ');
appendTwoDigits(result, cal.get(Calendar.DAY_OF_MONTH));
result.append(' ');
appendTwoDigits(result, cal.get(Calendar.HOUR_OF_DAY));
result.append(':');
appendTwoDigits(result, cal.get(Calendar.MINUTE));
result.append(':');
appendTwoDigits(result, cal.get(Calendar.SECOND));
result.append(' ');
result.append(tz.getDisplayName(tz.inDaylightTime(this), TimeZone.SHORT, Locale.US));
result.append(' ');
result.append(cal.get(Calendar.YEAR));
return result.toString();
}
private static void appendTwoDigits(StringBuilder sb, int n) {
if (n < 10) {
sb.append('0');
}
sb.append(n);
}
/**
* Returns the millisecond value of the specified date and time in GMT.
*
* @param year
* the year, 0 is 1900.
* @param month
* the month, 0 - 11.
* @param day
* the day of the month, 1 - 31.
* @param hour
* the hour of day, 0 - 23.
* @param minute
* the minute of the hour, 0 - 59.
* @param second
* the second of the minute, 0 - 59.
* @return the date and time in GMT in milliseconds.
*
* @deprecated Use code like this instead:<code>
* Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
* cal.set(year + 1900, month, day, hour, minute, second);
* cal.getTime().getTime();</code>
*/
@Deprecated
public static long UTC(int year, int month, int day, int hour, int minute,
int second) {
GregorianCalendar cal = new GregorianCalendar(false);
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
cal.set(1900 + year, month, day, hour, minute, second);
return cal.getTimeInMillis();
}
private static int zone(String text) {
if (text.equals("EST")) {
return -5;
}
if (text.equals("EDT")) {
return -4;
}
if (text.equals("CST")) {
return -6;
}
if (text.equals("CDT")) {
return -5;
}
if (text.equals("MST")) {
return -7;
}
if (text.equals("MDT")) {
return -6;
}
if (text.equals("PST")) {
return -8;
}
if (text.equals("PDT")) {
return -7;
}
return 0;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeLong(getTime());
}
private void readObject(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
stream.defaultReadObject();
setTime(stream.readLong());
}
}