All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mozilla.javascript.NativeDate Maven / Gradle / Ivy

Go to download

Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.

The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * This class implements the Date native object. See ECMA 15.9.
 *
 * @author Mike McCabe
 *     

Significant parts of this code are adapted from the venerable jsdate.cpp (also Mozilla): * https://dxr.mozilla.org/mozilla-central/source/js/src/jsdate.cpp */ final class NativeDate extends IdScriptableObject { private static final long serialVersionUID = -8307438915861678966L; private static final Object DATE_TAG = "Date"; private static final String js_NaN_date_str = "Invalid Date"; static void init(Scriptable scope, boolean sealed) { NativeDate obj = new NativeDate(); // Set the value of the prototype Date to NaN ('invalid date'); obj.date = ScriptRuntime.NaN; obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); } private NativeDate() {} @Override public String getClassName() { return "Date"; } @Override public Object getDefaultValue(Class typeHint) { if (typeHint == null) typeHint = ScriptRuntime.StringClass; return super.getDefaultValue(typeHint); } double getJSTimeValue() { return date; } @Override protected void fillConstructorProperties(IdFunctionObject ctor) { addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_now, "now", 0); addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_parse, "parse", 1); addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_UTC, "UTC", 7); super.fillConstructorProperties(ctor); } @Override protected void initPrototypeId(int id) { String s; int arity; switch (id) { case Id_constructor: arity = 7; s = "constructor"; break; case Id_toString: arity = 0; s = "toString"; break; case Id_toTimeString: arity = 0; s = "toTimeString"; break; case Id_toDateString: arity = 0; s = "toDateString"; break; case Id_toLocaleString: arity = 0; s = "toLocaleString"; break; case Id_toLocaleTimeString: arity = 0; s = "toLocaleTimeString"; break; case Id_toLocaleDateString: arity = 0; s = "toLocaleDateString"; break; case Id_toUTCString: arity = 0; s = "toUTCString"; break; case Id_toSource: arity = 0; s = "toSource"; break; case Id_valueOf: arity = 0; s = "valueOf"; break; case Id_getTime: arity = 0; s = "getTime"; break; case Id_getYear: arity = 0; s = "getYear"; break; case Id_getFullYear: arity = 0; s = "getFullYear"; break; case Id_getUTCFullYear: arity = 0; s = "getUTCFullYear"; break; case Id_getMonth: arity = 0; s = "getMonth"; break; case Id_getUTCMonth: arity = 0; s = "getUTCMonth"; break; case Id_getDate: arity = 0; s = "getDate"; break; case Id_getUTCDate: arity = 0; s = "getUTCDate"; break; case Id_getDay: arity = 0; s = "getDay"; break; case Id_getUTCDay: arity = 0; s = "getUTCDay"; break; case Id_getHours: arity = 0; s = "getHours"; break; case Id_getUTCHours: arity = 0; s = "getUTCHours"; break; case Id_getMinutes: arity = 0; s = "getMinutes"; break; case Id_getUTCMinutes: arity = 0; s = "getUTCMinutes"; break; case Id_getSeconds: arity = 0; s = "getSeconds"; break; case Id_getUTCSeconds: arity = 0; s = "getUTCSeconds"; break; case Id_getMilliseconds: arity = 0; s = "getMilliseconds"; break; case Id_getUTCMilliseconds: arity = 0; s = "getUTCMilliseconds"; break; case Id_getTimezoneOffset: arity = 0; s = "getTimezoneOffset"; break; case Id_setTime: arity = 1; s = "setTime"; break; case Id_setMilliseconds: arity = 1; s = "setMilliseconds"; break; case Id_setUTCMilliseconds: arity = 1; s = "setUTCMilliseconds"; break; case Id_setSeconds: arity = 2; s = "setSeconds"; break; case Id_setUTCSeconds: arity = 2; s = "setUTCSeconds"; break; case Id_setMinutes: arity = 3; s = "setMinutes"; break; case Id_setUTCMinutes: arity = 3; s = "setUTCMinutes"; break; case Id_setHours: arity = 4; s = "setHours"; break; case Id_setUTCHours: arity = 4; s = "setUTCHours"; break; case Id_setDate: arity = 1; s = "setDate"; break; case Id_setUTCDate: arity = 1; s = "setUTCDate"; break; case Id_setMonth: arity = 2; s = "setMonth"; break; case Id_setUTCMonth: arity = 2; s = "setUTCMonth"; break; case Id_setFullYear: arity = 3; s = "setFullYear"; break; case Id_setUTCFullYear: arity = 3; s = "setUTCFullYear"; break; case Id_setYear: arity = 1; s = "setYear"; break; case Id_toISOString: arity = 0; s = "toISOString"; break; case Id_toJSON: arity = 1; s = "toJSON"; break; default: throw new IllegalArgumentException(String.valueOf(id)); } initPrototypeMethod(DATE_TAG, id, s, arity); } @Override public Object execIdCall( IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { if (!f.hasTag(DATE_TAG)) { return super.execIdCall(f, cx, scope, thisObj, args); } int id = f.methodId(); switch (id) { case ConstructorId_now: return ScriptRuntime.wrapNumber(now()); case ConstructorId_parse: { String dataStr = ScriptRuntime.toString(args, 0); return ScriptRuntime.wrapNumber(date_parseString(cx, dataStr)); } case ConstructorId_UTC: return ScriptRuntime.wrapNumber(jsStaticFunction_UTC(args)); case Id_constructor: { // if called as a function, just return a string // representing the current time. if (thisObj != null) return date_format(cx, now(), Id_toString); return jsConstructor(cx, args); } case Id_toJSON: { final String toISOString = "toISOString"; Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj); Object tv = ScriptRuntime.toPrimitive(o, ScriptRuntime.NumberClass); if (tv instanceof Number) { double d = ((Number) tv).doubleValue(); if (Double.isNaN(d) || Double.isInfinite(d)) { return null; } } Object toISO = ScriptableObject.getProperty(o, toISOString); if (toISO == NOT_FOUND) { throw ScriptRuntime.typeErrorById( "msg.function.not.found.in", toISOString, ScriptRuntime.toString(o)); } if (!(toISO instanceof Callable)) { throw ScriptRuntime.typeErrorById( "msg.isnt.function.in", toISOString, ScriptRuntime.toString(o), ScriptRuntime.toString(toISO)); } Object result = ((Callable) toISO).call(cx, scope, o, ScriptRuntime.emptyArgs); if (!ScriptRuntime.isPrimitive(result)) { throw ScriptRuntime.typeErrorById( "msg.toisostring.must.return.primitive", ScriptRuntime.toString(result)); } return result; } } // The rest of Date.prototype methods require thisObj to be Date NativeDate realThis = ensureType(thisObj, NativeDate.class, f); double t = realThis.date; switch (id) { case Id_toString: case Id_toTimeString: case Id_toDateString: if (!Double.isNaN(t)) { return date_format(cx, t, id); } return js_NaN_date_str; case Id_toLocaleString: case Id_toLocaleTimeString: case Id_toLocaleDateString: if (!Double.isNaN(t)) { return toLocale_helper(t, id); } return js_NaN_date_str; case Id_toUTCString: if (!Double.isNaN(t)) { return js_toUTCString(t); } return js_NaN_date_str; case Id_toSource: return "(new Date(" + ScriptRuntime.toString(t) + "))"; case Id_valueOf: case Id_getTime: return ScriptRuntime.wrapNumber(t); case Id_getYear: case Id_getFullYear: case Id_getUTCFullYear: if (!Double.isNaN(t)) { if (id != Id_getUTCFullYear) t = LocalTime(cx, t); t = YearFromTime(t); if (id == Id_getYear) { if (cx.hasFeature(Context.FEATURE_NON_ECMA_GET_YEAR)) { if (1900 <= t && t < 2000) { t -= 1900; } } else { t -= 1900; } } } return ScriptRuntime.wrapNumber(t); case Id_getMonth: case Id_getUTCMonth: if (!Double.isNaN(t)) { if (id == Id_getMonth) t = LocalTime(cx, t); t = MonthFromTime(t); } return ScriptRuntime.wrapNumber(t); case Id_getDate: case Id_getUTCDate: if (!Double.isNaN(t)) { if (id == Id_getDate) t = LocalTime(cx, t); t = DateFromTime(t); } return ScriptRuntime.wrapNumber(t); case Id_getDay: case Id_getUTCDay: if (!Double.isNaN(t)) { if (id == Id_getDay) t = LocalTime(cx, t); t = WeekDay(t); } return ScriptRuntime.wrapNumber(t); case Id_getHours: case Id_getUTCHours: if (!Double.isNaN(t)) { if (id == Id_getHours) t = LocalTime(cx, t); t = HourFromTime(t); } return ScriptRuntime.wrapNumber(t); case Id_getMinutes: case Id_getUTCMinutes: if (!Double.isNaN(t)) { if (id == Id_getMinutes) t = LocalTime(cx, t); t = MinFromTime(t); } return ScriptRuntime.wrapNumber(t); case Id_getSeconds: case Id_getUTCSeconds: if (!Double.isNaN(t)) { if (id == Id_getSeconds) t = LocalTime(cx, t); t = SecFromTime(t); } return ScriptRuntime.wrapNumber(t); case Id_getMilliseconds: case Id_getUTCMilliseconds: if (!Double.isNaN(t)) { if (id == Id_getMilliseconds) t = LocalTime(cx, t); t = msFromTime(t); } return ScriptRuntime.wrapNumber(t); case Id_getTimezoneOffset: if (!Double.isNaN(t)) { t = (t - LocalTime(cx, t)) / msPerMinute; } return ScriptRuntime.wrapNumber(t); case Id_setTime: t = TimeClip(ScriptRuntime.toNumber(args, 0)); realThis.date = t; return ScriptRuntime.wrapNumber(t); case Id_setMilliseconds: case Id_setUTCMilliseconds: case Id_setSeconds: case Id_setUTCSeconds: case Id_setMinutes: case Id_setUTCMinutes: case Id_setHours: case Id_setUTCHours: t = makeTime(cx, t, args, id); realThis.date = t; return ScriptRuntime.wrapNumber(t); case Id_setDate: case Id_setUTCDate: case Id_setMonth: case Id_setUTCMonth: case Id_setFullYear: case Id_setUTCFullYear: t = makeDate(cx, t, args, id); realThis.date = t; return ScriptRuntime.wrapNumber(t); case Id_setYear: { double year = ScriptRuntime.toNumber(args, 0); if (Double.isNaN(year) || Double.isInfinite(year)) { t = ScriptRuntime.NaN; } else { if (Double.isNaN(t)) { t = 0; } else { t = LocalTime(cx, t); } if (year >= 0 && year <= 99) year += 1900; double day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); t = MakeDate(day, TimeWithinDay(t)); t = internalUTC(cx, t); t = TimeClip(t); } } realThis.date = t; return ScriptRuntime.wrapNumber(t); case Id_toISOString: if (!Double.isNaN(t)) { return js_toISOString(t); } String msg = ScriptRuntime.getMessageById("msg.invalid.date"); throw ScriptRuntime.rangeError(msg); default: throw new IllegalArgumentException(String.valueOf(id)); } } /* ECMA helper functions */ private static final double HalfTimeDomain = 8.64e15; private static final double HoursPerDay = 24.0; private static final double MinutesPerHour = 60.0; private static final double SecondsPerMinute = 60.0; private static final double msPerSecond = 1000.0; private static final double MinutesPerDay = (HoursPerDay * MinutesPerHour); private static final double SecondsPerDay = (MinutesPerDay * SecondsPerMinute); private static final double SecondsPerHour = (MinutesPerHour * SecondsPerMinute); private static final double msPerDay = (SecondsPerDay * msPerSecond); private static final double msPerHour = (SecondsPerHour * msPerSecond); private static final double msPerMinute = (SecondsPerMinute * msPerSecond); private static double Day(double t) { return Math.floor(t / msPerDay); } private static double TimeWithinDay(double t) { double result; result = t % msPerDay; if (result < 0) result += msPerDay; return result; } private static boolean IsLeapYear(int year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } /* math here has to be f.p, because we need * floor((1968 - 1969) / 4) == -1 */ private static double DayFromYear(double y) { return ((365 * ((y) - 1970) + Math.floor(((y) - 1969) / 4.0) - Math.floor(((y) - 1901) / 100.0) + Math.floor(((y) - 1601) / 400.0))); } private static double TimeFromYear(double y) { return DayFromYear(y) * msPerDay; } private static int YearFromTime(double t) { if (Double.isInfinite(t) || Double.isNaN(t)) { return 0; } double y = Math.floor(t / (msPerDay * 365.2425)) + 1970; double t2 = TimeFromYear(y); /* * Adjust the year if the approximation was wrong. Since the year was * computed using the average number of ms per year, it will usually * be wrong for dates within several hours of a year transition. */ if (t2 > t) { y--; } else { if (t2 + msPerDay * DaysInYear(y) <= t) y++; } return (int) y; } private static double DayFromMonth(int m, int year) { int day = m * 30; if (m >= 7) { day += m / 2 - 1; } else if (m >= 2) { day += (m - 1) / 2 - 1; } else { day += m; } if (m >= 2 && IsLeapYear(year)) { ++day; } return day; } private static double DaysInYear(double year) { if (Double.isInfinite(year) || Double.isNaN(year)) { return ScriptRuntime.NaN; } return IsLeapYear((int) year) ? 366.0 : 365.0; } private static int DaysInMonth(int year, int month) { // month is 1-based for DaysInMonth! if (month == 2) return IsLeapYear(year) ? 29 : 28; return month >= 8 ? 31 - (month & 1) : 30 + (month & 1); } private static int MonthFromTime(double t) { int year = YearFromTime(t); int d = (int) (Day(t) - DayFromYear(year)); d -= 31 + 28; if (d < 0) { return (d < -28) ? 0 : 1; } if (IsLeapYear(year)) { if (d == 0) return 1; // 29 February --d; } // d: date count from 1 March int estimate = d / 30; // approx number of month since March int mstart; switch (estimate) { case 0: return 2; case 1: mstart = 31; break; case 2: mstart = 31 + 30; break; case 3: mstart = 31 + 30 + 31; break; case 4: mstart = 31 + 30 + 31 + 30; break; case 5: mstart = 31 + 30 + 31 + 30 + 31; break; case 6: mstart = 31 + 30 + 31 + 30 + 31 + 31; break; case 7: mstart = 31 + 30 + 31 + 30 + 31 + 31 + 30; break; case 8: mstart = 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31; break; case 9: mstart = 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30; break; case 10: return 11; // Late december default: throw Kit.codeBug(); } // if d < mstart then real month since March == estimate - 1 return (d >= mstart) ? estimate + 2 : estimate + 1; } private static int DateFromTime(double t) { int year = YearFromTime(t); int d = (int) (Day(t) - DayFromYear(year)); d -= 31 + 28; if (d < 0) { return (d < -28) ? d + 31 + 28 + 1 : d + 28 + 1; } if (IsLeapYear(year)) { if (d == 0) return 29; // 29 February --d; } // d: date count from 1 March int mdays, mstart; switch (d / 30) { // approx number of month since March case 0: return d + 1; case 1: mdays = 31; mstart = 31; break; case 2: mdays = 30; mstart = 31 + 30; break; case 3: mdays = 31; mstart = 31 + 30 + 31; break; case 4: mdays = 30; mstart = 31 + 30 + 31 + 30; break; case 5: mdays = 31; mstart = 31 + 30 + 31 + 30 + 31; break; case 6: mdays = 31; mstart = 31 + 30 + 31 + 30 + 31 + 31; break; case 7: mdays = 30; mstart = 31 + 30 + 31 + 30 + 31 + 31 + 30; break; case 8: mdays = 31; mstart = 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31; break; case 9: mdays = 30; mstart = 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30; break; case 10: return d - (31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30) + 1; // Late december default: throw Kit.codeBug(); } d -= mstart; if (d < 0) { // wrong estimate: sfhift to previous month d += mdays; } return d + 1; } private static int WeekDay(double t) { double result; result = Day(t) + 4; result = result % 7; if (result < 0) result += 7; return (int) result; } private static double now() { return System.currentTimeMillis(); } private static double DaylightSavingTA(Context cx, double t) { // Another workaround! The JRE doesn't seem to know about DST // before year 1 AD, so we map to equivalent dates for the // purposes of finding DST. To be safe, we do this for years // before 1970. if (t < 0.0) { int year = EquivalentYear(YearFromTime(t)); double day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); t = MakeDate(day, TimeWithinDay(t)); } Date date = new Date((long) t); if (cx.getTimeZone().inDaylightTime(date)) return msPerHour; return 0; } /* * Find a year for which any given date will fall on the same weekday. * * This function should be used with caution when used other than * for determining DST; it hasn't been proven not to produce an * incorrect year for times near year boundaries. */ private static int EquivalentYear(int year) { int day = (int) DayFromYear(year) + 4; day = day % 7; if (day < 0) day += 7; // Years and leap years on which Jan 1 is a Sunday, Monday, etc. if (IsLeapYear(year)) { switch (day) { case 0: return 1984; case 1: return 1996; case 2: return 1980; case 3: return 1992; case 4: return 1976; case 5: return 1988; case 6: return 1972; } } else { switch (day) { case 0: return 1978; case 1: return 1973; case 2: return 1985; case 3: return 1986; case 4: return 1981; case 5: return 1971; case 6: return 1977; } } // Unreachable throw Kit.codeBug(); } private static double LocalTime(Context cx, double t) { return t + cx.getTimeZone().getRawOffset() + DaylightSavingTA(cx, t); } private static double internalUTC(Context cx, double t) { double local = t - cx.getTimeZone().getRawOffset(); return local - DaylightSavingTA(cx, local); } private static int HourFromTime(double t) { double result; result = Math.floor(t / msPerHour) % HoursPerDay; if (result < 0) result += HoursPerDay; return (int) result; } private static int MinFromTime(double t) { double result; result = Math.floor(t / msPerMinute) % MinutesPerHour; if (result < 0) result += MinutesPerHour; return (int) result; } private static int SecFromTime(double t) { double result; result = Math.floor(t / msPerSecond) % SecondsPerMinute; if (result < 0) result += SecondsPerMinute; return (int) result; } private static int msFromTime(double t) { double result; result = t % msPerSecond; if (result < 0) result += msPerSecond; return (int) result; } private static double MakeTime(double hour, double min, double sec, double ms) { return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) * msPerSecond + ms; } private static double MakeDay(double year, double month, double date) { year += Math.floor(month / 12); month = month % 12; if (month < 0) month += 12; double yearday = Math.floor(TimeFromYear(year) / msPerDay); double monthday = DayFromMonth((int) month, (int) year); return yearday + monthday + date - 1; } private static double MakeDate(double day, double time) { return day * msPerDay + time; } private static double TimeClip(double d) { if (Double.isNaN(d) || d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY || Math.abs(d) > HalfTimeDomain) { return ScriptRuntime.NaN; } if (d > 0.0) return Math.floor(d + 0.); return Math.ceil(d + 0.); } /* end of ECMA helper functions */ /* find UTC time from given date... no 1900 correction! */ private static double date_msecFromDate( double year, double mon, double mday, double hour, double min, double sec, double msec) { double day; double time; double result; day = MakeDay(year, mon, mday); time = MakeTime(hour, min, sec, msec); result = MakeDate(day, time); return result; } /* compute the time in msec (unclipped) from the given args */ private static final int MAXARGS = 7; private static double date_msecFromArgs(Object[] args) { double[] array = new double[MAXARGS]; int loop; double d; for (loop = 0; loop < MAXARGS; loop++) { if (loop < args.length) { d = ScriptRuntime.toNumber(args[loop]); if (Double.isNaN(d) || Double.isInfinite(d)) { return ScriptRuntime.NaN; } array[loop] = ScriptRuntime.toInteger(args[loop]); } else { if (loop == 2) { array[loop] = 1; /* Default the date argument to 1. */ } else { array[loop] = 0; } } } /* adjust 2-digit years into the 20th century */ if (array[0] >= 0 && array[0] <= 99) array[0] += 1900; return date_msecFromDate( array[0], array[1], array[2], array[3], array[4], array[5], array[6]); } private static double jsStaticFunction_UTC(Object[] args) { if (args.length == 0) { return ScriptRuntime.NaN; } return TimeClip(date_msecFromArgs(args)); } /** * 15.9.1.15 Date Time String Format
* Parse input string according to simplified ISO-8601 Extended Format: * *

    *
  • YYYY-MM-DD'T'HH:mm:ss.sss'Z' *
  • or YYYY-MM-DD'T'HH:mm:ss.sss[+-]hh:mm *
