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
26 package jdk.nashorn.internal.objects;
27
28 import static java.lang.Double.NaN;
29 import static java.lang.Double.isInfinite;
30 import static java.lang.Double.isNaN;
31 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
32 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
33
34 import java.util.Locale;
35 import java.util.TimeZone;
36 import java.util.concurrent.Callable;
37 import jdk.nashorn.internal.objects.annotations.Attribute;
38 import jdk.nashorn.internal.objects.annotations.Constructor;
39 import jdk.nashorn.internal.objects.annotations.Function;
40 import jdk.nashorn.internal.objects.annotations.ScriptClass;
41 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
42 import jdk.nashorn.internal.objects.annotations.Where;
43 import jdk.nashorn.internal.parser.DateParser;
44 import jdk.nashorn.internal.runtime.ConsString;
45 import jdk.nashorn.internal.runtime.JSType;
46 import jdk.nashorn.internal.runtime.PropertyMap;
47 import jdk.nashorn.internal.runtime.ScriptEnvironment;
48 import jdk.nashorn.internal.runtime.ScriptObject;
49 import jdk.nashorn.internal.runtime.ScriptRuntime;
50 import jdk.nashorn.internal.runtime.linker.Bootstrap;
51 import jdk.nashorn.internal.runtime.linker.InvokeByName;
52
53 /**
54 * ECMA 15.9 Date Objects
55 *
56 */
57 @ScriptClass("Date")
58 public 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 Object 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 Object 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 Object now(final Object self) {
257 return (double)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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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 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 //$FALL-THROUGH$
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 Object getField(final Object self, final int field) {
1286 final NativeDate nd = getNativeDate(self);
1287 return (nd != null && nd.isValidDate()) ? valueFromTime(field, nd.getLocalTime()) : Double.NaN;
1288 }
1289
1290 private static Object getUTCField(final Object self, final int field) {
1291 final NativeDate nd = getNativeDate(self);
1292 return (nd != null && nd.isValidDate()) ? 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 }
--- EOF ---