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