*/ private static double parseISOString(Context cx, String s) { // we use a simple state machine to parse the input string final int ERROR = -1; final int YEAR = 0, MONTH = 1, DAY = 2; final int HOUR = 3, MIN = 4, SEC = 5, MSEC = 6; final int TZHOUR = 7, TZMIN = 8; int state = YEAR; // default values per [15.9.1.15 Date Time String Format] int[] values = {1970, 1, 1, 0, 0, 0, 0, -1, -1}; boolean timeSpecified = false; int yearlen = 4, yearmod = 1, tzmod = 1; int i = 0, len = s.length(); if (len != 0) { char c = s.charAt(0); if (c == '+' || c == '-') { // 15.9.1.15.1 Extended years i += 1; yearlen = 6; yearmod = (c == '-') ? -1 : 1; } else if (c == 'T') { // time-only forms no longer in spec, but follow spidermonkey here i += 1; state = HOUR; } } loop: while (state != ERROR) { int m = i + (state == YEAR ? yearlen : state == MSEC ? 3 : 2); if (m > len) { state = ERROR; break; } int value = 0; for (; i < m; ++i) { char c = s.charAt(i); if (c < '0' || c > '9') { state = ERROR; break loop; } value = 10 * value + (c - '0'); } values[state] = value; if (i == len) { // reached EOF, check for end state switch (state) { case HOUR: case TZHOUR: state = ERROR; } break; } char c = s.charAt(i++); if (c == 'Z') { // handle abbrevation for UTC timezone values[TZHOUR] = 0; values[TZMIN] = 0; switch (state) { case MIN: case SEC: case MSEC: break; default: state = ERROR; } break; } // state transition switch (state) { case YEAR: case MONTH: state = (c == '-' ? state + 1 : c == 'T' ? HOUR : ERROR); break; case DAY: state = (c == 'T' ? HOUR : ERROR); break; case HOUR: state = (c == ':' ? MIN : ERROR); timeSpecified = true; break; case TZHOUR: // state = (c == ':' ? state + 1 : ERROR); // Non-standard extension, https://bugzilla.mozilla.org/show_bug.cgi?id=682754 if (c != ':') { // back off by one and try to read without ':' separator i -= 1; } state = TZMIN; break; case MIN: state = (c == ':' ? SEC : c == '+' || c == '-' ? TZHOUR : ERROR); break; case SEC: state = (c == '.' ? MSEC : c == '+' || c == '-' ? TZHOUR : ERROR); break; case MSEC: state = (c == '+' || c == '-' ? TZHOUR : ERROR); break; case TZMIN: state = ERROR; break; } if (state == TZHOUR) { // save timezone modificator tzmod = (c == '-') ? -1 : 1; } } syntax: { // error or unparsed characters if (state == ERROR || i != len) break syntax; // check values int year = values[YEAR], month = values[MONTH], day = values[DAY]; int hour = values[HOUR], min = values[MIN], sec = values[SEC], msec = values[MSEC]; int tzhour = values[TZHOUR], tzmin = values[TZMIN]; if (year > 275943 // ceil(1e8/365) + 1970 = 275943 || (month < 1 || month > 12) || (day < 1 || day > DaysInMonth(year, month)) || hour > 24 || (hour == 24 && (min > 0 || sec > 0 || msec > 0)) || min > 59 || sec > 59 || tzhour > 23 || tzmin > 59) { break syntax; } // valid ISO-8601 format, compute date in milliseconds double date = date_msecFromDate(year * yearmod, month - 1, day, hour, min, sec, msec); if (tzhour == -1) { // Spec says to use UTC timezone, the following bug report says // that local timezone was meant to be used. Stick with spec for now. // https://bugs.ecmascript.org/show_bug.cgi?id=112 // date = internalUTC(date); // browsers doing this now if (timeSpecified) { date -= cx.getTimeZone().getRawOffset() + DaylightSavingTA(cx, date); } } else { date -= (tzhour * 60 + tzmin) * msPerMinute * tzmod; } if (date < -HalfTimeDomain || date > HalfTimeDomain) break syntax; return date; } // invalid ISO-8601 format, return NaN return ScriptRuntime.NaN; } private static double date_parseString(Context cx, String s) { double d = parseISOString(cx, s); if (!Double.isNaN(d)) { return d; } int year = -1; int mon = -1; int mday = -1; int hour = -1; int min = -1; int sec = -1; char c = 0; char si = 0; int i = 0; int n = -1; double tzoffset = -1; char prevc = 0; int limit = 0; boolean seenplusminus = false; limit = s.length(); while (i < limit) { c = s.charAt(i); i++; if (c <= ' ' || c == ',' || c == '-') { if (i < limit) { si = s.charAt(i); if (c == '-' && '0' <= si && si <= '9') { prevc = c; } } continue; } if (c == '(') { /* comments) */ int depth = 1; while (i < limit) { c = s.charAt(i); i++; if (c == '(') depth++; else if (c == ')') if (--depth <= 0) break; } continue; } if ('0' <= c && c <= '9') { n = c - '0'; while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') { n = n * 10 + c - '0'; i++; } /* allow TZA before the year, so * 'Wed Nov 05 21:49:11 GMT-0800 1997' * works */ /* uses of seenplusminus allow : in TZA, so Java * no-timezone style of GMT+4:30 works */ if ((prevc == '+' || prevc == '-') /* && year>=0 */) { /* make ':' case below change tzoffset */ seenplusminus = true; /* offset */ if (n < 24) n = n * 60; /* EG. "GMT-3" */ else n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */ if (prevc == '+') /* plus means east of GMT */ n = -n; if (tzoffset != 0 && tzoffset != -1) return ScriptRuntime.NaN; tzoffset = n; } else if (n >= 70 || (prevc == '/' && mon >= 0 && mday >= 0 && year < 0)) { if (year >= 0) return ScriptRuntime.NaN; else if (c <= ' ' || c == ',' || c == '/' || i >= limit) year = n < 100 ? n + 1900 : n; else return ScriptRuntime.NaN; } else if (c == ':') { if (hour < 0) hour = /*byte*/ n; else if (min < 0) min = /*byte*/ n; else return ScriptRuntime.NaN; } else if (c == '/') { if (mon < 0) mon = /*byte*/ n - 1; else if (mday < 0) mday = /*byte*/ n; else return ScriptRuntime.NaN; } else if (i < limit && c != ',' && c > ' ' && c != '-') { return ScriptRuntime.NaN; } else if (seenplusminus && n < 60) { /* handle GMT-3:30 */ if (tzoffset < 0) tzoffset -= n; else tzoffset += n; } else if (hour >= 0 && min < 0) { min = /*byte*/ n; } else if (min >= 0 && sec < 0) { sec = /*byte*/ n; } else if (mday < 0) { mday = /*byte*/ n; } else { return ScriptRuntime.NaN; } prevc = 0; } else if (c == '/' || c == ':' || c == '+' || c == '-') { prevc = c; } else { int st = i - 1; while (i < limit) { c = s.charAt(i); if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))) break; i++; } int letterCount = i - st; if (letterCount < 2) return ScriptRuntime.NaN; /* * Use ported code from jsdate.c rather than the locale-specific * date-parsing code from Java, to keep js and rhino consistent. * Is this the right strategy? */ String wtb = "am;pm;" + "monday;tuesday;wednesday;thursday;friday;" + "saturday;sunday;" + "january;february;march;april;may;june;" + "july;august;september;october;november;december;" + "gmt;ut;utc;est;edt;cst;cdt;mst;mdt;pst;pdt;"; int index = 0; for (int wtbOffset = 0; ; ) { int wtbNext = wtb.indexOf(';', wtbOffset); if (wtbNext < 0) return ScriptRuntime.NaN; if (wtb.regionMatches(true, wtbOffset, s, st, letterCount)) break; wtbOffset = wtbNext + 1; ++index; } if (index < 2) { /* * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as * 12:30, instead of blindly adding 12 if PM. */ if (hour > 12 || hour < 0) { return ScriptRuntime.NaN; } else if (index == 0) { // AM if (hour == 12) hour = 0; } else { // PM if (hour != 12) hour += 12; } } else if ((index -= 2) < 7) { // ignore week days } else if ((index -= 7) < 12) { // month if (mon < 0) { mon = index; } else { return ScriptRuntime.NaN; } } else { index -= 12; // timezones switch (index) { case 0 /* gmt */: tzoffset = 0; break; case 1 /* ut */: tzoffset = 0; break; case 2 /* utc */: tzoffset = 0; break; case 3 /* est */: tzoffset = 5 * 60; break; case 4 /* edt */: tzoffset = 4 * 60; break; case 5 /* cst */: tzoffset = 6 * 60; break; case 6 /* cdt */: tzoffset = 5 * 60; break; case 7 /* mst */: tzoffset = 7 * 60; break; case 8 /* mdt */: tzoffset = 6 * 60; break; case 9 /* pst */: tzoffset = 8 * 60; break; case 10 /* pdt */: tzoffset = 7 * 60; break; default: Kit.codeBug(); } } } } if (year < 0 || mon < 0 || mday < 0) return ScriptRuntime.NaN; if (sec < 0) sec = 0; if (min < 0) min = 0; if (hour < 0) hour = 0; double msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0); if (tzoffset == -1) { /* no time zone specified, have to use local */ return internalUTC(cx, msec); } return msec + tzoffset * msPerMinute; } private static String date_format(Context cx, double t, int methodId) { StringBuilder result = new StringBuilder(60); double local = LocalTime(cx, t); /* Tue Oct 31 09:41:40 GMT-0800 (PST) 2000 */ /* Tue Oct 31 2000 */ /* 09:41:40 GMT-0800 (PST) */ if (methodId != Id_toTimeString) { appendWeekDayName(result, WeekDay(local)); result.append(' '); appendMonthName(result, MonthFromTime(local)); result.append(' '); append0PaddedUint(result, DateFromTime(local), 2); result.append(' '); int year = YearFromTime(local); if (year < 0) { result.append('-'); year = -year; } append0PaddedUint(result, year, 4); if (methodId != Id_toDateString) result.append(' '); } if (methodId != Id_toDateString) { append0PaddedUint(result, HourFromTime(local), 2); result.append(':'); append0PaddedUint(result, MinFromTime(local), 2); result.append(':'); append0PaddedUint(result, SecFromTime(local), 2); // offset from GMT in minutes. The offset includes daylight // savings, if it applies. int minutes = (int) Math.floor( (cx.getTimeZone().getRawOffset() + DaylightSavingTA(cx, t)) / msPerMinute); // map 510 minutes to 0830 hours int offset = (minutes / 60) * 100 + minutes % 60; if (offset > 0) { result.append(" GMT+"); } else { result.append(" GMT-"); offset = -offset; } append0PaddedUint(result, offset, 4); // Find an equivalent year before getting the timezone // comment. See DaylightSavingTA. if (t < 0.0) { int equiv = EquivalentYear(YearFromTime(local)); double day = MakeDay(equiv, MonthFromTime(t), DateFromTime(t)); t = MakeDate(day, TimeWithinDay(t)); } result.append(" ("); Date date = new Date((long) t); synchronized (timeZoneFormatter) { result.append(timeZoneFormatter.format(date)); } result.append(')'); } return result.toString(); } /* the javascript constructor */ private static Object jsConstructor(Context cx, Object[] args) { NativeDate obj = new NativeDate(); // if called as a constructor with no args, // return a new Date with the current time. if (args.length == 0) { obj.date = now(); return obj; } // if called with just one arg - if (args.length == 1) { Object arg0 = args[0]; if (arg0 instanceof NativeDate) { obj.date = ((NativeDate) arg0).date; return obj; } if (arg0 instanceof Scriptable) { arg0 = ((Scriptable) arg0).getDefaultValue(null); } double date; if (arg0 instanceof CharSequence) { // it's a string; parse it. date = date_parseString(cx, arg0.toString()); } else { // if it's not a string, use it as a millisecond date date = ScriptRuntime.toNumber(arg0); } obj.date = TimeClip(date); return obj; } double time = date_msecFromArgs(args); if (!Double.isNaN(time) && !Double.isInfinite(time)) time = TimeClip(internalUTC(cx, time)); obj.date = time; return obj; } private static String toLocale_helper(double t, int methodId) { DateFormat formatter; switch (methodId) { case Id_toLocaleString: formatter = localeDateTimeFormatter; break; case Id_toLocaleTimeString: formatter = localeTimeFormatter; break; case Id_toLocaleDateString: formatter = localeDateFormatter; break; default: throw new AssertionError(); // unreachable } synchronized (formatter) { return formatter.format(new Date((long) t)); } } private static String js_toUTCString(double date) { StringBuilder result = new StringBuilder(60); appendWeekDayName(result, WeekDay(date)); result.append(", "); append0PaddedUint(result, DateFromTime(date), 2); result.append(' '); appendMonthName(result, MonthFromTime(date)); result.append(' '); int year = YearFromTime(date); if (year < 0) { result.append('-'); year = -year; } append0PaddedUint(result, year, 4); result.append(' '); append0PaddedUint(result, HourFromTime(date), 2); result.append(':'); append0PaddedUint(result, MinFromTime(date), 2); result.append(':'); append0PaddedUint(result, SecFromTime(date), 2); result.append(" GMT"); return result.toString(); } private static String js_toISOString(double t) { StringBuilder result = new StringBuilder(27); int year = YearFromTime(t); if (year < 0) { result.append('-'); append0PaddedUint(result, -year, 6); } else if (year > 9999) { result.append('+'); append0PaddedUint(result, year, 6); } else { append0PaddedUint(result, year, 4); } result.append('-'); append0PaddedUint(result, MonthFromTime(t) + 1, 2); result.append('-'); append0PaddedUint(result, DateFromTime(t), 2); result.append('T'); append0PaddedUint(result, HourFromTime(t), 2); result.append(':'); append0PaddedUint(result, MinFromTime(t), 2); result.append(':'); append0PaddedUint(result, SecFromTime(t), 2); result.append('.'); append0PaddedUint(result, msFromTime(t), 3); result.append('Z'); return result.toString(); } private static void append0PaddedUint(StringBuilder sb, int i, int minWidth) { if (i < 0) Kit.codeBug(); int scale = 1; --minWidth; if (i >= 10) { if (i < 1000 * 1000 * 1000) { for (; ; ) { int newScale = scale * 10; if (i < newScale) { break; } --minWidth; scale = newScale; } } else { // Separated case not to check against 10 * 10^9 overflow minWidth -= 9; scale = 1000 * 1000 * 1000; } } while (minWidth > 0) { sb.append('0'); --minWidth; } while (scale != 1) { sb.append((char) ('0' + (i / scale))); i %= scale; scale /= 10; } sb.append((char) ('0' + i)); } private static void appendMonthName(StringBuilder sb, int index) { // Take advantage of the fact that all month abbreviations // have the same length to minimize amount of strings runtime has // to keep in memory String months = "Jan" + "Feb" + "Mar" + "Apr" + "May" + "Jun" + "Jul" + "Aug" + "Sep" + "Oct" + "Nov" + "Dec"; index *= 3; for (int i = 0; i != 3; ++i) { sb.append(months.charAt(index + i)); } } private static void appendWeekDayName(StringBuilder sb, int index) { String days = "Sun" + "Mon" + "Tue" + "Wed" + "Thu" + "Fri" + "Sat"; index *= 3; for (int i = 0; i != 3; ++i) { sb.append(days.charAt(index + i)); } } private static double makeTime(Context cx, double date, Object[] args, int methodId) { if (args.length == 0) { /* * Satisfy the ECMA rule that if a function is called with * fewer arguments than the specified formal arguments, the * remaining arguments are set to undefined. Seems like all * the Date.setWhatever functions in ECMA are only varargs * beyond the first argument; this should be set to undefined * if it's not given. This means that "d = new Date(); * d.setMilliseconds()" returns NaN. Blech. */ return ScriptRuntime.NaN; } int maxargs; boolean local = true; switch (methodId) { case Id_setUTCMilliseconds: local = false; // fallthrough case Id_setMilliseconds: maxargs = 1; break; case Id_setUTCSeconds: local = false; // fallthrough case Id_setSeconds: maxargs = 2; break; case Id_setUTCMinutes: local = false; // fallthrough case Id_setMinutes: maxargs = 3; break; case Id_setUTCHours: local = false; // fallthrough case Id_setHours: maxargs = 4; break; default: throw Kit.codeBug(); } boolean hasNaN = false; int numNums = args.length < maxargs ? args.length : maxargs; assert numNums <= 4; double[] nums = new double[4]; for (int i = 0; i < numNums; i++) { double d = ScriptRuntime.toNumber(args[i]); if (Double.isNaN(d) || Double.isInfinite(d)) { hasNaN = true; } else { nums[i] = ScriptRuntime.toInteger(d); } } // just return NaN if the date is already NaN, // limit checks that happen in MakeTime in ECMA. if (hasNaN || Double.isNaN(date)) { return ScriptRuntime.NaN; } int i = 0, stop = numNums; double hour, min, sec, msec; double lorutime; /* Local or UTC version of date */ if (local) lorutime = LocalTime(cx, date); else lorutime = date; if (maxargs >= 4 && i < stop) hour = nums[i++]; else hour = HourFromTime(lorutime); if (maxargs >= 3 && i < stop) min = nums[i++]; else min = MinFromTime(lorutime); if (maxargs >= 2 && i < stop) sec = nums[i++]; else sec = SecFromTime(lorutime); if (maxargs >= 1 && i < stop) msec = nums[i++]; else msec = msFromTime(lorutime); double time = MakeTime(hour, min, sec, msec); double result = MakeDate(Day(lorutime), time); if (local) result = internalUTC(cx, result); return TimeClip(result); } private static double makeDate(Context cx, double date, Object[] args, int methodId) { /* see complaint about ECMA in date_MakeTime */ if (args.length == 0) { return ScriptRuntime.NaN; } int maxargs; boolean local = true; switch (methodId) { case Id_setUTCDate: local = false; // fallthrough case Id_setDate: maxargs = 1; break; case Id_setUTCMonth: local = false; // fallthrough case Id_setMonth: maxargs = 2; break; case Id_setUTCFullYear: local = false; // fallthrough case Id_setFullYear: maxargs = 3; break; default: throw Kit.codeBug(); } boolean hasNaN = false; int numNums = args.length < maxargs ? args.length : maxargs; assert 1 <= numNums && numNums <= 3; double[] nums = new double[3]; for (int i = 0; i < numNums; i++) { double d = ScriptRuntime.toNumber(args[i]); if (Double.isNaN(d) || Double.isInfinite(d)) { hasNaN = true; } else { nums[i] = ScriptRuntime.toInteger(d); } } // limit checks that happen in MakeTime in ECMA. if (hasNaN) { return ScriptRuntime.NaN; } int i = 0, stop = numNums; double year, month, day; double lorutime; /* Local or UTC version of date */ /* return NaN if date is NaN and we're not setting the year, * If we are, use 0 as the time. */ if (Double.isNaN(date)) { if (maxargs < 3) { return ScriptRuntime.NaN; } lorutime = 0; } else { if (local) lorutime = LocalTime(cx, date); else lorutime = date; } if (maxargs >= 3 && i < stop) year = nums[i++]; else year = YearFromTime(lorutime); if (maxargs >= 2 && i < stop) month = nums[i++]; else month = MonthFromTime(lorutime); if (maxargs >= 1 && i < stop) day = nums[i++]; else day = DateFromTime(lorutime); day = MakeDay(year, month, day); /* day within year */ double result = MakeDate(day, TimeWithinDay(lorutime)); if (local) result = internalUTC(cx, result); return TimeClip(result); } @Override protected int findPrototypeId(String s) { int id; switch (s) { case "constructor": id = Id_constructor; break; case "toString": id = Id_toString; break; case "toTimeString": id = Id_toTimeString; break; case "toDateString": id = Id_toDateString; break; case "toLocaleString": id = Id_toLocaleString; break; case "toLocaleTimeString": id = Id_toLocaleTimeString; break; case "toLocaleDateString": id = Id_toLocaleDateString; break; case "toUTCString": id = Id_toUTCString; break; case "toSource": id = Id_toSource; break; case "valueOf": id = Id_valueOf; break; case "getTime": id = Id_getTime; break; case "getYear": id = Id_getYear; break; case "getFullYear": id = Id_getFullYear; break; case "getUTCFullYear": id = Id_getUTCFullYear; break; case "getMonth": id = Id_getMonth; break; case "getUTCMonth": id = Id_getUTCMonth; break; case "getDate": id = Id_getDate; break; case "getUTCDate": id = Id_getUTCDate; break; case "getDay": id = Id_getDay; break; case "getUTCDay": id = Id_getUTCDay; break; case "getHours": id = Id_getHours; break; case "getUTCHours": id = Id_getUTCHours; break; case "getMinutes": id = Id_getMinutes; break; case "getUTCMinutes": id = Id_getUTCMinutes; break; case "getSeconds": id = Id_getSeconds; break; case "getUTCSeconds": id = Id_getUTCSeconds; break; case "getMilliseconds": id = Id_getMilliseconds; break; case "getUTCMilliseconds": id = Id_getUTCMilliseconds; break; case "getTimezoneOffset": id = Id_getTimezoneOffset; break; case "setTime": id = Id_setTime; break; case "setMilliseconds": id = Id_setMilliseconds; break; case "setUTCMilliseconds": id = Id_setUTCMilliseconds; break; case "setSeconds": id = Id_setSeconds; break; case "setUTCSeconds": id = Id_setUTCSeconds; break; case "setMinutes": id = Id_setMinutes; break; case "setUTCMinutes": id = Id_setUTCMinutes; break; case "setHours": id = Id_setHours; break; case "setUTCHours": id = Id_setUTCHours; break; case "setDate": id = Id_setDate; break; case "setUTCDate": id = Id_setUTCDate; break; case "setMonth": id = Id_setMonth; break; case "setUTCMonth": id = Id_setUTCMonth; break; case "setFullYear": id = Id_setFullYear; break; case "setUTCFullYear": id = Id_setUTCFullYear; break; case "setYear": id = Id_setYear; break; case "toISOString": id = Id_toISOString; break; case "toJSON": id = Id_toJSON; break; case "toGMTString": id = Id_toGMTString; break; default: id = 0; break; } return id; } private static final int ConstructorId_now = -3, ConstructorId_parse = -2, ConstructorId_UTC = -1, Id_constructor = 1, Id_toString = 2, Id_toTimeString = 3, Id_toDateString = 4, Id_toLocaleString = 5, Id_toLocaleTimeString = 6, Id_toLocaleDateString = 7, Id_toUTCString = 8, Id_toSource = 9, Id_valueOf = 10, Id_getTime = 11, Id_getYear = 12, Id_getFullYear = 13, Id_getUTCFullYear = 14, Id_getMonth = 15, Id_getUTCMonth = 16, Id_getDate = 17, Id_getUTCDate = 18, Id_getDay = 19, Id_getUTCDay = 20, Id_getHours = 21, Id_getUTCHours = 22, Id_getMinutes = 23, Id_getUTCMinutes = 24, Id_getSeconds = 25, Id_getUTCSeconds = 26, Id_getMilliseconds = 27, Id_getUTCMilliseconds = 28, Id_getTimezoneOffset = 29, Id_setTime = 30, Id_setMilliseconds = 31, Id_setUTCMilliseconds = 32, Id_setSeconds = 33, Id_setUTCSeconds = 34, Id_setMinutes = 35, Id_setUTCMinutes = 36, Id_setHours = 37, Id_setUTCHours = 38, Id_setDate = 39, Id_setUTCDate = 40, Id_setMonth = 41, Id_setUTCMonth = 42, Id_setFullYear = 43, Id_setUTCFullYear = 44, Id_setYear = 45, Id_toISOString = 46, Id_toJSON = 47, MAX_PROTOTYPE_ID = Id_toJSON; private static final int Id_toGMTString = Id_toUTCString; // Alias, see Ecma B.2.6 // not thread safe private static final DateFormat timeZoneFormatter = new SimpleDateFormat("zzz"); private static final DateFormat localeDateTimeFormatter = new SimpleDateFormat("MMMM d, yyyy h:mm:ss a z"); private static final DateFormat localeDateFormatter = new SimpleDateFormat("MMMM d, yyyy"); private static final DateFormat localeTimeFormatter = new SimpleDateFormat("h:mm:ss a z"); private double date; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy