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