/* * Copyright 2007 Google Inc. * * 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 java.util; import java.io.Serializable; import jsinterop.annotations.JsPackage; import jsinterop.annotations.JsType; /** * Represents a date and time. */ public class Date implements Cloneable, Comparable<Date>, Serializable { /** * Encapsulates static data to avoid Date itself having a static initializer. */ private static class StringData { public static final String[] DAYS = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; public static final String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; } public static long parse(String s) { double parsed = NativeDate.parse(s); if (Double.isNaN(parsed)) { throw new IllegalArgumentException(); } return (long) parsed; } // CHECKSTYLE_OFF: Matching the spec. public static long UTC(int year, int month, int date, int hrs, int min, int sec) { return (long) NativeDate.UTC(year + 1900, month, date, hrs, min, sec, 0); } // CHECKSTYLE_ON /** * Ensure a number is displayed with two digits. * * @return a two-character base 10 representation of the number */ protected static String padTwo(int number) { if (number < 10) { return "0" + number; } else { return String.valueOf(number); } } /** * JavaScript Date instance. */ private final NativeDate jsdate; public Date() { jsdate = new NativeDate(); } public Date(int year, int month, int date) { this(year, month, date, 0, 0, 0); } public Date(int year, int month, int date, int hrs, int min) { this(year, month, date, hrs, min, 0); } public Date(int year, int month, int date, int hrs, int min, int sec) { jsdate = new NativeDate(); jsdate.setFullYear(year + 1900, month, date); jsdate.setHours(hrs, min, sec, 0); fixDaylightSavings(hrs); } public Date(long date) { jsdate = new NativeDate(date); } public Date(String date) { this(Date.parse(date)); } public boolean after(Date when) { return getTime() > when.getTime(); } public boolean before(Date when) { return getTime() < when.getTime(); } public Object clone() { return new Date(getTime()); } @Override public int compareTo(Date other) { return Long.compare(getTime(), other.getTime()); } @Override public boolean equals(Object obj) { return ((obj instanceof Date) && (getTime() == ((Date) obj).getTime())); } public int getDate() { return jsdate.getDate(); } public int getDay() { return jsdate.getDay(); } public int getHours() { return jsdate.getHours(); } public int getMinutes() { return jsdate.getMinutes(); } public int getMonth() { return jsdate.getMonth(); } public int getSeconds() { return jsdate.getSeconds(); } public long getTime() { return (long) jsdate.getTime(); } public int getTimezoneOffset() { return jsdate.getTimezoneOffset(); } public int getYear() { return jsdate.getFullYear() - 1900; } @Override public int hashCode() { long time = getTime(); return (int) (time ^ (time >>> 32)); } public void setDate(int date) { int hours = jsdate.getHours(); jsdate.setDate(date); fixDaylightSavings(hours); } public void setHours(int hours) { jsdate.setHours(hours); fixDaylightSavings(hours); } public void setMinutes(int minutes) { int hours = getHours() + minutes / 60; jsdate.setMinutes(minutes); fixDaylightSavings(hours); } public void setMonth(int month) { int hours = jsdate.getHours(); jsdate.setMonth(month); fixDaylightSavings(hours); } public void setSeconds(int seconds) { int hours = getHours() + seconds / (60 * 60); jsdate.setSeconds(seconds); fixDaylightSavings(hours); } public void setTime(long time) { jsdate.setTime(time); } public void setYear(int year) { int hours = jsdate.getHours(); jsdate.setFullYear(year + 1900); fixDaylightSavings(hours); } public String toGMTString() { return jsdate.getUTCDate() + " " + StringData.MONTHS[jsdate.getUTCMonth()] + " " + jsdate.getUTCFullYear() + " " + padTwo(jsdate.getUTCHours()) + ":" + padTwo(jsdate.getUTCMinutes()) + ":" + padTwo(jsdate.getUTCSeconds()) + " GMT"; } public String toLocaleString() { return jsdate.toLocaleString(); } @Override public String toString() { // Compute timezone offset. The value that getTimezoneOffset returns is // backwards for the transformation that we want. int offset = -jsdate.getTimezoneOffset(); String hourOffset = ((offset >= 0) ? "+" : "") + (offset / 60); String minuteOffset = padTwo(Math.abs(offset) % 60); return StringData.DAYS[jsdate.getDay()] + " " + StringData.MONTHS[jsdate.getMonth()] + " " + padTwo(jsdate.getDate()) + " " + padTwo(jsdate.getHours()) + ":" + padTwo(jsdate.getMinutes()) + ":" + padTwo(jsdate.getSeconds()) + " GMT" + hourOffset + minuteOffset + " " + jsdate.getFullYear(); } private static final long ONE_HOUR_IN_MILLISECONDS = 60 * 60 * 1000; /* * Some browsers have the following behavior: * * GAP * // Assume a U.S. time zone with daylight savings * // Set a non-existent time: 2:00 am Sunday March 8, 2009 * var date = new Date(2009, 2, 8, 2, 0, 0); * var hours = date.getHours(); // returns 1 * * The equivalent Java code will return 3. * * OVERLAP * // Assume a U.S. time zone with daylight savings * // Set to an ambiguous time: 1:30 am Sunday November 1, 2009 * var date = new Date(2009, 10, 1, 1, 30, 0); * var nextHour = new Date(date.getTime() + 60*60*1000); * var hours = nextHour.getHours(); // returns 1 * * The equivalent Java code will return 2. * * To compensate, fixDaylightSavings adjusts the date to match Java semantics. */ /** * Detects if the requested time falls into a non-existent time range due to local time advancing * into daylight savings time or is ambiguous due to going out of daylight savings. If so, adjust * accordingly. */ private void fixDaylightSavings(int requestedHours) { requestedHours %= 24; if (jsdate.getHours() != requestedHours) { // Hours passed to the constructor don't match the hours in the created JavaScript Date; this // might be due either because they are outside 0-24 range, there was overflow from // minutes:secs:millis or because we are in the situation GAP and has to be fixed. NativeDate copy = new NativeDate(jsdate.getTime()); copy.setDate(copy.getDate() + 1); int timeDiff = jsdate.getTimezoneOffset() - copy.getTimezoneOffset(); // If the time zone offset is changing, advance the hours and // minutes from the initially requested time by the change amount if (timeDiff > 0) { // The requested time falls into a non-existent time range due to // local time advancing into daylight savings time. If so, push the requested // time forward out of the non-existent range. int timeDiffHours = timeDiff / 60; int timeDiffMinutes = timeDiff % 60; int day = jsdate.getDate(); int badHours = jsdate.getHours(); if (badHours + timeDiffHours >= 24) { day++; } NativeDate newTime = new NativeDate(jsdate.getFullYear(), jsdate.getMonth(), day, requestedHours + timeDiffHours, jsdate.getMinutes() + timeDiffMinutes, jsdate.getSeconds(), jsdate.getMilliseconds()); jsdate.setTime(newTime.getTime()); } } // Check for situation OVERLAP by advancing the clock by 1 hour and see if getHours() returns // the same. This solves issues like Safari returning '3/21/2015 23:00' when time is set to // '2/22/2015'. double originalTimeInMillis = jsdate.getTime(); jsdate.setTime(originalTimeInMillis + ONE_HOUR_IN_MILLISECONDS); if (jsdate.getHours() != requestedHours) { // We are not in the duplicated hour, so revert the change. jsdate.setTime(originalTimeInMillis); } } @JsType(isNative = true, name = "Date", namespace = JsPackage.GLOBAL) private static class NativeDate { // CHECKSTYLE_OFF: Matching the spec. public static native double UTC(int year, int month, int dayOfMonth, int hours, int minutes, int seconds, int millis); // CHECKSTYLE_ON public static native double parse(String dateString); public NativeDate() { } public NativeDate(double milliseconds) { } public NativeDate(int year, int month, int dayOfMonth, int hours, int minutes, int seconds, int millis) { } public native int getDate(); public native int getDay(); public native int getFullYear(); public native int getHours(); public native int getMilliseconds(); public native int getMinutes(); public native int getMonth(); public native int getSeconds(); public native double getTime(); public native int getTimezoneOffset(); public native int getUTCDate(); public native int getUTCFullYear(); public native int getUTCHours(); public native int getUTCMinutes(); public native int getUTCMonth(); public native int getUTCSeconds(); public native void setDate(int dayOfMonth); public native void setFullYear(int year); public native void setFullYear(int year, int month, int day); public native void setHours(int hours); public native void setHours(int hours, int mins, int secs, int ms); public native void setMinutes(int minutes); public native void setMonth(int month); public native void setSeconds(int seconds); public native void setTime(double milliseconds); public native String toLocaleString(); } }