1// Copyright 2006-2008 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6//     * Redistributions of source code must retain the above copyright
7//       notice, this list of conditions and the following disclaimer.
8//     * Redistributions in binary form must reproduce the above
9//       copyright notice, this list of conditions and the following
10//       disclaimer in the documentation and/or other materials provided
11//       with the distribution.
12//     * Neither the name of Google Inc. nor the names of its
13//       contributors may be used to endorse or promote products derived
14//       from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
29// This file relies on the fact that the following declarations have been made
30// in v8natives.js:
31// const $isFinite = GlobalIsFinite;
32
33// -------------------------------------------------------------------
34
35// This file contains date support implemented in JavaScript.
36
37
38// Keep reference to original values of some global properties.  This
39// has the added benefit that the code in this file is isolated from
40// changes to these properties.
41const $Date = global.Date;
42
43// Helper function to throw error.
44function ThrowDateTypeError() {
45  throw new $TypeError('this is not a Date object.');
46}
47
48// ECMA 262 - 5.2
49function Modulo(value, remainder) {
50  var mod = value % remainder;
51  // Guard against returning -0.
52  if (mod == 0) return 0;
53  return mod >= 0 ? mod : mod + remainder;
54}
55
56
57function TimeWithinDay(time) {
58  return Modulo(time, msPerDay);
59}
60
61
62// ECMA 262 - 15.9.1.3
63function DaysInYear(year) {
64  if (year % 4 != 0) return 365;
65  if ((year % 100 == 0) && (year % 400 != 0)) return 365;
66  return 366;
67}
68
69
70function DayFromYear(year) {
71  return 365 * (year-1970)
72      + FLOOR((year-1969)/4)
73      - FLOOR((year-1901)/100)
74      + FLOOR((year-1601)/400);
75}
76
77
78function TimeFromYear(year) {
79  return msPerDay * DayFromYear(year);
80}
81
82
83function InLeapYear(time) {
84  return DaysInYear(YEAR_FROM_TIME(time)) == 366 ? 1 : 0;
85}
86
87
88function DayWithinYear(time) {
89  return DAY(time) - DayFromYear(YEAR_FROM_TIME(time));
90}
91
92
93// ECMA 262 - 15.9.1.9
94function EquivalentYear(year) {
95  // Returns an equivalent year in the range [2008-2035] matching
96  // - leap year.
97  // - week day of first day.
98  var time = TimeFromYear(year);
99  var recent_year = (InLeapYear(time) == 0 ? 1967 : 1956) +
100      (WeekDay(time) * 12) % 28;
101  // Find the year in the range 2008..2037 that is equivalent mod 28.
102  // Add 3*28 to give a positive argument to the modulus operator.
103  return 2008 + (recent_year + 3*28 - 2008) % 28;
104}
105
106
107function EquivalentTime(t) {
108  // The issue here is that some library calls don't work right for dates
109  // that cannot be represented using a non-negative signed 32 bit integer
110  // (measured in whole seconds based on the 1970 epoch).
111  // We solve this by mapping the time to a year with same leap-year-ness
112  // and same starting day for the year.  The ECMAscript specification says
113  // we must do this, but for compatibility with other browsers, we use
114  // the actual year if it is in the range 1970..2037
115  if (t >= 0 && t <= 2.1e12) return t;
116  var day = MakeDay(EquivalentYear(YEAR_FROM_TIME(t)), MONTH_FROM_TIME(t), DATE_FROM_TIME(t));
117  return TimeClip(MakeDate(day, TimeWithinDay(t)));
118}
119
120
121// local_time_offset is initialized when the DST_offset_cache is missed.
122// It must not be used until after a call to DaylightSavingsOffset().
123// In this way, only one check, for a DST cache miss, is needed.
124var local_time_offset;
125
126
127// Because computing the DST offset is an expensive operation,
128// we keep a cache of the last computed DST offset along with a time interval
129// where we know the cache is valid.
130// When the cache is valid, local_time_offset is also valid.
131var DST_offset_cache = {
132  // Cached DST offset.
133  offset: 0,
134  // Time interval where the cached offset is valid.
135  start: 0, end: -1,
136  // Size of next interval expansion.
137  increment: 0
138};
139
140
141// NOTE: The implementation relies on the fact that no time zones have
142// more than one daylight savings offset change per month.
143// If this function is called with NaN it returns NaN.
144function DaylightSavingsOffset(t) {
145  // Load the cache object from the builtins object.
146  var cache = DST_offset_cache;
147
148  // Cache the start and the end in local variables for fast access.
149  var start = cache.start;
150  var end = cache.end;
151
152  if (start <= t) {
153    // If the time fits in the cached interval, return the cached offset.
154    if (t <= end) return cache.offset;
155
156    // If the cache misses, the local_time_offset may not be initialized.
157    if (IS_UNDEFINED(local_time_offset)) {
158      local_time_offset = %DateLocalTimeOffset();
159    }
160
161    // Compute a possible new interval end.
162    var new_end = end + cache.increment;
163
164    if (t <= new_end) {
165      var end_offset = %DateDaylightSavingsOffset(EquivalentTime(new_end));
166      if (cache.offset == end_offset) {
167        // If the offset at the end of the new interval still matches
168        // the offset in the cache, we grow the cached time interval
169        // and return the offset.
170        cache.end = new_end;
171        cache.increment = msPerMonth;
172        return end_offset;
173      } else {
174        var offset = %DateDaylightSavingsOffset(EquivalentTime(t));
175        if (offset == end_offset) {
176          // The offset at the given time is equal to the offset at the
177          // new end of the interval, so that means that we've just skipped
178          // the point in time where the DST offset change occurred. Updated
179          // the interval to reflect this and reset the increment.
180          cache.start = t;
181          cache.end = new_end;
182          cache.increment = msPerMonth;
183        } else {
184          // The interval contains a DST offset change and the given time is
185          // before it. Adjust the increment to avoid a linear search for
186          // the offset change point and change the end of the interval.
187          cache.increment /= 3;
188          cache.end = t;
189        }
190        // Update the offset in the cache and return it.
191        cache.offset = offset;
192        return offset;
193      }
194    }
195  }
196
197  // If the cache misses, the local_time_offset may not be initialized.
198  if (IS_UNDEFINED(local_time_offset)) {
199    local_time_offset = %DateLocalTimeOffset();
200  }
201  // Compute the DST offset for the time and shrink the cache interval
202  // to only contain the time. This allows fast repeated DST offset
203  // computations for the same time.
204  var offset = %DateDaylightSavingsOffset(EquivalentTime(t));
205  cache.offset = offset;
206  cache.start = cache.end = t;
207  cache.increment = msPerMonth;
208  return offset;
209}
210
211
212var timezone_cache_time = $NaN;
213var timezone_cache_timezone;
214
215function LocalTimezone(t) {
216  if (NUMBER_IS_NAN(t)) return "";
217  if (t == timezone_cache_time) {
218    return timezone_cache_timezone;
219  }
220  var timezone = %DateLocalTimezone(EquivalentTime(t));
221  timezone_cache_time = t;
222  timezone_cache_timezone = timezone;
223  return timezone;
224}
225
226
227function WeekDay(time) {
228  return Modulo(DAY(time) + 4, 7);
229}
230
231
232function LocalTime(time) {
233  if (NUMBER_IS_NAN(time)) return time;
234  // DaylightSavingsOffset called before local_time_offset used.
235  return time + DaylightSavingsOffset(time) + local_time_offset;
236}
237
238function LocalTimeNoCheck(time) {
239  // Inline the DST offset cache checks for speed.
240  // The cache is hit, or DaylightSavingsOffset is called,
241  // before local_time_offset is used.
242  var cache = DST_offset_cache;
243  if (cache.start <= time && time <= cache.end) {
244    var dst_offset = cache.offset;
245  } else {
246    var dst_offset = DaylightSavingsOffset(time);
247  }
248  return time + local_time_offset + dst_offset;
249}
250
251
252function UTC(time) {
253  if (NUMBER_IS_NAN(time)) return time;
254  // local_time_offset is needed before the call to DaylightSavingsOffset,
255  // so it may be uninitialized.
256  if (IS_UNDEFINED(local_time_offset)) {
257    local_time_offset = %DateLocalTimeOffset();
258  }
259  var tmp = time - local_time_offset;
260  return tmp - DaylightSavingsOffset(tmp);
261}
262
263
264// ECMA 262 - 15.9.1.11
265function MakeTime(hour, min, sec, ms) {
266  if (!$isFinite(hour)) return $NaN;
267  if (!$isFinite(min)) return $NaN;
268  if (!$isFinite(sec)) return $NaN;
269  if (!$isFinite(ms)) return $NaN;
270  return TO_INTEGER(hour) * msPerHour
271      + TO_INTEGER(min) * msPerMinute
272      + TO_INTEGER(sec) * msPerSecond
273      + TO_INTEGER(ms);
274}
275
276
277// ECMA 262 - 15.9.1.12
278function TimeInYear(year) {
279  return DaysInYear(year) * msPerDay;
280}
281
282
283// Compute modified Julian day from year, month, date.
284function ToJulianDay(year, month, date) {
285  var jy = (month > 1) ? year : year - 1;
286  var jm = (month > 1) ? month + 2 : month + 14;
287  var ja = FLOOR(jy / 100);
288  return FLOOR(FLOOR(365.25*jy) + FLOOR(30.6001*jm) + date + 1720995) + 2 - ja + FLOOR(0.25*ja);
289}
290
291var four_year_cycle_table = CalculateDateTable();
292
293
294function CalculateDateTable() {
295  var month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
296  var four_year_cycle_table = new $Array(1461);
297
298  var cumulative = 0;
299  var position = 0;
300  var leap_position = 0;
301  for (var month = 0; month < 12; month++) {
302    var month_bits = month << kMonthShift;
303    var length = month_lengths[month];
304    for (var day = 1; day <= length; day++) {
305      four_year_cycle_table[leap_position] =
306        month_bits + day;
307      four_year_cycle_table[366 + position] =
308        (1 << kYearShift) + month_bits + day;
309      four_year_cycle_table[731 + position] =
310        (2 << kYearShift) + month_bits + day;
311      four_year_cycle_table[1096 + position] =
312        (3 << kYearShift) + month_bits + day;
313      leap_position++;
314      position++;
315    }
316    if (month == 1) {
317      four_year_cycle_table[leap_position++] = month_bits + 29;
318    }
319  }
320  return four_year_cycle_table;
321}
322
323
324// Constructor for creating objects holding year, month, and date.
325// Introduced to ensure the two return points in FromJulianDay match same map.
326function DayTriplet(year, month, date) {
327  this.year = year;
328  this.month = month;
329  this.date = date;
330}
331
332var julian_day_cache_triplet;
333var julian_day_cache_day = $NaN;
334
335// Compute year, month, and day from modified Julian day.
336// The missing days in 1582 are ignored for JavaScript compatibility.
337function FromJulianDay(julian) {
338  if (julian_day_cache_day == julian) {
339    return julian_day_cache_triplet;
340  }
341  var result;
342  // Avoid floating point and non-Smi maths in common case.  This is also a period of
343  // time where leap years are very regular.  The range is not too large to avoid overflow
344  // when doing the multiply-to-divide trick.
345  if (julian > kDayZeroInJulianDay &&
346      (julian - kDayZeroInJulianDay) < 40177) { // 1970 - 2080
347    var jsimple = (julian - kDayZeroInJulianDay) + 731; // Day 0 is 1st January 1968
348    var y = 1968;
349    // Divide by 1461 by multiplying with 22967 and shifting down by 25!
350    var after_1968 = (jsimple * 22967) >> 25;
351    y += after_1968 << 2;
352    jsimple -= 1461 * after_1968;
353    var four_year_cycle = four_year_cycle_table[jsimple];
354    result = new DayTriplet(y + (four_year_cycle >> kYearShift),
355                            (four_year_cycle & kMonthMask) >> kMonthShift,
356                            four_year_cycle & kDayMask);
357  } else {
358    var jalpha = FLOOR((julian - 1867216.25) / 36524.25);
359    var jb = julian + 1 + jalpha - FLOOR(0.25 * jalpha) + 1524;
360    var jc = FLOOR(6680.0 + ((jb-2439870) - 122.1)/365.25);
361    var jd = FLOOR(365 * jc + (0.25 * jc));
362    var je = FLOOR((jb - jd)/30.6001);
363    var m = je - 1;
364    if (m > 12) m -= 13;
365    var y = jc - 4715;
366    if (m > 2) { --y; --m; }
367    var d = jb - jd - FLOOR(30.6001 * je);
368    result = new DayTriplet(y, m, d);
369  }
370  julian_day_cache_day = julian;
371  julian_day_cache_triplet = result;
372  return result;
373}
374
375
376// Compute number of days given a year, month, date.
377// Note that month and date can lie outside the normal range.
378//   For example:
379//     MakeDay(2007, -4, 20) --> MakeDay(2006, 8, 20)
380//     MakeDay(2007, -33, 1) --> MakeDay(2004, 3, 1)
381//     MakeDay(2007, 14, -50) --> MakeDay(2007, 8, 11)
382function MakeDay(year, month, date) {
383  if (!$isFinite(year) || !$isFinite(month) || !$isFinite(date)) return $NaN;
384
385  // Conversion to integers.
386  year = TO_INTEGER(year);
387  month = TO_INTEGER(month);
388  date = TO_INTEGER(date);
389
390  // Overflow months into year.
391  year = year + FLOOR(month/12);
392  month = month % 12;
393  if (month < 0) {
394    month += 12;
395  }
396
397  // Return days relative to Jan 1 1970.
398  return ToJulianDay(year, month, date) - kDayZeroInJulianDay;
399}
400
401
402// ECMA 262 - 15.9.1.13
403function MakeDate(day, time) {
404  if (!$isFinite(day)) return $NaN;
405  if (!$isFinite(time)) return $NaN;
406  return day * msPerDay + time;
407}
408
409
410// ECMA 262 - 15.9.1.14
411function TimeClip(time) {
412  if (!$isFinite(time)) return $NaN;
413  if ($abs(time) > 8.64E15) return $NaN;
414  return TO_INTEGER(time);
415}
416
417
418// The Date cache is used to limit the cost of parsing the same Date
419// strings over and over again.
420var Date_cache = {
421  // Cached time value.
422  time: $NaN,
423  // Cached year when interpreting the time as a local time. Only
424  // valid when the time matches cached time.
425  year: $NaN,
426  // String input for which the cached time is valid.
427  string: null
428};
429
430
431%SetCode($Date, function(year, month, date, hours, minutes, seconds, ms) {
432  if (!%_IsConstructCall()) {
433    // ECMA 262 - 15.9.2
434    return (new $Date()).toString();
435  }
436
437  // ECMA 262 - 15.9.3
438  var argc = %_ArgumentsLength();
439  var value;
440  if (argc == 0) {
441    value = %DateCurrentTime();
442
443  } else if (argc == 1) {
444    if (IS_NUMBER(year)) {
445      value = TimeClip(year);
446
447    } else if (IS_STRING(year)) {
448      // Probe the Date cache. If we already have a time value for the
449      // given time, we re-use that instead of parsing the string again.
450      var cache = Date_cache;
451      if (cache.string === year) {
452        value = cache.time;
453      } else {
454        value = DateParse(year);
455        if (!NUMBER_IS_NAN(value)) {
456          cache.time = value;
457          cache.year = YEAR_FROM_TIME(LocalTimeNoCheck(value));
458          cache.string = year;
459        }
460      }
461
462    } else {
463      // According to ECMA 262, no hint should be given for this
464      // conversion. However, ToPrimitive defaults to STRING_HINT for
465      // Date objects which will lose precision when the Date
466      // constructor is called with another Date object as its
467      // argument. We therefore use NUMBER_HINT for the conversion,
468      // which is the default for everything else than Date objects.
469      // This makes us behave like KJS and SpiderMonkey.
470      var time = ToPrimitive(year, NUMBER_HINT);
471      value = IS_STRING(time) ? DateParse(time) : TimeClip(ToNumber(time));
472    }
473
474  } else {
475    year = ToNumber(year);
476    month = ToNumber(month);
477    date = argc > 2 ? ToNumber(date) : 1;
478    hours = argc > 3 ? ToNumber(hours) : 0;
479    minutes = argc > 4 ? ToNumber(minutes) : 0;
480    seconds = argc > 5 ? ToNumber(seconds) : 0;
481    ms = argc > 6 ? ToNumber(ms) : 0;
482    year = (!NUMBER_IS_NAN(year) && 0 <= TO_INTEGER(year) && TO_INTEGER(year) <= 99)
483        ? 1900 + TO_INTEGER(year) : year;
484    var day = MakeDay(year, month, date);
485    var time = MakeTime(hours, minutes, seconds, ms);
486    value = TimeClip(UTC(MakeDate(day, time)));
487  }
488  %_SetValueOf(this, value);
489});
490
491
492// Helper functions.
493function GetTimeFrom(aDate) {
494  return DATE_VALUE(aDate);
495}
496
497function GetMillisecondsFrom(aDate) {
498  var t = DATE_VALUE(aDate);
499  if (NUMBER_IS_NAN(t)) return t;
500  return MS_FROM_TIME(LocalTimeNoCheck(t));
501}
502
503
504function GetUTCMillisecondsFrom(aDate) {
505  var t = DATE_VALUE(aDate);
506  if (NUMBER_IS_NAN(t)) return t;
507  return MS_FROM_TIME(t);
508}
509
510
511function GetSecondsFrom(aDate) {
512  var t = DATE_VALUE(aDate);
513  if (NUMBER_IS_NAN(t)) return t;
514  return SEC_FROM_TIME(LocalTimeNoCheck(t));
515}
516
517
518function GetUTCSecondsFrom(aDate) {
519  var t = DATE_VALUE(aDate);
520  if (NUMBER_IS_NAN(t)) return t;
521  return SEC_FROM_TIME(t);
522}
523
524
525function GetMinutesFrom(aDate) {
526  var t = DATE_VALUE(aDate);
527  if (NUMBER_IS_NAN(t)) return t;
528  return MIN_FROM_TIME(LocalTimeNoCheck(t));
529}
530
531
532function GetUTCMinutesFrom(aDate) {
533  var t = DATE_VALUE(aDate);
534  if (NUMBER_IS_NAN(t)) return t;
535  return MIN_FROM_TIME(t);
536}
537
538
539function GetHoursFrom(aDate) {
540  var t = DATE_VALUE(aDate);
541  if (NUMBER_IS_NAN(t)) return t;
542  return HOUR_FROM_TIME(LocalTimeNoCheck(t));
543}
544
545
546function GetUTCHoursFrom(aDate) {
547  var t = DATE_VALUE(aDate);
548  if (NUMBER_IS_NAN(t)) return t;
549  return HOUR_FROM_TIME(t);
550}
551
552
553function GetFullYearFrom(aDate) {
554  var t = DATE_VALUE(aDate);
555  if (NUMBER_IS_NAN(t)) return t;
556  var cache = Date_cache;
557  if (cache.time === t) return cache.year;
558  return YEAR_FROM_TIME(LocalTimeNoCheck(t));
559}
560
561
562function GetUTCFullYearFrom(aDate) {
563  var t = DATE_VALUE(aDate);
564  if (NUMBER_IS_NAN(t)) return t;
565  return YEAR_FROM_TIME(t);
566}
567
568
569function GetMonthFrom(aDate) {
570  var t = DATE_VALUE(aDate);
571  if (NUMBER_IS_NAN(t)) return t;
572  return MONTH_FROM_TIME(LocalTimeNoCheck(t));
573}
574
575
576function GetUTCMonthFrom(aDate) {
577  var t = DATE_VALUE(aDate);
578  if (NUMBER_IS_NAN(t)) return t;
579  return MONTH_FROM_TIME(t);
580}
581
582
583function GetDateFrom(aDate) {
584  var t = DATE_VALUE(aDate);
585  if (NUMBER_IS_NAN(t)) return t;
586  return DATE_FROM_TIME(LocalTimeNoCheck(t));
587}
588
589
590function GetUTCDateFrom(aDate) {
591  var t = DATE_VALUE(aDate);
592  if (NUMBER_IS_NAN(t)) return t;
593  return DATE_FROM_TIME(t);
594}
595
596
597%FunctionSetPrototype($Date, new $Date($NaN));
598
599
600var WeekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
601var Months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
602
603
604function TwoDigitString(value) {
605  return value < 10 ? "0" + value : "" + value;
606}
607
608
609function DateString(time) {
610  var YMD = FromJulianDay(DAY(time) + kDayZeroInJulianDay);
611  return WeekDays[WeekDay(time)] + ' '
612      + Months[YMD.month] + ' '
613      + TwoDigitString(YMD.date) + ' '
614      + YMD.year;
615}
616
617
618var LongWeekDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
619var LongMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
620
621
622function LongDateString(time) {
623  var YMD = FromJulianDay(DAY(time) + kDayZeroInJulianDay);
624  return LongWeekDays[WeekDay(time)] + ', '
625      + LongMonths[YMD.month] + ' '
626      + TwoDigitString(YMD.date) + ', '
627      + YMD.year;
628}
629
630
631function TimeString(time) {
632  return TwoDigitString(HOUR_FROM_TIME(time)) + ':'
633      + TwoDigitString(MIN_FROM_TIME(time)) + ':'
634      + TwoDigitString(SEC_FROM_TIME(time));
635}
636
637
638function LocalTimezoneString(time) {
639  var old_timezone = timezone_cache_timezone;
640  var timezone = LocalTimezone(time);
641  if (old_timezone && timezone != old_timezone) {
642    // If the timezone string has changed from the one that we cached,
643    // the local time offset may now be wrong. So we need to update it
644    // and try again.
645    local_time_offset = %DateLocalTimeOffset();
646    // We also need to invalidate the DST cache as the new timezone may have
647    // different DST times.
648    var dst_cache = DST_offset_cache;
649    dst_cache.start = 0;
650    dst_cache.end = -1;
651  }
652
653  var timezoneOffset =
654      (DaylightSavingsOffset(time) + local_time_offset) / msPerMinute;
655  var sign = (timezoneOffset >= 0) ? 1 : -1;
656  var hours = FLOOR((sign * timezoneOffset)/60);
657  var min   = FLOOR((sign * timezoneOffset)%60);
658  var gmt = ' GMT' + ((sign == 1) ? '+' : '-') +
659      TwoDigitString(hours) + TwoDigitString(min);
660  return gmt + ' (' +  timezone + ')';
661}
662
663
664function DatePrintString(time) {
665  return DateString(time) + ' ' + TimeString(time);
666}
667
668// -------------------------------------------------------------------
669
670// Reused output buffer. Used when parsing date strings.
671var parse_buffer = $Array(7);
672
673// ECMA 262 - 15.9.4.2
674function DateParse(string) {
675  var arr = %DateParseString(ToString(string), parse_buffer);
676  if (IS_NULL(arr)) return $NaN;
677
678  var day = MakeDay(arr[0], arr[1], arr[2]);
679  var time = MakeTime(arr[3], arr[4], arr[5], 0);
680  var date = MakeDate(day, time);
681
682  if (IS_NULL(arr[6])) {
683    return TimeClip(UTC(date));
684  } else {
685    return TimeClip(date - arr[6] * 1000);
686  }
687}
688
689
690// ECMA 262 - 15.9.4.3
691function DateUTC(year, month, date, hours, minutes, seconds, ms) {
692  year = ToNumber(year);
693  month = ToNumber(month);
694  var argc = %_ArgumentsLength();
695  date = argc > 2 ? ToNumber(date) : 1;
696  hours = argc > 3 ? ToNumber(hours) : 0;
697  minutes = argc > 4 ? ToNumber(minutes) : 0;
698  seconds = argc > 5 ? ToNumber(seconds) : 0;
699  ms = argc > 6 ? ToNumber(ms) : 0;
700  year = (!NUMBER_IS_NAN(year) && 0 <= TO_INTEGER(year) && TO_INTEGER(year) <= 99)
701      ? 1900 + TO_INTEGER(year) : year;
702  var day = MakeDay(year, month, date);
703  var time = MakeTime(hours, minutes, seconds, ms);
704  return %_SetValueOf(this, TimeClip(MakeDate(day, time)));
705}
706
707
708// Mozilla-specific extension. Returns the number of milliseconds
709// elapsed since 1 January 1970 00:00:00 UTC.
710function DateNow() {
711  return %DateCurrentTime();
712}
713
714
715// ECMA 262 - 15.9.5.2
716function DateToString() {
717  var t = DATE_VALUE(this);
718  if (NUMBER_IS_NAN(t)) return kInvalidDate;
719  var time_zone_string = LocalTimezoneString(t);  // May update local offset.
720  return DatePrintString(LocalTimeNoCheck(t)) + time_zone_string;
721}
722
723
724// ECMA 262 - 15.9.5.3
725function DateToDateString() {
726  var t = DATE_VALUE(this);
727  if (NUMBER_IS_NAN(t)) return kInvalidDate;
728  return DateString(LocalTimeNoCheck(t));
729}
730
731
732// ECMA 262 - 15.9.5.4
733function DateToTimeString() {
734  var t = DATE_VALUE(this);
735  if (NUMBER_IS_NAN(t)) return kInvalidDate;
736  var time_zone_string = LocalTimezoneString(t);  // May update local offset.
737  return TimeString(LocalTimeNoCheck(t)) + time_zone_string;
738}
739
740
741// ECMA 262 - 15.9.5.5
742function DateToLocaleString() {
743  return DateToString.call(this);
744}
745
746
747// ECMA 262 - 15.9.5.6
748function DateToLocaleDateString() {
749  var t = DATE_VALUE(this);
750  if (NUMBER_IS_NAN(t)) return kInvalidDate;
751  return LongDateString(LocalTimeNoCheck(t));
752}
753
754
755// ECMA 262 - 15.9.5.7
756function DateToLocaleTimeString() {
757  var t = DATE_VALUE(this);
758  if (NUMBER_IS_NAN(t)) return kInvalidDate;
759  var lt = LocalTimeNoCheck(t);
760  return TimeString(lt);
761}
762
763
764// ECMA 262 - 15.9.5.8
765function DateValueOf() {
766  return DATE_VALUE(this);
767}
768
769
770// ECMA 262 - 15.9.5.9
771function DateGetTime() {
772  return DATE_VALUE(this);
773}
774
775
776// ECMA 262 - 15.9.5.10
777function DateGetFullYear() {
778  return GetFullYearFrom(this)
779}
780
781
782// ECMA 262 - 15.9.5.11
783function DateGetUTCFullYear() {
784  return GetUTCFullYearFrom(this)
785}
786
787
788// ECMA 262 - 15.9.5.12
789function DateGetMonth() {
790  return GetMonthFrom(this);
791}
792
793
794// ECMA 262 - 15.9.5.13
795function DateGetUTCMonth() {
796  return GetUTCMonthFrom(this);
797}
798
799
800// ECMA 262 - 15.9.5.14
801function DateGetDate() {
802  return GetDateFrom(this);
803}
804
805
806// ECMA 262 - 15.9.5.15
807function DateGetUTCDate() {
808  return GetUTCDateFrom(this);
809}
810
811
812// ECMA 262 - 15.9.5.16
813function DateGetDay() {
814  var t = %_ValueOf(this);
815  if (NUMBER_IS_NAN(t)) return t;
816  return WeekDay(LocalTimeNoCheck(t));
817}
818
819
820// ECMA 262 - 15.9.5.17
821function DateGetUTCDay() {
822  var t = %_ValueOf(this);
823  if (NUMBER_IS_NAN(t)) return t;
824  return WeekDay(t);
825}
826
827
828// ECMA 262 - 15.9.5.18
829function DateGetHours() {
830  return GetHoursFrom(this);
831}
832
833
834// ECMA 262 - 15.9.5.19
835function DateGetUTCHours() {
836  return GetUTCHoursFrom(this);
837}
838
839
840// ECMA 262 - 15.9.5.20
841function DateGetMinutes() {
842  return GetMinutesFrom(this);
843}
844
845
846// ECMA 262 - 15.9.5.21
847function DateGetUTCMinutes() {
848  return GetUTCMinutesFrom(this);
849}
850
851
852// ECMA 262 - 15.9.5.22
853function DateGetSeconds() {
854  return GetSecondsFrom(this);
855}
856
857
858// ECMA 262 - 15.9.5.23
859function DateGetUTCSeconds() {
860  return GetUTCSecondsFrom(this);
861}
862
863
864// ECMA 262 - 15.9.5.24
865function DateGetMilliseconds() {
866  return GetMillisecondsFrom(this);
867}
868
869
870// ECMA 262 - 15.9.5.25
871function DateGetUTCMilliseconds() {
872  return GetUTCMillisecondsFrom(this);
873}
874
875
876// ECMA 262 - 15.9.5.26
877function DateGetTimezoneOffset() {
878  var t = DATE_VALUE(this);
879  if (NUMBER_IS_NAN(t)) return t;
880  return (t - LocalTimeNoCheck(t)) / msPerMinute;
881}
882
883
884// ECMA 262 - 15.9.5.27
885function DateSetTime(ms) {
886  if (!IS_DATE(this)) ThrowDateTypeError();
887  return %_SetValueOf(this, TimeClip(ToNumber(ms)));
888}
889
890
891// ECMA 262 - 15.9.5.28
892function DateSetMilliseconds(ms) {
893  var t = LocalTime(DATE_VALUE(this));
894  ms = ToNumber(ms);
895  var time = MakeTime(HOUR_FROM_TIME(t), MIN_FROM_TIME(t), SEC_FROM_TIME(t), ms);
896  return %_SetValueOf(this, TimeClip(UTC(MakeDate(DAY(t), time))));
897}
898
899
900// ECMA 262 - 15.9.5.29
901function DateSetUTCMilliseconds(ms) {
902  var t = DATE_VALUE(this);
903  ms = ToNumber(ms);
904  var time = MakeTime(HOUR_FROM_TIME(t), MIN_FROM_TIME(t), SEC_FROM_TIME(t), ms);
905  return %_SetValueOf(this, TimeClip(MakeDate(DAY(t), time)));
906}
907
908
909// ECMA 262 - 15.9.5.30
910function DateSetSeconds(sec, ms) {
911  var t = LocalTime(DATE_VALUE(this));
912  sec = ToNumber(sec);
913  ms = %_ArgumentsLength() < 2 ? GetMillisecondsFrom(this) : ToNumber(ms);
914  var time = MakeTime(HOUR_FROM_TIME(t), MIN_FROM_TIME(t), sec, ms);
915  return %_SetValueOf(this, TimeClip(UTC(MakeDate(DAY(t), time))));
916}
917
918
919// ECMA 262 - 15.9.5.31
920function DateSetUTCSeconds(sec, ms) {
921  var t = DATE_VALUE(this);
922  sec = ToNumber(sec);
923  ms = %_ArgumentsLength() < 2 ? GetUTCMillisecondsFrom(this) : ToNumber(ms);
924  var time = MakeTime(HOUR_FROM_TIME(t), MIN_FROM_TIME(t), sec, ms);
925  return %_SetValueOf(this, TimeClip(MakeDate(DAY(t), time)));
926}
927
928
929// ECMA 262 - 15.9.5.33
930function DateSetMinutes(min, sec, ms) {
931  var t = LocalTime(DATE_VALUE(this));
932  min = ToNumber(min);
933  var argc = %_ArgumentsLength();
934  sec = argc < 2 ? GetSecondsFrom(this) : ToNumber(sec);
935  ms = argc < 3 ? GetMillisecondsFrom(this) : ToNumber(ms);
936  var time = MakeTime(HOUR_FROM_TIME(t), min, sec, ms);
937  return %_SetValueOf(this, TimeClip(UTC(MakeDate(DAY(t), time))));
938}
939
940
941// ECMA 262 - 15.9.5.34
942function DateSetUTCMinutes(min, sec, ms) {
943  var t = DATE_VALUE(this);
944  min = ToNumber(min);
945  var argc = %_ArgumentsLength();
946  sec = argc < 2 ? GetUTCSecondsFrom(this) : ToNumber(sec);
947  ms = argc < 3 ? GetUTCMillisecondsFrom(this) : ToNumber(ms);
948  var time = MakeTime(HOUR_FROM_TIME(t), min, sec, ms);
949  return %_SetValueOf(this, TimeClip(MakeDate(DAY(t), time)));
950}
951
952
953// ECMA 262 - 15.9.5.35
954function DateSetHours(hour, min, sec, ms) {
955  var t = LocalTime(DATE_VALUE(this));
956  hour = ToNumber(hour);
957  var argc = %_ArgumentsLength();
958  min = argc < 2 ? GetMinutesFrom(this) : ToNumber(min);
959  sec = argc < 3 ? GetSecondsFrom(this) : ToNumber(sec);
960  ms = argc < 4 ? GetMillisecondsFrom(this) : ToNumber(ms);
961  var time = MakeTime(hour, min, sec, ms);
962  return %_SetValueOf(this, TimeClip(UTC(MakeDate(DAY(t), time))));
963}
964
965
966// ECMA 262 - 15.9.5.34
967function DateSetUTCHours(hour, min, sec, ms) {
968  var t = DATE_VALUE(this);
969  hour = ToNumber(hour);
970  var argc = %_ArgumentsLength();
971  min = argc < 2 ? GetUTCMinutesFrom(this) : ToNumber(min);
972  sec = argc < 3 ? GetUTCSecondsFrom(this) : ToNumber(sec);
973  ms = argc < 4 ? GetUTCMillisecondsFrom(this) : ToNumber(ms);
974  var time = MakeTime(hour, min, sec, ms);
975  return %_SetValueOf(this, TimeClip(MakeDate(DAY(t), time)));
976}
977
978
979// ECMA 262 - 15.9.5.36
980function DateSetDate(date) {
981  var t = LocalTime(DATE_VALUE(this));
982  date = ToNumber(date);
983  var day = MakeDay(YEAR_FROM_TIME(t), MONTH_FROM_TIME(t), date);
984  return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t)))));
985}
986
987
988// ECMA 262 - 15.9.5.37
989function DateSetUTCDate(date) {
990  var t = DATE_VALUE(this);
991  date = ToNumber(date);
992  var day = MakeDay(YEAR_FROM_TIME(t), MONTH_FROM_TIME(t), date);
993  return %_SetValueOf(this, TimeClip(MakeDate(day, TimeWithinDay(t))));
994}
995
996
997// ECMA 262 - 15.9.5.38
998function DateSetMonth(month, date) {
999  var t = LocalTime(DATE_VALUE(this));
1000  month = ToNumber(month);
1001  date = %_ArgumentsLength() < 2 ? GetDateFrom(this) : ToNumber(date);
1002  var day = MakeDay(YEAR_FROM_TIME(t), month, date);
1003  return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t)))));
1004}
1005
1006
1007// ECMA 262 - 15.9.5.39
1008function DateSetUTCMonth(month, date) {
1009  var t = DATE_VALUE(this);
1010  month = ToNumber(month);
1011  date = %_ArgumentsLength() < 2 ? GetUTCDateFrom(this) : ToNumber(date);
1012  var day = MakeDay(YEAR_FROM_TIME(t), month, date);
1013  return %_SetValueOf(this, TimeClip(MakeDate(day, TimeWithinDay(t))));
1014}
1015
1016
1017// ECMA 262 - 15.9.5.40
1018function DateSetFullYear(year, month, date) {
1019  var t = DATE_VALUE(this);
1020  t = NUMBER_IS_NAN(t) ? 0 : LocalTimeNoCheck(t);
1021  year = ToNumber(year);
1022  var argc = %_ArgumentsLength();
1023  month = argc < 2 ? MONTH_FROM_TIME(t) : ToNumber(month);
1024  date = argc < 3 ? DATE_FROM_TIME(t) : ToNumber(date);
1025  var day = MakeDay(year, month, date);
1026  return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t)))));
1027}
1028
1029
1030// ECMA 262 - 15.9.5.41
1031function DateSetUTCFullYear(year, month, date) {
1032  var t = DATE_VALUE(this);
1033  if (NUMBER_IS_NAN(t)) t = 0;
1034  var argc = %_ArgumentsLength();
1035  year = ToNumber(year);
1036  month = argc < 2 ? MONTH_FROM_TIME(t) : ToNumber(month);
1037  date = argc < 3 ? DATE_FROM_TIME(t) : ToNumber(date);
1038  var day = MakeDay(year, month, date);
1039  return %_SetValueOf(this, TimeClip(MakeDate(day, TimeWithinDay(t))));
1040}
1041
1042
1043// ECMA 262 - 15.9.5.42
1044function DateToUTCString() {
1045  var t = DATE_VALUE(this);
1046  if (NUMBER_IS_NAN(t)) return kInvalidDate;
1047  // Return UTC string of the form: Sat, 31 Jan 1970 23:00:00 GMT
1048  return WeekDays[WeekDay(t)] + ', '
1049      + TwoDigitString(DATE_FROM_TIME(t)) + ' '
1050      + Months[MONTH_FROM_TIME(t)] + ' '
1051      + YEAR_FROM_TIME(t) + ' '
1052      + TimeString(t) + ' GMT';
1053}
1054
1055
1056// ECMA 262 - B.2.4
1057function DateGetYear() {
1058  var t = DATE_VALUE(this);
1059  if (NUMBER_IS_NAN(t)) return $NaN;
1060  return YEAR_FROM_TIME(LocalTimeNoCheck(t)) - 1900;
1061}
1062
1063
1064// ECMA 262 - B.2.5
1065function DateSetYear(year) {
1066  var t = LocalTime(DATE_VALUE(this));
1067  if (NUMBER_IS_NAN(t)) t = 0;
1068  year = ToNumber(year);
1069  if (NUMBER_IS_NAN(year)) return %_SetValueOf(this, $NaN);
1070  year = (0 <= TO_INTEGER(year) && TO_INTEGER(year) <= 99)
1071      ? 1900 + TO_INTEGER(year) : year;
1072  var day = MakeDay(year, MONTH_FROM_TIME(t), DATE_FROM_TIME(t));
1073  return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t)))));
1074}
1075
1076
1077// ECMA 262 - B.2.6
1078//
1079// Notice that this does not follow ECMA 262 completely.  ECMA 262
1080// says that toGMTString should be the same Function object as
1081// toUTCString.  JSC does not do this, so for compatibility we do not
1082// do that either.  Instead, we create a new function whose name
1083// property will return toGMTString.
1084function DateToGMTString() {
1085  return DateToUTCString.call(this);
1086}
1087
1088
1089function PadInt(n, digits) {
1090  if (digits == 1) return n;
1091  return n < MathPow(10, digits - 1) ? '0' + PadInt(n, digits - 1) : n;
1092}
1093
1094
1095function DateToISOString() {
1096  var t = DATE_VALUE(this);
1097  if (NUMBER_IS_NAN(t)) return kInvalidDate;
1098  return this.getUTCFullYear() + '-' + PadInt(this.getUTCMonth() + 1, 2) +
1099      '-' + PadInt(this.getUTCDate(), 2) + 'T' + PadInt(this.getUTCHours(), 2) +
1100      ':' + PadInt(this.getUTCMinutes(), 2) + ':' + PadInt(this.getUTCSeconds(), 2) +
1101      '.' + PadInt(this.getUTCMilliseconds(), 3) +
1102      'Z';
1103}
1104
1105
1106function DateToJSON(key) {
1107  return CheckJSONPrimitive(this.toISOString());
1108}
1109
1110
1111// -------------------------------------------------------------------
1112
1113function SetupDate() {
1114  // Setup non-enumerable properties of the Date object itself.
1115  InstallFunctions($Date, DONT_ENUM, $Array(
1116    "UTC", DateUTC,
1117    "parse", DateParse,
1118    "now", DateNow
1119  ));
1120
1121  // Setup non-enumerable constructor property of the Date prototype object.
1122  %SetProperty($Date.prototype, "constructor", $Date, DONT_ENUM);
1123
1124  // Setup non-enumerable functions of the Date prototype object and
1125  // set their names.
1126  InstallFunctionsOnHiddenPrototype($Date.prototype, DONT_ENUM, $Array(
1127    "toString", DateToString,
1128    "toDateString", DateToDateString,
1129    "toTimeString", DateToTimeString,
1130    "toLocaleString", DateToLocaleString,
1131    "toLocaleDateString", DateToLocaleDateString,
1132    "toLocaleTimeString", DateToLocaleTimeString,
1133    "valueOf", DateValueOf,
1134    "getTime", DateGetTime,
1135    "getFullYear", DateGetFullYear,
1136    "getUTCFullYear", DateGetUTCFullYear,
1137    "getMonth", DateGetMonth,
1138    "getUTCMonth", DateGetUTCMonth,
1139    "getDate", DateGetDate,
1140    "getUTCDate", DateGetUTCDate,
1141    "getDay", DateGetDay,
1142    "getUTCDay", DateGetUTCDay,
1143    "getHours", DateGetHours,
1144    "getUTCHours", DateGetUTCHours,
1145    "getMinutes", DateGetMinutes,
1146    "getUTCMinutes", DateGetUTCMinutes,
1147    "getSeconds", DateGetSeconds,
1148    "getUTCSeconds", DateGetUTCSeconds,
1149    "getMilliseconds", DateGetMilliseconds,
1150    "getUTCMilliseconds", DateGetUTCMilliseconds,
1151    "getTimezoneOffset", DateGetTimezoneOffset,
1152    "setTime", DateSetTime,
1153    "setMilliseconds", DateSetMilliseconds,
1154    "setUTCMilliseconds", DateSetUTCMilliseconds,
1155    "setSeconds", DateSetSeconds,
1156    "setUTCSeconds", DateSetUTCSeconds,
1157    "setMinutes", DateSetMinutes,
1158    "setUTCMinutes", DateSetUTCMinutes,
1159    "setHours", DateSetHours,
1160    "setUTCHours", DateSetUTCHours,
1161    "setDate", DateSetDate,
1162    "setUTCDate", DateSetUTCDate,
1163    "setMonth", DateSetMonth,
1164    "setUTCMonth", DateSetUTCMonth,
1165    "setFullYear", DateSetFullYear,
1166    "setUTCFullYear", DateSetUTCFullYear,
1167    "toGMTString", DateToGMTString,
1168    "toUTCString", DateToUTCString,
1169    "getYear", DateGetYear,
1170    "setYear", DateSetYear,
1171    "toISOString", DateToISOString,
1172    "toJSON", DateToJSON
1173  ));
1174}
1175
1176SetupDate();
1177