1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.objects;
27
28import static java.lang.Double.NaN;
29import static java.lang.Double.isInfinite;
30import static java.lang.Double.isNaN;
31import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
32import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
33
34import java.util.Locale;
35import java.util.TimeZone;
36import java.util.concurrent.Callable;
37import jdk.nashorn.internal.objects.annotations.Attribute;
38import jdk.nashorn.internal.objects.annotations.Constructor;
39import jdk.nashorn.internal.objects.annotations.Function;
40import jdk.nashorn.internal.objects.annotations.ScriptClass;
41import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
42import jdk.nashorn.internal.objects.annotations.Where;
43import jdk.nashorn.internal.parser.DateParser;
44import jdk.nashorn.internal.runtime.JSType;
45import jdk.nashorn.internal.runtime.PropertyMap;
46import jdk.nashorn.internal.runtime.ScriptEnvironment;
47import jdk.nashorn.internal.runtime.ScriptObject;
48import jdk.nashorn.internal.runtime.ScriptRuntime;
49import jdk.nashorn.internal.runtime.linker.Bootstrap;
50import jdk.nashorn.internal.runtime.linker.InvokeByName;
51
52/**
53 * ECMA 15.9 Date Objects
54 *
55 */
56@ScriptClass("Date")
57public final class NativeDate extends ScriptObject {
58
59    private static final String INVALID_DATE = "Invalid Date";
60
61    private static final int YEAR        = 0;
62    private static final int MONTH       = 1;
63    private static final int DAY         = 2;
64    private static final int HOUR        = 3;
65    private static final int MINUTE      = 4;
66    private static final int SECOND      = 5;
67    private static final int MILLISECOND = 6;
68
69    private static final int FORMAT_DATE_TIME       = 0;
70    private static final int FORMAT_DATE            = 1;
71    private static final int FORMAT_TIME            = 2;
72    private static final int FORMAT_LOCAL_DATE_TIME = 3;
73    private static final int FORMAT_LOCAL_DATE      = 4;
74    private static final int FORMAT_LOCAL_TIME      = 5;
75
76    // Constants defined in ECMA 15.9.1.10
77    private static final int    hoursPerDay      = 24;
78    private static final int    minutesPerHour   = 60;
79    private static final int    secondsPerMinute = 60;
80    private static final int    msPerSecond   = 1_000;
81    private static final int    msPerMinute  = 60_000;
82    private static final double msPerHour = 3_600_000;
83    private static final double msPerDay = 86_400_000;
84
85    private static int[][] firstDayInMonth = {
86            {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, // normal year
87            {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}  // leap year
88    };
89
90    private static String[] weekDays = {
91            "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
92    };
93
94    private static String[] months = {
95            "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
96    };
97
98    private static final Object TO_ISO_STRING = new Object();
99
100    private static InvokeByName getTO_ISO_STRING() {
101        return Global.instance().getInvokeByName(TO_ISO_STRING,
102                new Callable<InvokeByName>() {
103                    @Override
104                    public InvokeByName call() {
105                        return new InvokeByName("toISOString", ScriptObject.class, Object.class, Object.class);
106                    }
107                });
108    }
109
110    private double time;
111    private final TimeZone timezone;
112
113    // initialized by nasgen
114    private static PropertyMap $nasgenmap$;
115
116    private NativeDate(final double time, final ScriptObject proto, final PropertyMap map) {
117        super(proto, map);
118        final ScriptEnvironment env = Global.getEnv();
119
120        this.time = time;
121        this.timezone = env._timezone;
122    }
123
124    NativeDate(final double time, final ScriptObject proto) {
125        this(time, proto, $nasgenmap$);
126    }
127
128    NativeDate(final double time, final Global global) {
129        this(time, global.getDatePrototype(), $nasgenmap$);
130    }
131
132    private NativeDate (final double time) {
133        this(time, Global.instance());
134    }
135
136    private NativeDate() {
137        this(System.currentTimeMillis());
138    }
139
140    @Override
141    public String getClassName() {
142        return "Date";
143    }
144
145    // ECMA 8.12.8 [[DefaultValue]] (hint)
146    @Override
147    public Object getDefaultValue(final Class<?> hint) {
148        // When the [[DefaultValue]] internal method of O is called with no hint,
149        // then it behaves as if the hint were Number, unless O is a Date object
150        // in which case it behaves as if the hint were String.
151        return super.getDefaultValue(hint == null ? String.class : hint);
152    }
153
154    /**
155     * Constructor - ECMA 15.9.3.1 new Date
156     *
157     * @param isNew is this Date constructed with the new operator
158     * @param self  self references
159     * @return Date representing now
160     */
161    @SpecializedFunction(isConstructor=true)
162    public static Object construct(final boolean isNew, final Object self) {
163        final NativeDate result = new NativeDate();
164        return isNew ? result : toStringImpl(result, FORMAT_DATE_TIME);
165    }
166
167    /**
168     * Constructor - ECMA 15.9.3.1 new Date (year, month [, date [, hours [, minutes [, seconds [, ms ] ] ] ] ] )
169     *
170     * @param isNew is this Date constructed with the new operator
171     * @param self  self reference
172     * @param args  arguments
173     * @return new Date
174     */
175    @Constructor(arity = 7)
176    public static Object construct(final boolean isNew, final Object self, final Object... args) {
177        if (! isNew) {
178            return toStringImpl(new NativeDate(), FORMAT_DATE_TIME);
179        }
180
181        NativeDate result;
182        switch (args.length) {
183        case 0:
184            result = new NativeDate();
185            break;
186
187        case 1:
188            double num;
189            final Object arg = JSType.toPrimitive(args[0]);
190            if (JSType.isString(arg)) {
191                num = parseDateString(arg.toString());
192            } else {
193                num = timeClip(JSType.toNumber(args[0]));
194            }
195            result = new NativeDate(num);
196            break;
197
198        default:
199            result = new NativeDate(0);
200            final double[] d = convertCtorArgs(args);
201            if (d == null) {
202                result.setTime(Double.NaN);
203            } else {
204                final double time = timeClip(utc(makeDate(d), result.getTimeZone()));
205                result.setTime(time);
206            }
207            break;
208         }
209
210         return result;
211    }
212
213    @Override
214    public String safeToString() {
215        final String str = isValidDate() ? toISOStringImpl(this) : INVALID_DATE;
216        return "[Date " + str + "]";
217    }
218
219    @Override
220    public String toString() {
221        return isValidDate() ? toString(this) : INVALID_DATE;
222    }
223
224    /**
225     * ECMA 15.9.4.2 Date.parse (string)
226     *
227     * @param self self reference
228     * @param string string to parse as date
229     * @return Date interpreted from the string, or NaN for illegal values
230     */
231    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
232    public static double parse(final Object self, final Object string) {
233        return parseDateString(JSType.toString(string));
234    }
235
236    /**
237     * ECMA 15.9.4.3 Date.UTC (year, month [, date [, hours [, minutes [, seconds [, ms ] ] ] ] ] )
238     *
239     * @param self self reference
240     * @param args mandatory args are year, month. Optional are date, hours, minutes, seconds and milliseconds
241     * @return a time clip according to the ECMA specification
242     */
243    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 7, where = Where.CONSTRUCTOR)
244    public static double UTC(final Object self, final Object... args) {
245        final NativeDate nd = new NativeDate(0);
246        final double[] d = convertCtorArgs(args);
247        final double time = d == null ? Double.NaN : timeClip(makeDate(d));
248        nd.setTime(time);
249        return time;
250    }
251
252    /**
253     * ECMA 15.9.4.4 Date.now ( )
254     *
255     * @param self self reference
256     * @return a Date that points to the current moment in time
257     */
258    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
259    public static double now(final Object self) {
260        // convert to double as long does not represent the primitive JS number type
261        return (double) System.currentTimeMillis();
262    }
263
264    /**
265     * ECMA 15.9.5.2 Date.prototype.toString ( )
266     *
267     * @param self self reference
268     * @return string value that represents the Date in the current time zone
269     */
270    @Function(attributes = Attribute.NOT_ENUMERABLE)
271    public static String toString(final Object self) {
272        return toStringImpl(self, FORMAT_DATE_TIME);
273    }
274
275    /**
276     * ECMA 15.9.5.3 Date.prototype.toDateString ( )
277     *
278     * @param self self reference
279     * @return string value with the "date" part of the Date in the current time zone
280     */
281    @Function(attributes = Attribute.NOT_ENUMERABLE)
282    public static String toDateString(final Object self) {
283        return toStringImpl(self, FORMAT_DATE);
284    }
285
286    /**
287     * ECMA 15.9.5.4 Date.prototype.toTimeString ( )
288     *
289     * @param self self reference
290     * @return string value with "time" part of Date in the current time zone
291     */
292    @Function(attributes = Attribute.NOT_ENUMERABLE)
293    public static String toTimeString(final Object self) {
294        return toStringImpl(self, FORMAT_TIME);
295    }
296
297    /**
298     * ECMA 15.9.5.5 Date.prototype.toLocaleString ( )
299     *
300     * @param self self reference
301     * @return string value that represents the Data in the current time zone and locale
302     */
303    @Function(attributes = Attribute.NOT_ENUMERABLE)
304    public static String toLocaleString(final Object self) {
305        return toStringImpl(self, FORMAT_LOCAL_DATE_TIME);
306    }
307
308    /**
309     * ECMA 15.9.5.6 Date.prototype.toLocaleDateString ( )
310     *
311     * @param self self reference
312     * @return string value with the "date" part of the Date in the current time zone and locale
313     */
314    @Function(attributes = Attribute.NOT_ENUMERABLE)
315    public static String toLocaleDateString(final Object self) {
316        return toStringImpl(self, FORMAT_LOCAL_DATE);
317    }
318
319    /**
320     * ECMA 15.9.5.7 Date.prototype.toLocaleTimeString ( )
321     *
322     * @param self self reference
323     * @return string value with the "time" part of Date in the current time zone and locale
324     */
325    @Function(attributes = Attribute.NOT_ENUMERABLE)
326    public static String toLocaleTimeString(final Object self) {
327        return toStringImpl(self, FORMAT_LOCAL_TIME);
328    }
329
330    /**
331     * ECMA 15.9.5.8 Date.prototype.valueOf ( )
332     *
333     * @param self self reference
334     * @return valueOf - a number which is this time value
335     */
336    @Function(attributes = Attribute.NOT_ENUMERABLE)
337    public static double valueOf(final Object self) {
338        final NativeDate nd = getNativeDate(self);
339        return (nd != null) ? nd.getTime() : Double.NaN;
340    }
341
342    /**
343     * ECMA 15.9.5.9 Date.prototype.getTime ( )
344     *
345     * @param self self reference
346     * @return time
347     */
348    @Function(attributes = Attribute.NOT_ENUMERABLE)
349    public static double getTime(final Object self) {
350        final NativeDate nd = getNativeDate(self);
351        return (nd != null) ? nd.getTime() : Double.NaN;
352    }
353
354    /**
355     * ECMA 15.9.5.10 Date.prototype.getFullYear ( )
356     *
357     * @param self self reference
358     * @return full year
359     */
360    @Function(attributes = Attribute.NOT_ENUMERABLE)
361    public static Object getFullYear(final Object self) {
362        return getField(self, YEAR);
363    }
364
365    /**
366     * ECMA 15.9.5.11 Date.prototype.getUTCFullYear( )
367     *
368     * @param self self reference
369     * @return UTC full year
370     */
371    @Function(attributes = Attribute.NOT_ENUMERABLE)
372    public static double getUTCFullYear(final Object self) {
373        return getUTCField(self, YEAR);
374    }
375
376    /**
377     * B.2.4 Date.prototype.getYear ( )
378     *
379     * @param self self reference
380     * @return year
381     */
382    @Function(attributes = Attribute.NOT_ENUMERABLE)
383    public static double getYear(final Object self) {
384        final NativeDate nd = getNativeDate(self);
385        return (nd != null && nd.isValidDate()) ? (yearFromTime(nd.getLocalTime()) - 1900) : Double.NaN;
386    }
387
388    /**
389     * ECMA 15.9.5.12 Date.prototype.getMonth ( )
390     *
391     * @param self self reference
392     * @return month
393     */
394    @Function(attributes = Attribute.NOT_ENUMERABLE)
395    public static double getMonth(final Object self) {
396        return getField(self, MONTH);
397    }
398
399    /**
400     * ECMA 15.9.5.13 Date.prototype.getUTCMonth ( )
401     *
402     * @param self self reference
403     * @return UTC month
404     */
405    @Function(attributes = Attribute.NOT_ENUMERABLE)
406    public static double getUTCMonth(final Object self) {
407        return getUTCField(self, MONTH);
408    }
409
410    /**
411     * ECMA 15.9.5.14 Date.prototype.getDate ( )
412     *
413     * @param self self reference
414     * @return date
415     */
416    @Function(attributes = Attribute.NOT_ENUMERABLE)
417    public static double getDate(final Object self) {
418        return getField(self, DAY);
419    }
420
421    /**
422     * ECMA 15.9.5.15 Date.prototype.getUTCDate ( )
423     *
424     * @param self self reference
425     * @return UTC Date
426     */
427    @Function(attributes = Attribute.NOT_ENUMERABLE)
428    public static double getUTCDate(final Object self) {
429        return getUTCField(self, DAY);
430    }
431
432    /**
433     * ECMA 15.9.5.16 Date.prototype.getDay ( )
434     *
435     * @param self self reference
436     * @return day
437     */
438    @Function(attributes = Attribute.NOT_ENUMERABLE)
439    public static double getDay(final Object self) {
440        final NativeDate nd = getNativeDate(self);
441        return (nd != null && nd.isValidDate()) ? weekDay(nd.getLocalTime()) : Double.NaN;
442    }
443
444    /**
445     * ECMA 15.9.5.17 Date.prototype.getUTCDay ( )
446     *
447     * @param self self reference
448     * @return UTC day
449     */
450    @Function(attributes = Attribute.NOT_ENUMERABLE)
451    public static double getUTCDay(final Object self) {
452        final NativeDate nd = getNativeDate(self);
453        return (nd != null && nd.isValidDate()) ? weekDay(nd.getTime()) : Double.NaN;
454    }
455
456    /**
457     * ECMA 15.9.5.18 Date.prototype.getHours ( )
458     *
459     * @param self self reference
460     * @return hours
461     */
462    @Function(attributes = Attribute.NOT_ENUMERABLE)
463    public static double getHours(final Object self) {
464        return getField(self, HOUR);
465    }
466
467    /**
468     * ECMA 15.9.5.19 Date.prototype.getUTCHours ( )
469     *
470     * @param self self reference
471     * @return UTC hours
472     */
473    @Function(attributes = Attribute.NOT_ENUMERABLE)
474    public static double getUTCHours(final Object self) {
475        return getUTCField(self, HOUR);
476    }
477
478    /**
479     * ECMA 15.9.5.20 Date.prototype.getMinutes ( )
480     *
481     * @param self self reference
482     * @return minutes
483     */
484    @Function(attributes = Attribute.NOT_ENUMERABLE)
485    public static double getMinutes(final Object self) {
486        return getField(self, MINUTE);
487    }
488
489    /**
490     * ECMA 15.9.5.21 Date.prototype.getUTCMinutes ( )
491     *
492     * @param self self reference
493     * @return UTC minutes
494     */
495    @Function(attributes = Attribute.NOT_ENUMERABLE)
496    public static double getUTCMinutes(final Object self) {
497        return getUTCField(self, MINUTE);
498    }
499
500    /**
501     * ECMA 15.9.5.22 Date.prototype.getSeconds ( )
502     *
503     * @param self self reference
504     * @return seconds
505     */
506    @Function(attributes = Attribute.NOT_ENUMERABLE)
507    public static double getSeconds(final Object self) {
508        return getField(self, SECOND);
509    }
510
511    /**
512     * ECMA 15.9.5.23 Date.prototype.getUTCSeconds ( )
513     *
514     * @param self self reference
515     * @return UTC seconds
516     */
517    @Function(attributes = Attribute.NOT_ENUMERABLE)
518    public static double getUTCSeconds(final Object self) {
519        return getUTCField(self, SECOND);
520    }
521
522    /**
523     * ECMA 15.9.5.24 Date.prototype.getMilliseconds ( )
524     *
525     * @param self self reference
526     * @return milliseconds
527     */
528    @Function(attributes = Attribute.NOT_ENUMERABLE)
529    public static double getMilliseconds(final Object self) {
530        return getField(self, MILLISECOND);
531    }
532
533    /**
534     * ECMA 15.9.5.25 Date.prototype.getUTCMilliseconds ( )
535     *
536     * @param self self reference
537     * @return UTC milliseconds
538     */
539    @Function(attributes = Attribute.NOT_ENUMERABLE)
540    public static double getUTCMilliseconds(final Object self) {
541        return getUTCField(self, MILLISECOND);
542    }
543
544    /**
545     * ECMA 15.9.5.26 Date.prototype.getTimezoneOffset ( )
546     *
547     * @param self self reference
548     * @return time zone offset or NaN if N/A
549     */
550    @Function(attributes = Attribute.NOT_ENUMERABLE)
551    public static double getTimezoneOffset(final Object self) {
552        final NativeDate nd = getNativeDate(self);
553        if (nd != null && nd.isValidDate()) {
554            final long msec = (long) nd.getTime();
555            return - nd.getTimeZone().getOffset(msec) / msPerMinute;
556        }
557        return Double.NaN;
558    }
559
560    /**
561     * ECMA 15.9.5.27 Date.prototype.setTime (time)
562     *
563     * @param self self reference
564     * @param time time
565     * @return time
566     */
567    @Function(attributes = Attribute.NOT_ENUMERABLE)
568    public static double setTime(final Object self, final Object time) {
569        final NativeDate nd  = getNativeDate(self);
570        final double     num = timeClip(JSType.toNumber(time));
571        nd.setTime(num);
572        return num;
573    }
574
575    /**
576     * ECMA 15.9.5.28 Date.prototype.setMilliseconds (ms)
577     *
578     * @param self self reference
579     * @param args milliseconds
580     * @return time
581     */
582    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
583    public static double setMilliseconds(final Object self, final Object... args) {
584        final NativeDate nd = getNativeDate(self);
585        setFields(nd, MILLISECOND, args, true);
586        return nd.getTime();
587    }
588
589    /**
590     * ECMA 15.9.5.29 Date.prototype.setUTCMilliseconds (ms)
591     *
592     * @param self self reference
593     * @param args utc milliseconds
594     * @return time
595     */
596    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
597    public static double setUTCMilliseconds(final Object self, final Object... args) {
598        final NativeDate nd = getNativeDate(self);
599        setFields(nd, MILLISECOND, args, false);
600        return nd.getTime();
601    }
602
603    /**
604     * ECMA 15.9.5.30 Date.prototype.setSeconds (sec [, ms ] )
605     *
606     * @param self self reference
607     * @param args seconds (milliseconds optional second argument)
608     * @return time
609     */
610    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2)
611    public static double setSeconds(final Object self, final Object... args) {
612        final NativeDate nd = getNativeDate(self);
613        setFields(nd, SECOND, args, true);
614        return nd.getTime();
615    }
616
617    /**
618     * ECMA 15.9.5.31 Date.prototype.setUTCSeconds (sec [, ms ] )
619     *
620     * @param self self reference
621     * @param args UTC seconds (milliseconds optional second argument)
622     * @return time
623     */
624    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2)
625    public static double setUTCSeconds(final Object self, final Object... args) {
626        final NativeDate nd = getNativeDate(self);
627        setFields(nd, SECOND, args, false);
628        return nd.getTime();
629    }
630
631    /**
632     * ECMA 15.9.5.32 Date.prototype.setMinutes (min [, sec [, ms ] ] )
633     *
634     * @param self self reference
635     * @param args minutes (seconds and milliseconds are optional second and third arguments)
636     * @return time
637     */
638    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3)
639    public static double setMinutes(final Object self, final Object... args) {
640        final NativeDate nd = getNativeDate(self);
641        setFields(nd, MINUTE, args, true);
642        return nd.getTime();
643    }
644
645    /**
646     * ECMA 15.9.5.33 Date.prototype.setUTCMinutes (min [, sec [, ms ] ] )
647     *
648     * @param self self reference
649     * @param args minutes (seconds and milliseconds are optional second and third arguments)
650     * @return time
651     */
652    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3)
653    public static double setUTCMinutes(final Object self, final Object... args) {
654        final NativeDate nd = getNativeDate(self);
655        setFields(nd, MINUTE, args, false);
656        return nd.getTime();
657    }
658
659    /**
660     * ECMA 15.9.5.34 Date.prototype.setHours (hour [, min [, sec [, ms ] ] ] )
661     *
662     * @param self self reference
663     * @param args hour (optional arguments after are minutes, seconds, milliseconds)
664     * @return time
665     */
666    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 4)
667    public static double setHours(final Object self, final Object... args) {
668        final NativeDate nd = getNativeDate(self);
669        setFields(nd, HOUR, args, true);
670        return nd.getTime();
671    }
672
673    /**
674     * ECMA 15.9.5.35 Date.prototype.setUTCHours (hour [, min [, sec [, ms ] ] ] )
675     *
676     * @param self self reference
677     * @param args hour (optional arguments after are minutes, seconds, milliseconds)
678     * @return time
679     */
680    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 4)
681    public static double setUTCHours(final Object self, final Object... args) {
682        final NativeDate nd = getNativeDate(self);
683        setFields(nd, HOUR, args, false);
684        return nd.getTime();
685    }
686
687    /**
688     * ECMA 15.9.5.36 Date.prototype.setDate (date)
689     *
690     * @param self self reference
691     * @param args date
692     * @return time
693     */
694    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
695    public static double setDate(final Object self, final Object... args) {
696        final NativeDate nd = getNativeDate(self);
697        setFields(nd, DAY, args, true);
698        return nd.getTime();
699    }
700
701    /**
702     * ECMA 15.9.5.37 Date.prototype.setUTCDate (date)
703     *
704     * @param self self reference
705     * @param args UTC date
706     * @return time
707     */
708    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
709    public static double setUTCDate(final Object self, final Object... args) {
710        final NativeDate nd = getNativeDate(self);
711        setFields(nd, DAY, args, false);
712        return nd.getTime();
713    }
714
715    /**
716     * ECMA 15.9.5.38 Date.prototype.setMonth (month [, date ] )
717     *
718     * @param self self reference
719     * @param args month (optional second argument is date)
720     * @return time
721     */
722    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2)
723    public static double setMonth(final Object self, final Object... args) {
724        final NativeDate nd = getNativeDate(self);
725        setFields(nd, MONTH, args, true);
726        return nd.getTime();
727    }
728
729    /**
730     * ECMA 15.9.5.39 Date.prototype.setUTCMonth (month [, date ] )
731     *
732     * @param self self reference
733     * @param args UTC month (optional second argument is date)
734     * @return time
735     */
736    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2)
737    public static double setUTCMonth(final Object self, final Object... args) {
738        final NativeDate nd = ensureNativeDate(self);
739        setFields(nd, MONTH, args, false);
740        return nd.getTime();
741    }
742
743    /**
744     * ECMA 15.9.5.40 Date.prototype.setFullYear (year [, month [, date ] ] )
745     *
746     * @param self self reference
747     * @param args year (optional second and third arguments are month and date)
748     * @return time
749     */
750    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3)
751    public static double setFullYear(final Object self, final Object... args) {
752        final NativeDate nd   = ensureNativeDate(self);
753        if (nd.isValidDate()) {
754            setFields(nd, YEAR, args, true);
755        } else {
756            final double[] d = convertArgs(args, 0, YEAR, YEAR, 3);
757            if (d != null) {
758                nd.setTime(timeClip(utc(makeDate(makeDay(d[0], d[1], d[2]), 0), nd.getTimeZone())));
759            } else {
760                nd.setTime(NaN);
761            }
762        }
763        return nd.getTime();
764    }
765
766    /**
767     * ECMA 15.9.5.41 Date.prototype.setUTCFullYear (year [, month [, date ] ] )
768     *
769     * @param self self reference
770     * @param args UTC full year (optional second and third arguments are month and date)
771     * @return time
772     */
773    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3)
774    public static double setUTCFullYear(final Object self, final Object... args) {
775        final NativeDate nd   = ensureNativeDate(self);
776        if (nd.isValidDate()) {
777            setFields(nd, YEAR, args, false);
778        } else {
779            final double[] d = convertArgs(args, 0, YEAR, YEAR, 3);
780            nd.setTime(timeClip(makeDate(makeDay(d[0], d[1], d[2]), 0)));
781        }
782        return nd.getTime();
783    }
784
785    /**
786     * ECMA B.2.5 Date.prototype.setYear (year)
787     *
788     * @param self self reference
789     * @param year year
790     * @return NativeDate
791     */
792    @Function(attributes = Attribute.NOT_ENUMERABLE)
793    public static double setYear(final Object self, final Object year) {
794        final NativeDate nd = getNativeDate(self);
795        if (isNaN(nd.getTime())) {
796            nd.setTime(utc(0, nd.getTimeZone()));
797        }
798
799        final double yearNum = JSType.toNumber(year);
800        if (isNaN(yearNum)) {
801            nd.setTime(NaN);
802            return nd.getTime();
803        }
804        int yearInt = (int)yearNum;
805        if (0 <= yearInt && yearInt <= 99) {
806            yearInt += 1900;
807        }
808        setFields(nd, YEAR, new Object[] {yearInt}, true);
809
810        return nd.getTime();
811    }
812
813    /**
814     * ECMA 15.9.5.42 Date.prototype.toUTCString ( )
815     *
816     * @param self self reference
817     * @return string representation of date
818     */
819    @Function(attributes = Attribute.NOT_ENUMERABLE)
820    public static String toUTCString(final Object self) {
821        return toGMTStringImpl(self);
822    }
823
824    /**
825     * ECMA B.2.6 Date.prototype.toGMTString ( )
826     *
827     * See {@link NativeDate#toUTCString(Object)}
828     *
829     * @param self self reference
830     * @return string representation of date
831     */
832    @Function(attributes = Attribute.NOT_ENUMERABLE)
833    public static String toGMTString(final Object self) {
834        return toGMTStringImpl(self);
835    }
836
837    /**
838     * ECMA 15.9.5.43 Date.prototype.toISOString ( )
839     *
840     * @param self self reference
841     * @return string representation of date
842     */
843    @Function(attributes = Attribute.NOT_ENUMERABLE)
844    public static String toISOString(final Object self) {
845        return toISOStringImpl(self);
846    }
847
848    /**
849     * ECMA 15.9.5.44 Date.prototype.toJSON ( key )
850     *
851     * Provides a string representation of this Date for use by {@link NativeJSON#stringify(Object, Object, Object, Object)}
852     *
853     * @param self self reference
854     * @param key ignored
855     * @return JSON representation of this date
856     */
857    @Function(attributes = Attribute.NOT_ENUMERABLE)
858    public static Object toJSON(final Object self, final Object key) {
859        // NOTE: Date.prototype.toJSON is generic. Accepts other objects as well.
860        final Object selfObj = Global.toObject(self);
861        if (!(selfObj instanceof ScriptObject)) {
862            return null;
863        }
864        final ScriptObject sobj  = (ScriptObject)selfObj;
865        final Object       value = sobj.getDefaultValue(Number.class);
866        if (value instanceof Number) {
867            final double num = ((Number)value).doubleValue();
868            if (isInfinite(num) || isNaN(num)) {
869                return null;
870            }
871        }
872
873        try {
874            final InvokeByName toIsoString = getTO_ISO_STRING();
875            final Object func = toIsoString.getGetter().invokeExact(sobj);
876            if (Bootstrap.isCallable(func)) {
877                return toIsoString.getInvoker().invokeExact(func, sobj, key);
878            }
879            throw typeError("not.a.function", ScriptRuntime.safeToString(func));
880        } catch (final RuntimeException | Error e) {
881            throw e;
882        } catch (final Throwable t) {
883            throw new RuntimeException(t);
884        }
885    }
886
887    // -- Internals below this point
888
889    private static double parseDateString(final String str) {
890
891        final DateParser parser = new DateParser(str);
892        if (parser.parse()) {
893            final Integer[] fields = parser.getDateFields();
894            double d = makeDate(fields);
895            if (fields[DateParser.TIMEZONE] != null) {
896                d -= fields[DateParser.TIMEZONE] * 60000;
897            } else {
898                d = utc(d, Global.getEnv()._timezone);
899            }
900            d = timeClip(d);
901            return d;
902        }
903
904        return Double.NaN;
905    }
906
907    private static void zeroPad(final StringBuilder sb, final int n, final int length) {
908        for (int l = 1, d = 10; l < length; l++, d *= 10) {
909            if (n < d) {
910                sb.append('0');
911            }
912        }
913        sb.append(n);
914    }
915
916    @SuppressWarnings("fallthrough")
917    private static String toStringImpl(final Object self, final int format) {
918        final NativeDate nd = getNativeDate(self);
919
920        if (nd != null && nd.isValidDate()) {
921            final StringBuilder sb = new StringBuilder(40);
922            final double t = nd.getLocalTime();
923
924            switch (format) {
925
926                case FORMAT_DATE_TIME:
927                case FORMAT_DATE :
928                case FORMAT_LOCAL_DATE_TIME:
929                    // EEE MMM dd yyyy
930                    sb.append(weekDays[weekDay(t)])
931                            .append(' ')
932                            .append(months[monthFromTime(t)])
933                            .append(' ');
934                    zeroPad(sb, dayFromTime(t), 2);
935                    sb.append(' ');
936                    zeroPad(sb, yearFromTime(t), 4);
937                    if (format == FORMAT_DATE) {
938                        break;
939                    }
940                    sb.append(' ');
941
942                case FORMAT_TIME:
943                    final TimeZone tz = nd.getTimeZone();
944                    final double utcTime = nd.getTime();
945                    int offset = tz.getOffset((long) utcTime) / 60000;
946                    final boolean inDaylightTime = offset != tz.getRawOffset() / 60000;
947                    // Convert minutes to HHmm timezone offset
948                    offset = (offset / 60) * 100 + offset % 60;
949
950                    // HH:mm:ss GMT+HHmm
951                    zeroPad(sb, hourFromTime(t), 2);
952                    sb.append(':');
953                    zeroPad(sb, minFromTime(t), 2);
954                    sb.append(':');
955                    zeroPad(sb, secFromTime(t), 2);
956                    sb.append(" GMT")
957                            .append(offset < 0 ? '-' : '+');
958                    zeroPad(sb, Math.abs(offset), 4);
959                    sb.append(" (")
960                            .append(tz.getDisplayName(inDaylightTime, TimeZone.SHORT, Locale.US))
961                            .append(')');
962                    break;
963
964                case FORMAT_LOCAL_DATE:
965                    // yyyy-MM-dd
966                    zeroPad(sb, yearFromTime(t), 4);
967                    sb.append('-');
968                    zeroPad(sb, monthFromTime(t) + 1, 2);
969                    sb.append('-');
970                    zeroPad(sb, dayFromTime(t), 2);
971                    break;
972
973                case FORMAT_LOCAL_TIME:
974                    // HH:mm:ss
975                    zeroPad(sb, hourFromTime(t), 2);
976                    sb.append(':');
977                    zeroPad(sb, minFromTime(t), 2);
978                    sb.append(':');
979                    zeroPad(sb, secFromTime(t), 2);
980                    break;
981
982                default:
983                    throw new IllegalArgumentException("format: " + format);
984            }
985
986            return sb.toString();
987        }
988
989        return INVALID_DATE;
990    }
991
992    private static String toGMTStringImpl(final Object self) {
993        final NativeDate nd = getNativeDate(self);
994
995        if (nd != null && nd.isValidDate()) {
996            final StringBuilder sb = new StringBuilder(29);
997            final double t = nd.getTime();
998            // EEE, dd MMM yyyy HH:mm:ss z
999            sb.append(weekDays[weekDay(t)])
1000                    .append(", ");
1001            zeroPad(sb, dayFromTime(t), 2);
1002            sb.append(' ')
1003                    .append(months[monthFromTime(t)])
1004                    .append(' ');
1005            zeroPad(sb, yearFromTime(t), 4);
1006            sb.append(' ');
1007            zeroPad(sb, hourFromTime(t), 2);
1008            sb.append(':');
1009            zeroPad(sb, minFromTime(t), 2);
1010            sb.append(':');
1011            zeroPad(sb, secFromTime(t), 2);
1012            sb.append(" GMT");
1013            return sb.toString();
1014        }
1015
1016        throw rangeError("invalid.date");
1017    }
1018
1019    private static String toISOStringImpl(final Object self) {
1020        final NativeDate nd = getNativeDate(self);
1021
1022        if (nd != null && nd.isValidDate()) {
1023            final StringBuilder sb = new StringBuilder(24);
1024            final double t = nd.getTime();
1025            // yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
1026            zeroPad(sb, yearFromTime(t), 4);
1027            sb.append('-');
1028            zeroPad(sb, monthFromTime(t) + 1, 2);
1029            sb.append('-');
1030            zeroPad(sb, dayFromTime(t), 2);
1031            sb.append('T');
1032            zeroPad(sb, hourFromTime(t), 2);
1033            sb.append(':');
1034            zeroPad(sb, minFromTime(t), 2);
1035            sb.append(':');
1036            zeroPad(sb, secFromTime(t), 2);
1037            sb.append('.');
1038            zeroPad(sb, msFromTime(t), 3);
1039            sb.append("Z");
1040            return sb.toString();
1041        }
1042
1043        throw rangeError("invalid.date");
1044    }
1045
1046    // ECMA 15.9.1.2 Day (t)
1047    private static double day(final double t) {
1048        return Math.floor(t / msPerDay);
1049    }
1050
1051    // ECMA 15.9.1.2 TimeWithinDay (t)
1052    private static double timeWithinDay(final double t) {
1053        final double val = t % msPerDay;
1054        return val < 0? val + msPerDay : val;
1055    }
1056
1057    // ECMA 15.9.1.3 InLeapYear (t)
1058    private static boolean isLeapYear(final int y) {
1059        return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
1060    }
1061
1062    // ECMA 15.9.1.3 DaysInYear (y)
1063    private static int daysInYear(final int y) {
1064        return isLeapYear(y) ? 366 : 365;
1065    }
1066
1067    // ECMA 15.9.1.3 DayFromYear (y)
1068    private static double dayFromYear(final double y) {
1069        return 365 * (y - 1970)
1070                + Math.floor((y -1969) / 4.0)
1071                - Math.floor((y - 1901) / 100.0)
1072                + Math.floor((y - 1601) / 400.0);
1073    }
1074
1075    // ECMA 15.9.1.3 Year Number
1076    private static double timeFromYear(final int y) {
1077        return dayFromYear(y) * msPerDay;
1078    }
1079
1080    // ECMA 15.9.1.3 Year Number
1081    private static int yearFromTime(final double t) {
1082        int y = (int) Math.floor(t / (msPerDay * 365.2425)) + 1970;
1083        final double t2 = timeFromYear(y);
1084        if (t2 > t) {
1085            y--;
1086        } else if (t2 + msPerDay * daysInYear(y) <= t) {
1087            y++;
1088        }
1089        return y;
1090    }
1091
1092    private static int dayWithinYear(final double t, final int year) {
1093        return (int) (day(t) - dayFromYear(year));
1094    }
1095
1096    private static int monthFromTime(final double t) {
1097        final int year = yearFromTime(t);
1098        final int day = dayWithinYear(t, year);
1099        final int[] firstDay = firstDayInMonth[isLeapYear(year) ? 1 : 0];
1100        int month = 0;
1101
1102        while (month < 11 && firstDay[month + 1] <= day) {
1103            month++;
1104        }
1105        return month;
1106    }
1107
1108    private static int dayFromTime(final double t)  {
1109        final int year = yearFromTime(t);
1110        final int day = dayWithinYear(t, year);
1111        final int[] firstDay = firstDayInMonth[isLeapYear(year) ? 1 : 0];
1112        int month = 0;
1113
1114        while (month < 11 && firstDay[month + 1] <= day) {
1115            month++;
1116        }
1117        return 1 + day - firstDay[month];
1118    }
1119
1120    private static int dayFromMonth(final int month, final int year) {
1121        assert(month >= 0 && month <= 11);
1122        final int[] firstDay = firstDayInMonth[isLeapYear(year) ? 1 : 0];
1123        return firstDay[month];
1124    }
1125
1126    private static int weekDay(final double time) {
1127        final int day = (int) (day(time) + 4) % 7;
1128        return day < 0 ? day + 7 : day;
1129    }
1130
1131    // ECMA 15.9.1.9 LocalTime
1132    private static double localTime(final double time, final TimeZone tz) {
1133        return time + tz.getOffset((long) time);
1134    }
1135
1136    // ECMA 15.9.1.9 UTC
1137    private static double utc(final double time, final TimeZone tz) {
1138        return time - tz.getOffset((long) (time - tz.getRawOffset()));
1139    }
1140
1141    // ECMA 15.9.1.10 Hours, Minutes, Second, and Milliseconds
1142    private static int hourFromTime(final double t) {
1143        final int h = (int) (Math.floor(t / msPerHour) % hoursPerDay);
1144        return h < 0 ? h + hoursPerDay: h;
1145    }
1146    private static int minFromTime(final double t) {
1147        final int m = (int) (Math.floor(t / msPerMinute) % minutesPerHour);
1148        return m < 0 ? m + minutesPerHour : m;
1149    }
1150
1151    private static int secFromTime(final double t) {
1152        final int s = (int) (Math.floor(t / msPerSecond) % secondsPerMinute);
1153        return s < 0 ? s + secondsPerMinute : s;
1154    }
1155
1156    private static int msFromTime(final double t) {
1157        final int m = (int) (t % msPerSecond);
1158        return m < 0 ? m + msPerSecond : m;
1159    }
1160
1161    private static int valueFromTime(final int unit, final double t) {
1162        switch (unit) {
1163            case YEAR: return yearFromTime(t);
1164            case MONTH: return monthFromTime(t);
1165            case DAY: return dayFromTime(t);
1166            case HOUR: return hourFromTime(t);
1167            case MINUTE: return minFromTime(t);
1168            case SECOND: return secFromTime(t);
1169            case MILLISECOND: return msFromTime(t);
1170            default: throw new IllegalArgumentException(Integer.toString(unit));
1171        }
1172    }
1173
1174    // ECMA 15.9.1.11 MakeTime (hour, min, sec, ms)
1175    private static double makeTime(final double hour, final double min, final double sec, final double ms) {
1176        return hour * 3600000 + min * 60000 + sec * 1000 + ms;
1177    }
1178
1179    // ECMA 15.9.1.12 MakeDay (year, month, date)
1180    private static double makeDay(final double year, final double month, final double date) {
1181        final double y = year + Math.floor(month / 12);
1182        int m = (int) (month % 12);
1183        if (m < 0) {
1184            m += 12;
1185        }
1186        double d = dayFromYear(y);
1187        d += dayFromMonth(m, (int) y);
1188
1189        return d + date - 1;
1190    }
1191
1192    // ECMA 15.9.1.13 MakeDate (day, time)
1193    private static double makeDate(final double day, final double time) {
1194        return day * msPerDay + time;
1195    }
1196
1197
1198    private static double makeDate(final Integer[] d) {
1199        final double time = makeDay(d[0], d[1], d[2]) * msPerDay;
1200        return time + makeTime(d[3], d[4], d[5], d[6]);
1201    }
1202
1203    private static double makeDate(final double[] d) {
1204        final double time = makeDay(d[0], d[1], d[2]) * msPerDay;
1205        return time + makeTime(d[3], d[4], d[5], d[6]);
1206    }
1207
1208    // Convert Date constructor args, checking for NaN, filling in defaults etc.
1209    private static double[] convertCtorArgs(final Object[] args) {
1210        final double[] d = new double[7];
1211        boolean nullReturn = false;
1212
1213        // should not bailout on first NaN or infinite. Need to convert all
1214        // subsequent args for possible side-effects via valueOf/toString overrides
1215        // on argument objects.
1216        for (int i = 0; i < d.length; i++) {
1217            if (i < args.length) {
1218                final double darg = JSType.toNumber(args[i]);
1219                if (isNaN(darg) || isInfinite(darg)) {
1220                    nullReturn = true;
1221                }
1222
1223                d[i] = (long)darg;
1224            } else {
1225                d[i] = i == 2 ? 1 : 0; // day in month defaults to 1
1226            }
1227        }
1228
1229        if (0 <= d[0] && d[0] <= 99) {
1230            d[0] += 1900;
1231        }
1232
1233        return nullReturn? null : d;
1234    }
1235
1236    // This method does the hard work for all setter methods: If a value is provided
1237    // as argument it is used, otherwise the value is calculated from the existing time value.
1238    private static double[] convertArgs(final Object[] args, final double time, final int fieldId, final int start, final int length) {
1239        final double[] d = new double[length];
1240        boolean nullReturn = false;
1241
1242        // Need to call toNumber on all args for side-effects - even if an argument
1243        // fails to convert to number, subsequent toNumber calls needed for possible
1244        // side-effects via valueOf/toString overrides.
1245        for (int i = start; i < start + length; i++) {
1246            if (fieldId <= i && i < fieldId + args.length) {
1247                final double darg = JSType.toNumber(args[i - fieldId]);
1248                if (isNaN(darg) || isInfinite(darg)) {
1249                    nullReturn = true;
1250                }
1251
1252                d[i - start] = (long) darg;
1253            } else {
1254                // Date.prototype.set* methods require first argument to be defined
1255                if (i == fieldId) {
1256                    nullReturn = true;
1257                }
1258
1259                if (!nullReturn && !isNaN(time)) {
1260                    d[i - start] = valueFromTime(i, time);
1261                }
1262            }
1263        }
1264
1265        return nullReturn ? null : d;
1266    }
1267
1268    // ECMA 15.9.1.14 TimeClip (time)
1269    private static double timeClip(final double time) {
1270        if (isInfinite(time) || isNaN(time) || Math.abs(time) > 8.64e15) {
1271            return Double.NaN;
1272        }
1273        return (long)time;
1274    }
1275
1276    private static NativeDate ensureNativeDate(final Object self) {
1277        return getNativeDate(self);
1278    }
1279
1280    private static NativeDate getNativeDate(final Object self) {
1281        if (self instanceof NativeDate) {
1282            return (NativeDate)self;
1283        } else if (self != null && self == Global.instance().getDatePrototype()) {
1284            return Global.instance().getDefaultDate();
1285        } else {
1286            throw typeError("not.a.date", ScriptRuntime.safeToString(self));
1287        }
1288    }
1289
1290    private static double getField(final Object self, final int field) {
1291        final NativeDate nd = getNativeDate(self);
1292        return (nd != null && nd.isValidDate()) ? (double)valueFromTime(field, nd.getLocalTime()) : Double.NaN;
1293    }
1294
1295    private static double getUTCField(final Object self, final int field) {
1296        final NativeDate nd = getNativeDate(self);
1297        return (nd != null && nd.isValidDate()) ? (double)valueFromTime(field, nd.getTime()) : Double.NaN;
1298    }
1299
1300    private static void setFields(final NativeDate nd, final int fieldId, final Object[] args, final boolean local) {
1301        int start, length;
1302        if (fieldId < HOUR) {
1303            start = YEAR;
1304            length = 3;
1305        } else {
1306            start = HOUR;
1307            length = 4;
1308        }
1309        final double time = local ? nd.getLocalTime() : nd.getTime();
1310        final double d[] = convertArgs(args, time, fieldId, start, length);
1311
1312        if (! nd.isValidDate()) {
1313            return;
1314        }
1315
1316        double newTime;
1317        if (d == null) {
1318            newTime = NaN;
1319        } else {
1320            if (start == YEAR) {
1321                newTime = makeDate(makeDay(d[0], d[1], d[2]), timeWithinDay(time));
1322            } else {
1323                newTime = makeDate(day(time), makeTime(d[0], d[1], d[2], d[3]));
1324            }
1325            if (local) {
1326                newTime = utc(newTime, nd.getTimeZone());
1327            }
1328            newTime = timeClip(newTime);
1329        }
1330        nd.setTime(newTime);
1331    }
1332
1333    private boolean isValidDate() {
1334        return !isNaN(time);
1335    }
1336
1337    private double getLocalTime() {
1338        return localTime(time, timezone);
1339    }
1340
1341    private double getTime() {
1342        return time;
1343    }
1344
1345    private void setTime(final double time) {
1346        this.time = time;
1347    }
1348
1349    private TimeZone getTimeZone() {
1350        return timezone;
1351    }
1352}
1353