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