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