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.SpecializedFunction;
42 import jdk.nashorn.internal.objects.annotations.Where;
43 import jdk.nashorn.internal.parser.DateParser;
44 import jdk.nashorn.internal.runtime.JSType;
45 import jdk.nashorn.internal.runtime.PropertyMap;
46 import jdk.nashorn.internal.runtime.ScriptEnvironment;
47 import jdk.nashorn.internal.runtime.ScriptObject;
48 import jdk.nashorn.internal.runtime.ScriptRuntime;
49 import jdk.nashorn.internal.runtime.linker.Bootstrap;
50 import jdk.nashorn.internal.runtime.linker.InvokeByName;
51
52 /**
53 * ECMA 15.9 Date Objects
54 *
55 */
56 @ScriptClass("Date")
57 public 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 (JSType.isString(arg)) {
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 final double val = t % msPerDay;
1049 return val < 0? val + msPerDay : val;
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 }
--- EOF ---