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