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 && nd.isValidDate()) { 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 NativeDate nd = getNativeDate(self); 539 final double num = timeClip(JSType.toNumber(time)); 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 setFields(nd, MILLISECOND, args, true); 555 return nd.getTime(); 556 } 557 558 /** 559 * ECMA 15.9.5.29 Date.prototype.setUTCMilliseconds (ms) 560 * 561 * @param self self reference 562 * @param args utc milliseconds 563 * @return time 564 */ 565 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 566 public static Object setUTCMilliseconds(final Object self, final Object... args) { 567 final NativeDate nd = getNativeDate(self); 568 setFields(nd, MILLISECOND, args, false); 569 return nd.getTime(); 570 } 571 572 /** 573 * ECMA 15.9.5.30 Date.prototype.setSeconds (sec [, ms ] ) 574 * 575 * @param self self reference 576 * @param args seconds (milliseconds optional second argument) 577 * @return time 578 */ 579 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) 580 public static Object setSeconds(final Object self, final Object... args) { 581 final NativeDate nd = getNativeDate(self); 582 setFields(nd, SECOND, args, true); 583 return nd.getTime(); 584 } 585 586 /** 587 * ECMA 15.9.5.31 Date.prototype.setUTCSeconds (sec [, ms ] ) 588 * 589 * @param self self reference 590 * @param args UTC seconds (milliseconds optional second argument) 591 * @return time 592 */ 593 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) 594 public static Object setUTCSeconds(final Object self, final Object... args) { 595 final NativeDate nd = getNativeDate(self); 596 setFields(nd, SECOND, args, false); 597 return nd.getTime(); 598 } 599 600 /** 601 * ECMA 15.9.5.32 Date.prototype.setMinutes (min [, sec [, ms ] ] ) 602 * 603 * @param self self reference 604 * @param args minutes (seconds and milliseconds are optional second and third arguments) 605 * @return time 606 */ 607 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3) 608 public static Object setMinutes(final Object self, final Object... args) { 609 final NativeDate nd = getNativeDate(self); 610 setFields(nd, MINUTE, args, true); 611 return nd.getTime(); 612 } 613 614 /** 615 * ECMA 15.9.5.33 Date.prototype.setUTCMinutes (min [, sec [, ms ] ] ) 616 * 617 * @param self self reference 618 * @param args minutes (seconds and milliseconds are optional second and third arguments) 619 * @return time 620 */ 621 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3) 622 public static Object setUTCMinutes(final Object self, final Object... args) { 623 final NativeDate nd = getNativeDate(self); 624 setFields(nd, MINUTE, args, false); 625 return nd.getTime(); 626 } 627 628 /** 629 * ECMA 15.9.5.34 Date.prototype.setHours (hour [, min [, sec [, ms ] ] ] ) 630 * 631 * @param self self reference 632 * @param args hour (optional arguments after are minutes, seconds, milliseconds) 633 * @return time 634 */ 635 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 4) 636 public static Object setHours(final Object self, final Object... args) { 637 final NativeDate nd = getNativeDate(self); 638 setFields(nd, HOUR, args, true); 639 return nd.getTime(); 640 } 641 642 /** 643 * ECMA 15.9.5.35 Date.prototype.setUTCHours (hour [, min [, sec [, ms ] ] ] ) 644 * 645 * @param self self reference 646 * @param args hour (optional arguments after are minutes, seconds, milliseconds) 647 * @return time 648 */ 649 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 4) 650 public static Object setUTCHours(final Object self, final Object... args) { 651 final NativeDate nd = getNativeDate(self); 652 setFields(nd, HOUR, args, false); 653 return nd.getTime(); 654 } 655 656 /** 657 * ECMA 15.9.5.36 Date.prototype.setDate (date) 658 * 659 * @param self self reference 660 * @param args date 661 * @return time 662 */ 663 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 664 public static Object setDate(final Object self, final Object... args) { 665 final NativeDate nd = getNativeDate(self); 666 setFields(nd, DAY, args, true); 667 return nd.getTime(); 668 } 669 670 /** 671 * ECMA 15.9.5.37 Date.prototype.setUTCDate (date) 672 * 673 * @param self self reference 674 * @param args UTC date 675 * @return time 676 */ 677 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 678 public static Object setUTCDate(final Object self, final Object... args) { 679 final NativeDate nd = getNativeDate(self); 680 setFields(nd, DAY, args, false); 681 return nd.getTime(); 682 } 683 684 /** 685 * ECMA 15.9.5.38 Date.prototype.setMonth (month [, date ] ) 686 * 687 * @param self self reference 688 * @param args month (optional second argument is date) 689 * @return time 690 */ 691 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) 692 public static Object setMonth(final Object self, final Object... args) { 693 final NativeDate nd = getNativeDate(self); 694 setFields(nd, MONTH, args, true); 695 return nd.getTime(); 696 } 697 698 /** 699 * ECMA 15.9.5.39 Date.prototype.setUTCMonth (month [, date ] ) 700 * 701 * @param self self reference 702 * @param args UTC month (optional second argument is date) 703 * @return time 704 */ 705 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) 706 public static Object setUTCMonth(final Object self, final Object... args) { 707 final NativeDate nd = ensureNativeDate(self); 708 setFields(nd, MONTH, args, false); 709 return nd.getTime(); 710 } 711 712 /** 713 * ECMA 15.9.5.40 Date.prototype.setFullYear (year [, month [, date ] ] ) 714 * 715 * @param self self reference 716 * @param args year (optional second and third arguments are month and date) 717 * @return time 718 */ 719 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3) 720 public static Object setFullYear(final Object self, final Object... args) { 721 final NativeDate nd = ensureNativeDate(self); 722 if (nd.isValidDate()) { 723 setFields(nd, YEAR, args, true); 724 } else { 725 final double[] d = convertArgs(args, 0, YEAR, YEAR, 3); 726 if (d != null) { 727 nd.setTime(timeClip(utc(makeDate(makeDay(d[0], d[1], d[2]), 0), nd.getTimeZone()))); 728 } else { 729 nd.setTime(NaN); 730 } 731 } 732 return nd.getTime(); 733 } 734 735 /** 736 * ECMA 15.9.5.41 Date.prototype.setUTCFullYear (year [, month [, date ] ] ) 737 * 738 * @param self self reference 739 * @param args UTC full year (optional second and third arguments are month and date) 740 * @return time 741 */ 742 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3) 743 public static Object setUTCFullYear(final Object self, final Object... args) { 744 final NativeDate nd = ensureNativeDate(self); 745 if (nd.isValidDate()) { 746 setFields(nd, YEAR, args, false); 747 } else { 748 final double[] d = convertArgs(args, 0, YEAR, YEAR, 3); 749 nd.setTime(timeClip(makeDate(makeDay(d[0], d[1], d[2]), 0))); 750 } 751 return nd.getTime(); 752 } 753 754 /** 755 * ECMA B.2.5 Date.prototype.setYear (year) 756 * 757 * @param self self reference 758 * @param year year 759 * @return NativeDate 760 */ 761 @Function(attributes = Attribute.NOT_ENUMERABLE) 762 public static Object setYear(final Object self, final Object year) { 763 final NativeDate nd = getNativeDate(self); 764 if (isNaN(nd.getTime())) { 765 nd.setTime(utc(0, nd.getTimeZone())); 766 } 767 768 final double yearNum = JSType.toNumber(year); 769 if (isNaN(yearNum)) { 770 nd.setTime(NaN); 771 return nd.getTime(); 772 } 773 int yearInt = JSType.toInteger(yearNum); 774 if (0 <= yearInt && yearInt <= 99) { 775 yearInt += 1900; 776 } 777 setFields(nd, YEAR, new Object[] {yearInt}, true); 778 779 return nd.getTime(); 780 } 781 782 /** 783 * ECMA 15.9.5.42 Date.prototype.toUTCString ( ) 784 * 785 * @param self self reference 786 * @return string representation of date 787 */ 788 @Function(attributes = Attribute.NOT_ENUMERABLE) 789 public static Object toUTCString(final Object self) { 790 return toGMTStringImpl(self); 791 } 792 793 /** 794 * ECMA B.2.6 Date.prototype.toGMTString ( ) 795 * 796 * See {@link NativeDate#toUTCString(Object)} 797 * 798 * @param self self reference 799 * @return string representation of date 800 */ 801 @Function(attributes = Attribute.NOT_ENUMERABLE) 802 public static Object toGMTString(final Object self) { 803 return toGMTStringImpl(self); 804 } 805 806 /** 807 * ECMA 15.9.5.43 Date.prototype.toISOString ( ) 808 * 809 * @param self self reference 810 * @return string representation of date 811 */ 812 @Function(attributes = Attribute.NOT_ENUMERABLE) 813 public static Object toISOString(final Object self) { 814 return toISOStringImpl(self); 815 } 816 817 /** 818 * ECMA 15.9.5.44 Date.prototype.toJSON ( key ) 819 * 820 * Provides a string representation of this Date for use by {@link NativeJSON#stringify(Object, Object, Object, Object)} 821 * 822 * @param self self reference 823 * @param key ignored 824 * @return JSON representation of this date 825 */ 826 @Function(attributes = Attribute.NOT_ENUMERABLE) 827 public static Object toJSON(final Object self, final Object key) { 828 // NOTE: Date.prototype.toJSON is generic. Accepts other objects as well. 829 final Object selfObj = Global.toObject(self); 830 if (!(selfObj instanceof ScriptObject)) { 831 return null; 832 } 833 final ScriptObject sobj = (ScriptObject)selfObj; 834 final Object value = sobj.getDefaultValue(Number.class); 835 if (value instanceof Number) { 836 final double num = ((Number)value).doubleValue(); 837 if (isInfinite(num) || isNaN(num)) { 838 return null; 839 } 840 } 841 842 try { 843 final Object func = TO_ISO_STRING.getGetter().invokeExact(sobj); 844 if (func instanceof ScriptFunction) { 845 return TO_ISO_STRING.getInvoker().invokeExact(func, sobj, key); 846 } 847 throw typeError("not.a.function", ScriptRuntime.safeToString(func)); 848 } catch (final RuntimeException | Error e) { 849 throw e; 850 } catch (final Throwable t) { 851 throw new RuntimeException(t); 852 } 853 } 854 855 // -- Internals below this point 856 857 private static double parseDateString(final String str) { 858 859 final DateParser parser = new DateParser(str); 860 if (parser.parse()) { 861 final Integer[] fields = parser.getDateFields(); 862 double d = makeDate(fields); 863 if (fields[DateParser.TIMEZONE] != null) { 864 d -= fields[DateParser.TIMEZONE] * 60000; 865 } else { 866 d = utc(d, Global.getEnv()._timezone); 867 } 868 d = timeClip(d); 869 return d; 870 } 871 872 return Double.NaN; 873 } 874 875 private static void zeroPad(final StringBuilder sb, final int n, final int length) { 876 for (int l = 1, d = 10; l < length; l++, d *= 10) { 877 if (n < d) { 878 sb.append('0'); 879 } 880 } 881 sb.append(n); 882 } 883 884 private static String toStringImpl(final Object self, final int format) { 885 final NativeDate nd = getNativeDate(self); 886 887 if (nd != null && nd.isValidDate()) { 888 final StringBuilder sb = new StringBuilder(40); 889 final double t = nd.getLocalTime(); 890 891 switch (format) { 892 893 case FORMAT_DATE_TIME: 894 case FORMAT_DATE : 895 case FORMAT_LOCAL_DATE_TIME: 896 // EEE MMM dd yyyy 897 sb.append(weekDays[(int) weekDay(t)]) 898 .append(' ') 899 .append(months[(int) monthFromTime(t)]) 900 .append(' '); 901 zeroPad(sb, (int) dayFromTime(t), 2); 902 sb.append(' '); 903 zeroPad(sb, (int) yearFromTime(t), 4); 904 if (format == FORMAT_DATE) { 905 break; 906 } 907 sb.append(' '); 908 909 //$FALL-THROUGH$ 910 case FORMAT_TIME: 911 final TimeZone tz = nd.getTimeZone(); 912 final double utcTime = nd.getTime(); 913 int offset = tz.getOffset((long) utcTime) / 60000; 914 final boolean inDaylightTime = offset != tz.getRawOffset() / 60000; 915 // Convert minutes to HHmm timezone offset 916 offset = (offset / 60) * 100 + offset % 60; 917 918 // HH:mm:ss GMT+HHmm 919 zeroPad(sb, (int) hourFromTime(t), 2); 920 sb.append(':'); 921 zeroPad(sb, (int) minFromTime(t), 2); 922 sb.append(':'); 923 zeroPad(sb, (int) secFromTime(t), 2); 924 sb.append(" GMT") 925 .append(offset < 0 ? '-' : '+'); 926 zeroPad(sb, Math.abs(offset), 4); 927 sb.append(" (") 928 .append(tz.getDisplayName(inDaylightTime, TimeZone.SHORT, Locale.US)) 929 .append(')'); 930 break; 931 932 case FORMAT_LOCAL_DATE: 933 // yyyy-MM-dd 934 zeroPad(sb, (int) yearFromTime(t), 4); 935 sb.append('-'); 936 zeroPad(sb, (int) monthFromTime(t) + 1, 2); 937 sb.append('-'); 938 zeroPad(sb, (int) dayFromTime(t), 2); 939 break; 940 941 case FORMAT_LOCAL_TIME: 942 // HH:mm:ss 943 zeroPad(sb, (int) hourFromTime(t), 2); 944 sb.append(':'); 945 zeroPad(sb, (int) minFromTime(t), 2); 946 sb.append(':'); 947 zeroPad(sb, (int) secFromTime(t), 2); 948 break; 949 950 default: 951 throw new IllegalArgumentException("format: " + format); 952 } 953 954 return sb.toString(); 955 } 956 957 return INVALID_DATE; 958 } 959 960 private static String toGMTStringImpl(final Object self) { 961 final NativeDate nd = getNativeDate(self); 962 963 if (nd != null && nd.isValidDate()) { 964 final StringBuilder sb = new StringBuilder(29); 965 final double t = nd.getTime(); 966 // EEE, dd MMM yyyy HH:mm:ss z 967 sb.append(weekDays[(int) weekDay(t)]) 968 .append(", "); 969 zeroPad(sb, (int) dayFromTime(t), 2); 970 sb.append(' ') 971 .append(months[(int) monthFromTime(t)]) 972 .append(' '); 973 zeroPad(sb, (int) yearFromTime(t), 4); 974 sb.append(' '); 975 zeroPad(sb, (int) hourFromTime(t), 2); 976 sb.append(':'); 977 zeroPad(sb, (int) minFromTime(t), 2); 978 sb.append(':'); 979 zeroPad(sb, (int) secFromTime(t), 2); 980 sb.append(" GMT"); 981 return sb.toString(); 982 } 983 984 throw rangeError("invalid.date"); 985 } 986 987 private static String toISOStringImpl(final Object self) { 988 final NativeDate nd = getNativeDate(self); 989 990 if (nd != null && nd.isValidDate()) { 991 final StringBuilder sb = new StringBuilder(24); 992 final double t = nd.getTime(); 993 // yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 994 zeroPad(sb, (int) yearFromTime(t), 4); 995 sb.append('-'); 996 zeroPad(sb, (int) monthFromTime(t) + 1, 2); 997 sb.append('-'); 998 zeroPad(sb, (int) dayFromTime(t), 2); 999 sb.append('T'); 1000 zeroPad(sb, (int) hourFromTime(t), 2); 1001 sb.append(':'); 1002 zeroPad(sb, (int) minFromTime(t), 2); 1003 sb.append(':'); 1004 zeroPad(sb, (int) secFromTime(t), 2); 1005 sb.append('.'); 1006 zeroPad(sb, (int) msFromTime(t), 3); 1007 sb.append("Z"); 1008 return sb.toString(); 1009 } 1010 1011 throw rangeError("invalid.date"); 1012 } 1013 1014 // ECMA 15.9.1.2 Day (t) 1015 private static double day(final double t) { 1016 return Math.floor(t / msPerDay); 1017 } 1018 1019 // ECMA 15.9.1.2 TimeWithinDay (t) 1020 private static double timeWithinDay(final double t) { 1021 return t % msPerDay; 1022 } 1023 1024 // ECMA 15.9.1.3 InLeapYear (t) 1025 private static boolean isLeapYear(final int y) { 1026 return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); 1027 } 1028 1029 // ECMA 15.9.1.3 DaysInYear (y) 1030 private static int daysInYear(final int y) { 1031 return isLeapYear(y) ? 366 : 365; 1032 } 1033 1034 // ECMA 15.9.1.3 DayFromYear (y) 1035 private static double dayFromYear(final double y) { 1036 return 365 * (y - 1970) 1037 + Math.floor((y -1969) / 4.0) 1038 - Math.floor((y - 1901) / 100.0) 1039 + Math.floor((y - 1601) / 400.0); 1040 } 1041 1042 // ECMA 15.9.1.3 Year Number 1043 private static double timeFromYear(final double y) { 1044 return dayFromYear(y) * msPerDay; 1045 } 1046 1047 private static double yearFromTime(final double t) { 1048 double y = Math.floor(t / (msPerDay * 365.2425)) + 1970; 1049 final double t2 = timeFromYear(y); 1050 if (t2 > t) { 1051 y--; 1052 } else if (t2 + msPerDay * daysInYear((int) y) <= t) { 1053 y++; 1054 } 1055 return y; 1056 } 1057 1058 private static double dayWithinYear(final double t, final double year) { 1059 return day(t) - dayFromYear(year); 1060 } 1061 1062 private static double monthFromTime(final double t) { 1063 final double year = yearFromTime(t); 1064 final double day = dayWithinYear(t, year); 1065 final int[] firstDay = firstDayInMonth[isLeapYear((int) year) ? 1 : 0]; 1066 int month = 0; 1067 1068 while (month < 11 && firstDay[month + 1] <= day) { 1069 month++; 1070 } 1071 return month; 1072 } 1073 1074 private static double dayFromTime(final double t) { 1075 final double year = yearFromTime(t); 1076 final double day = dayWithinYear(t, year); 1077 final int[] firstDay = firstDayInMonth[isLeapYear((int) year) ? 1 : 0]; 1078 int month = 0; 1079 1080 while (month < 11 && firstDay[month + 1] <= day) { 1081 month++; 1082 } 1083 return 1 + day - firstDay[month]; 1084 } 1085 1086 private static int dayFromMonth(final int month, final int year) { 1087 assert(month >= 0 && month <= 11); 1088 final int[] firstDay = firstDayInMonth[isLeapYear(year) ? 1 : 0]; 1089 return firstDay[month]; 1090 } 1091 1092 private static double weekDay(final double time) { 1093 if (isNaN(time)) { 1094 return NaN; 1095 } 1096 final double day = (day(time) + 4) % 7; 1097 return day < 0 ? day + 7 : day; 1098 } 1099 1100 // ECMA 15.9.1.9 LocalTime 1101 private static double localTime(final double time, final TimeZone tz) { 1102 return time + tz.getOffset((long) time); 1103 } 1104 1105 // ECMA 15.9.1.9 UTC 1106 private static double utc(final double time, final TimeZone tz) { 1107 return time - tz.getOffset((long) (time - tz.getRawOffset())); 1108 } 1109 1110 // ECMA 15.9.1.10 Hours, Minutes, Second, and Milliseconds 1111 private static double hourFromTime(final double t) { 1112 final double h = Math.floor(t / msPerHour) % hoursPerDay; 1113 return h < 0 ? h + hoursPerDay: h; 1114 } 1115 private static double minFromTime(final double t) { 1116 final double m = Math.floor(t / msPerMinute) % minutesPerHour; 1117 return m < 0 ? m + minutesPerHour : m; 1118 } 1119 1120 private static double secFromTime(final double t) { 1121 final double s = Math.floor(t / msPerSecond) % secondsPerMinute; 1122 return s < 0 ? s + secondsPerMinute : s; 1123 } 1124 1125 private static double msFromTime(final double t) { 1126 final double m = t % msPerSecond; 1127 return m < 0 ? m + msPerSecond : m; 1128 } 1129 1130 private static double valueFromTime(final int unit, final double t) { 1131 switch (unit) { 1132 case YEAR: return yearFromTime(t); 1133 case MONTH: return monthFromTime(t); 1134 case DAY: return dayFromTime(t); 1135 case HOUR: return hourFromTime(t); 1136 case MINUTE: return minFromTime(t); 1137 case SECOND: return secFromTime(t); 1138 case MILLISECOND: return msFromTime(t); 1139 default: throw new IllegalArgumentException(Integer.toString(unit)); 1140 } 1141 } 1142 1143 // ECMA 15.9.1.11 MakeTime (hour, min, sec, ms) 1144 private static double makeTime(final double hour, final double min, final double sec, final double ms) { 1145 return hour * 3600000 + min * 60000 + sec * 1000 + ms; 1146 } 1147 1148 // ECMA 15.9.1.12 MakeDay (year, month, date) 1149 private static double makeDay(final double year, final double month, final double date) { 1150 final double y = year + Math.floor(month / 12); 1151 double m = month % 12; 1152 if (m < 0) { 1153 m += 12; 1154 } 1155 double d = Math.floor(dayFromYear(y)); 1156 d += dayFromMonth((int) m, (int) y); 1157 1158 return d + date - 1; 1159 } 1160 1161 // ECMA 15.9.1.13 MakeDate (day, time) 1162 private static double makeDate(final double day, final double time) { 1163 return day * msPerDay + time; 1164 } 1165 1166 1167 private static double makeDate(final Integer[] d) { 1168 final double time = makeDay(d[0], d[1], d[2]) * msPerDay; 1169 return time + makeTime(d[3], d[4], d[5], d[6]); 1170 } 1171 1172 private static double makeDate(final double[] d) { 1173 final double time = makeDay(d[0], d[1], d[2]) * msPerDay; 1174 return time + makeTime(d[3], d[4], d[5], d[6]); 1175 } 1176 1177 // Convert Date constructor args, checking for NaN, filling in defaults etc. 1178 private static double[] convertCtorArgs(final Object[] args) { 1179 final double[] d = new double[7]; 1180 boolean nullReturn = false; 1181 1182 // should not bailout on first NaN or infinite. Need to convert all 1183 // subsequent args for possible side-effects via valueOf/toString overrides 1184 // on argument objects. 1185 for (int i = 0; i < d.length; i++) { 1186 if (i < args.length) { 1187 final double darg = JSType.toNumber(args[i]); 1188 if (isNaN(darg) || isInfinite(darg)) { 1189 nullReturn = true; 1190 } 1191 1192 d[i] = (long)darg; 1193 } else { 1194 d[i] = i == 2 ? 1 : 0; // day in month defaults to 1 1195 } 1196 } 1197 1198 if (0 <= d[0] && d[0] <= 99) { 1199 d[0] += 1900; 1200 } 1201 1202 return nullReturn? null : d; 1203 } 1204 1205 // This method does the hard work for all setter methods: If a value is provided 1206 // as argument it is used, otherwise the value is calculated from the existing time value. 1207 private static double[] convertArgs(final Object[] args, final double time, final int fieldId, final int start, final int length) { 1208 final double[] d = new double[length]; 1209 boolean nullReturn = false; 1210 1211 // Need to call toNumber on all args for side-effects - even if an argument 1212 // fails to convert to number, subsequent toNumber calls needed for possible 1213 // side-effects via valueOf/toString overrides. 1214 for (int i = start; i < start + length; i++) { 1215 if (fieldId <= i && i < fieldId + args.length) { 1216 final double darg = JSType.toNumber(args[i - fieldId]); 1217 if (isNaN(darg) || isInfinite(darg)) { 1218 nullReturn = true; 1219 } 1220 1221 d[i - start] = (long) darg; 1222 } else { 1223 // Date.prototype.set* methods require first argument to be defined 1224 if (i == fieldId) { 1225 nullReturn = true; 1226 } 1227 1228 if (! nullReturn) { 1229 d[i - start] = valueFromTime(i, time); 1230 } 1231 } 1232 } 1233 1234 return nullReturn? null : d; 1235 } 1236 1237 // ECMA 15.9.1.14 TimeClip (time) 1238 private static double timeClip(final double time) { 1239 if (isInfinite(time) || isNaN(time) || Math.abs(time) > 8.64e15) { 1240 return Double.NaN; 1241 } 1242 return (long)time; 1243 } 1244 1245 private static NativeDate ensureNativeDate(final Object self) { 1246 return getNativeDate(self); 1247 } 1248 1249 private static NativeDate getNativeDate(final Object self) { 1250 if (self instanceof NativeDate) { 1251 return (NativeDate)self; 1252 } else if (self != null && self == Global.instance().getDatePrototype()) { 1253 return Global.instance().DEFAULT_DATE; 1254 } else { 1255 throw typeError("not.a.date", ScriptRuntime.safeToString(self)); 1256 } 1257 } 1258 1259 private static Object getField(final Object self, final int field) { 1260 final NativeDate nd = getNativeDate(self); 1261 return (nd != null && nd.isValidDate()) ? valueFromTime(field, nd.getLocalTime()) : Double.NaN; 1262 } 1263 1264 private static Object getUTCField(final Object self, final int field) { 1265 final NativeDate nd = getNativeDate(self); 1266 return (nd != null && nd.isValidDate()) ? valueFromTime(field, nd.getTime()) : Double.NaN; 1267 } 1268 1269 private static void setFields(final NativeDate nd, final int fieldId, final Object[] args, final boolean local) { 1270 int start, length; 1271 if (fieldId < HOUR) { 1272 start = YEAR; 1273 length = 3; 1274 } else { 1275 start = HOUR; 1276 length = 4; 1277 } 1278 final double time = local ? nd.getLocalTime() : nd.getTime(); 1279 final double d[] = convertArgs(args, time, fieldId, start, length); 1280 1281 if (! nd.isValidDate()) { 1282 return; 1283 } 1284 1285 double newTime; 1286 if (d == null) { 1287 newTime = NaN; 1288 } else { 1289 if (start == YEAR) { 1290 newTime = makeDate(makeDay(d[0], d[1], d[2]), timeWithinDay(time)); 1291 } else { 1292 newTime = makeDate(day(time), makeTime(d[0], d[1], d[2], d[3])); 1293 } 1294 if (local) { 1295 newTime = utc(newTime, nd.getTimeZone()); 1296 } 1297 newTime = timeClip(newTime); 1298 } 1299 nd.setTime(newTime); 1300 } 1301 1302 private boolean isValidDate() { 1303 return !isNaN(time); 1304 } 1305 1306 private double getLocalTime() { 1307 return localTime(time, timezone); 1308 } 1309 1310 private double getTime() { 1311 return time; 1312 } 1313 1314 private void setTime(final double time) { 1315 this.time = time; 1316 } 1317 1318 private TimeZone getTimeZone() { 1319 return timezone; 1320 } 1321 1322 } --- EOF ---