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