xref: /third_party/node/deps/v8/src/date/date.cc (revision 1cb0ef41)
1// Copyright 2012 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/date/date.h"
6
7#include "src/base/overflowing-math.h"
8#include "src/numbers/conversions.h"
9#include "src/objects/objects-inl.h"
10#ifdef V8_INTL_SUPPORT
11#include "src/objects/intl-objects.h"
12#endif
13#include "src/strings/string-stream.h"
14
15namespace v8 {
16namespace internal {
17
18static const int kDaysIn4Years = 4 * 365 + 1;
19static const int kDaysIn100Years = 25 * kDaysIn4Years - 1;
20static const int kDaysIn400Years = 4 * kDaysIn100Years + 1;
21static const int kDays1970to2000 = 30 * 365 + 7;
22static const int kDaysOffset =
23    1000 * kDaysIn400Years + 5 * kDaysIn400Years - kDays1970to2000;
24static const int kYearsOffset = 400000;
25static const char kDaysInMonths[] = {31, 28, 31, 30, 31, 30,
26                                     31, 31, 30, 31, 30, 31};
27
28DateCache::DateCache()
29    : stamp_(kNullAddress),
30      tz_cache_(
31#ifdef V8_INTL_SUPPORT
32          Intl::CreateTimeZoneCache()
33#else
34          base::OS::CreateTimezoneCache()
35#endif
36      ) {
37  ResetDateCache(base::TimezoneCache::TimeZoneDetection::kSkip);
38}
39
40void DateCache::ResetDateCache(
41    base::TimezoneCache::TimeZoneDetection time_zone_detection) {
42  if (stamp_.value() >= Smi::kMaxValue) {
43    stamp_ = Smi::zero();
44  } else {
45    stamp_ = Smi::FromInt(stamp_.value() + 1);
46  }
47  DCHECK(stamp_ != Smi::FromInt(kInvalidStamp));
48  for (int i = 0; i < kDSTSize; ++i) {
49    ClearSegment(&dst_[i]);
50  }
51  dst_usage_counter_ = 0;
52  before_ = &dst_[0];
53  after_ = &dst_[1];
54  ymd_valid_ = false;
55#ifdef V8_INTL_SUPPORT
56  if (!FLAG_icu_timezone_data) {
57#endif
58    local_offset_ms_ = kInvalidLocalOffsetInMs;
59#ifdef V8_INTL_SUPPORT
60  }
61#endif
62  tz_cache_->Clear(time_zone_detection);
63  tz_name_ = nullptr;
64  dst_tz_name_ = nullptr;
65}
66
67// ECMA 262 - ES#sec-timeclip TimeClip (time)
68double DateCache::TimeClip(double time) {
69  if (-kMaxTimeInMs <= time && time <= kMaxTimeInMs) {
70    return DoubleToInteger(time);
71  }
72  return std::numeric_limits<double>::quiet_NaN();
73}
74
75void DateCache::ClearSegment(DST* segment) {
76  segment->start_sec = kMaxEpochTimeInSec;
77  segment->end_sec = -kMaxEpochTimeInSec;
78  segment->offset_ms = 0;
79  segment->last_used = 0;
80}
81
82void DateCache::YearMonthDayFromDays(int days, int* year, int* month,
83                                     int* day) {
84  if (ymd_valid_) {
85    // Check conservatively if the given 'days' has
86    // the same year and month as the cached 'days'.
87    int new_day = ymd_day_ + (days - ymd_days_);
88    if (new_day >= 1 && new_day <= 28) {
89      ymd_day_ = new_day;
90      ymd_days_ = days;
91      *year = ymd_year_;
92      *month = ymd_month_;
93      *day = new_day;
94      return;
95    }
96  }
97  int save_days = days;
98
99  days += kDaysOffset;
100  *year = 400 * (days / kDaysIn400Years) - kYearsOffset;
101  days %= kDaysIn400Years;
102
103  DCHECK_EQ(save_days, DaysFromYearMonth(*year, 0) + days);
104
105  days--;
106  int yd1 = days / kDaysIn100Years;
107  days %= kDaysIn100Years;
108  *year += 100 * yd1;
109
110  days++;
111  int yd2 = days / kDaysIn4Years;
112  days %= kDaysIn4Years;
113  *year += 4 * yd2;
114
115  days--;
116  int yd3 = days / 365;
117  days %= 365;
118  *year += yd3;
119
120  bool is_leap = (!yd1 || yd2) && !yd3;
121
122  DCHECK_GE(days, -1);
123  DCHECK(is_leap || (days >= 0));
124  DCHECK((days < 365) || (is_leap && (days < 366)));
125  DCHECK(is_leap == ((*year % 4 == 0) && (*year % 100 || (*year % 400 == 0))));
126  DCHECK(is_leap || ((DaysFromYearMonth(*year, 0) + days) == save_days));
127  DCHECK(!is_leap || ((DaysFromYearMonth(*year, 0) + days + 1) == save_days));
128
129  days += is_leap;
130
131  // Check if the date is after February.
132  if (days >= 31 + 28 + (is_leap ? 1 : 0)) {
133    days -= 31 + 28 + (is_leap ? 1 : 0);
134    // Find the date starting from March.
135    for (int i = 2; i < 12; i++) {
136      if (days < kDaysInMonths[i]) {
137        *month = i;
138        *day = days + 1;
139        break;
140      }
141      days -= kDaysInMonths[i];
142    }
143  } else {
144    // Check January and February.
145    if (days < 31) {
146      *month = 0;
147      *day = days + 1;
148    } else {
149      *month = 1;
150      *day = days - 31 + 1;
151    }
152  }
153  DCHECK(DaysFromYearMonth(*year, *month) + *day - 1 == save_days);
154  ymd_valid_ = true;
155  ymd_year_ = *year;
156  ymd_month_ = *month;
157  ymd_day_ = *day;
158  ymd_days_ = save_days;
159}
160
161int DateCache::DaysFromYearMonth(int year, int month) {
162  static const int day_from_month[] = {0,   31,  59,  90,  120, 151,
163                                       181, 212, 243, 273, 304, 334};
164  static const int day_from_month_leap[] = {0,   31,  60,  91,  121, 152,
165                                            182, 213, 244, 274, 305, 335};
166
167  year += month / 12;
168  month %= 12;
169  if (month < 0) {
170    year--;
171    month += 12;
172  }
173
174  DCHECK_GE(month, 0);
175  DCHECK_LT(month, 12);
176
177  // year_delta is an arbitrary number such that:
178  // a) year_delta = -1 (mod 400)
179  // b) year + year_delta > 0 for years in the range defined by
180  //    ECMA 262 - 15.9.1.1, i.e. upto 100,000,000 days on either side of
181  //    Jan 1 1970. This is required so that we don't run into integer
182  //    division of negative numbers.
183  // c) there shouldn't be an overflow for 32-bit integers in the following
184  //    operations.
185  static const int year_delta = 399999;
186  static const int base_day =
187      365 * (1970 + year_delta) + (1970 + year_delta) / 4 -
188      (1970 + year_delta) / 100 + (1970 + year_delta) / 400;
189
190  int year1 = year + year_delta;
191  int day_from_year =
192      365 * year1 + year1 / 4 - year1 / 100 + year1 / 400 - base_day;
193
194  if ((year % 4 != 0) || (year % 100 == 0 && year % 400 != 0)) {
195    return day_from_year + day_from_month[month];
196  }
197  return day_from_year + day_from_month_leap[month];
198}
199
200void DateCache::BreakDownTime(int64_t time_ms, int* year, int* month, int* day,
201                              int* weekday, int* hour, int* min, int* sec,
202                              int* ms) {
203  int const days = DaysFromTime(time_ms);
204  int const time_in_day_ms = TimeInDay(time_ms, days);
205  YearMonthDayFromDays(days, year, month, day);
206  *weekday = Weekday(days);
207  *hour = time_in_day_ms / (60 * 60 * 1000);
208  *min = (time_in_day_ms / (60 * 1000)) % 60;
209  *sec = (time_in_day_ms / 1000) % 60;
210  *ms = time_in_day_ms % 1000;
211}
212
213// Implements LocalTimeZonedjustment(t, isUTC)
214// ECMA 262 - ES#sec-local-time-zone-adjustment
215int DateCache::GetLocalOffsetFromOS(int64_t time_ms, bool is_utc) {
216  double offset;
217#ifdef V8_INTL_SUPPORT
218  if (FLAG_icu_timezone_data) {
219    offset = tz_cache_->LocalTimeOffset(static_cast<double>(time_ms), is_utc);
220  } else {
221#endif
222    // When ICU timezone data is not used, we need to compute the timezone
223    // offset for a given local time.
224    //
225    // The following shows that using DST for (t - LocalTZA - hour) produces
226    // correct conversion where LocalTZA is the timezone offset in winter (no
227    // DST) and the timezone offset is assumed to have no historical change.
228    // Note that it does not work for the past and the future if LocalTZA (no
229    // DST) is different from the current LocalTZA (no DST). For instance,
230    // this will break for Europe/Moscow in 2012 ~ 2013 because LocalTZA was
231    // 4h instead of the current 3h (as of 2018).
232    //
233    // Consider transition to DST at local time L1.
234    // Let L0 = L1 - hour, L2 = L1 + hour,
235    //     U1 = UTC time that corresponds to L1,
236    //     U0 = U1 - hour.
237    // Transitioning to DST moves local clock one hour forward L1 => L2, so
238    // U0 = UTC time that corresponds to L0 = L0 - LocalTZA,
239    // U1 = UTC time that corresponds to L1 = L1 - LocalTZA,
240    // U1 = UTC time that corresponds to L2 = L2 - LocalTZA - hour.
241    // Note that DST(U0 - hour) = 0, DST(U0) = 0, DST(U1) = 1.
242    // U0 = L0 - LocalTZA - DST(L0 - LocalTZA - hour),
243    // U1 = L1 - LocalTZA - DST(L1 - LocalTZA - hour),
244    // U1 = L2 - LocalTZA - DST(L2 - LocalTZA - hour).
245    //
246    // Consider transition from DST at local time L1.
247    // Let L0 = L1 - hour,
248    //     U1 = UTC time that corresponds to L1,
249    //     U0 = U1 - hour, U2 = U1 + hour.
250    // Transitioning from DST moves local clock one hour back L1 => L0, so
251    // U0 = UTC time that corresponds to L0 (before transition)
252    //    = L0 - LocalTZA - hour.
253    // U1 = UTC time that corresponds to L0 (after transition)
254    //    = L0 - LocalTZA = L1 - LocalTZA - hour
255    // U2 = UTC time that corresponds to L1 = L1 - LocalTZA.
256    // Note that DST(U0) = 1, DST(U1) = 0, DST(U2) = 0.
257    // U0 = L0 - LocalTZA - DST(L0 - LocalTZA - hour) = L0 - LocalTZA - DST(U0).
258    // U2 = L1 - LocalTZA - DST(L1 - LocalTZA - hour) = L1 - LocalTZA - DST(U1).
259    // It is impossible to get U1 from local time.
260    if (local_offset_ms_ == kInvalidLocalOffsetInMs) {
261      // This gets the constant LocalTZA (arguments are ignored).
262      local_offset_ms_ =
263          tz_cache_->LocalTimeOffset(static_cast<double>(time_ms), is_utc);
264    }
265    offset = local_offset_ms_;
266    if (!is_utc) {
267      const int kMsPerHour = 3600 * 1000;
268      time_ms -= (offset + kMsPerHour);
269    }
270    offset += DaylightSavingsOffsetInMs(time_ms);
271#ifdef V8_INTL_SUPPORT
272  }
273#endif
274  DCHECK_LT(offset, kInvalidLocalOffsetInMs);
275  return static_cast<int>(offset);
276}
277
278void DateCache::ExtendTheAfterSegment(int time_sec, int offset_ms) {
279  if (after_->offset_ms == offset_ms &&
280      after_->start_sec - kDefaultDSTDeltaInSec <= time_sec &&
281      time_sec <= after_->end_sec) {
282    // Extend the after_ segment.
283    after_->start_sec = time_sec;
284  } else {
285    // The after_ segment is either invalid or starts too late.
286    if (!InvalidSegment(after_)) {
287      // If the after_ segment is valid, replace it with a new segment.
288      after_ = LeastRecentlyUsedDST(before_);
289    }
290    after_->start_sec = time_sec;
291    after_->end_sec = time_sec;
292    after_->offset_ms = offset_ms;
293    after_->last_used = ++dst_usage_counter_;
294  }
295}
296
297int DateCache::DaylightSavingsOffsetInMs(int64_t time_ms) {
298  int time_sec = (time_ms >= 0 && time_ms <= kMaxEpochTimeInMs)
299                     ? static_cast<int>(time_ms / 1000)
300                     : static_cast<int>(EquivalentTime(time_ms) / 1000);
301
302  // Invalidate cache if the usage counter is close to overflow.
303  // Note that dst_usage_counter is incremented less than ten times
304  // in this function.
305  if (dst_usage_counter_ >= kMaxInt - 10) {
306    dst_usage_counter_ = 0;
307    for (int i = 0; i < kDSTSize; ++i) {
308      ClearSegment(&dst_[i]);
309    }
310  }
311
312  // Optimistic fast check.
313  if (before_->start_sec <= time_sec && time_sec <= before_->end_sec) {
314    // Cache hit.
315    before_->last_used = ++dst_usage_counter_;
316    return before_->offset_ms;
317  }
318
319  ProbeDST(time_sec);
320
321  DCHECK(InvalidSegment(before_) || before_->start_sec <= time_sec);
322  DCHECK(InvalidSegment(after_) || time_sec < after_->start_sec);
323
324  if (InvalidSegment(before_)) {
325    // Cache miss.
326    before_->start_sec = time_sec;
327    before_->end_sec = time_sec;
328    before_->offset_ms = GetDaylightSavingsOffsetFromOS(time_sec);
329    before_->last_used = ++dst_usage_counter_;
330    return before_->offset_ms;
331  }
332
333  if (time_sec <= before_->end_sec) {
334    // Cache hit.
335    before_->last_used = ++dst_usage_counter_;
336    return before_->offset_ms;
337  }
338
339  if (time_sec - kDefaultDSTDeltaInSec > before_->end_sec) {
340    // If the before_ segment ends too early, then just
341    // query for the offset of the time_sec
342    int offset_ms = GetDaylightSavingsOffsetFromOS(time_sec);
343    ExtendTheAfterSegment(time_sec, offset_ms);
344    // This swap helps the optimistic fast check in subsequent invocations.
345    DST* temp = before_;
346    before_ = after_;
347    after_ = temp;
348    return offset_ms;
349  }
350
351  // Now the time_sec is between
352  // before_->end_sec and before_->end_sec + default DST delta.
353  // Update the usage counter of before_ since it is going to be used.
354  before_->last_used = ++dst_usage_counter_;
355
356  // Check if after_ segment is invalid or starts too late.
357  // Note that start_sec of invalid segments is kMaxEpochTimeInSec.
358  int new_after_start_sec =
359      before_->end_sec < kMaxEpochTimeInSec - kDefaultDSTDeltaInSec
360          ? before_->end_sec + kDefaultDSTDeltaInSec
361          : kMaxEpochTimeInSec;
362  if (new_after_start_sec <= after_->start_sec) {
363    int new_offset_ms = GetDaylightSavingsOffsetFromOS(new_after_start_sec);
364    ExtendTheAfterSegment(new_after_start_sec, new_offset_ms);
365  } else {
366    DCHECK(!InvalidSegment(after_));
367    // Update the usage counter of after_ since it is going to be used.
368    after_->last_used = ++dst_usage_counter_;
369  }
370
371  // Now the time_sec is between before_->end_sec and after_->start_sec.
372  // Only one daylight savings offset change can occur in this interval.
373
374  if (before_->offset_ms == after_->offset_ms) {
375    // Merge two segments if they have the same offset.
376    before_->end_sec = after_->end_sec;
377    ClearSegment(after_);
378    return before_->offset_ms;
379  }
380
381  // Binary search for daylight savings offset change point,
382  // but give up if we don't find it in five iterations.
383  for (int i = 4; i >= 0; --i) {
384    int delta = after_->start_sec - before_->end_sec;
385    int middle_sec = (i == 0) ? time_sec : before_->end_sec + delta / 2;
386    int offset_ms = GetDaylightSavingsOffsetFromOS(middle_sec);
387    if (before_->offset_ms == offset_ms) {
388      before_->end_sec = middle_sec;
389      if (time_sec <= before_->end_sec) {
390        return offset_ms;
391      }
392    } else {
393      DCHECK(after_->offset_ms == offset_ms);
394      after_->start_sec = middle_sec;
395      if (time_sec >= after_->start_sec) {
396        // This swap helps the optimistic fast check in subsequent invocations.
397        DST* temp = before_;
398        before_ = after_;
399        after_ = temp;
400        return offset_ms;
401      }
402    }
403  }
404  return 0;
405}
406
407void DateCache::ProbeDST(int time_sec) {
408  DST* before = nullptr;
409  DST* after = nullptr;
410  DCHECK(before_ != after_);
411
412  for (int i = 0; i < kDSTSize; ++i) {
413    if (dst_[i].start_sec <= time_sec) {
414      if (before == nullptr || before->start_sec < dst_[i].start_sec) {
415        before = &dst_[i];
416      }
417    } else if (time_sec < dst_[i].end_sec) {
418      if (after == nullptr || after->end_sec > dst_[i].end_sec) {
419        after = &dst_[i];
420      }
421    }
422  }
423
424  // If before or after segments were not found,
425  // then set them to any invalid segment.
426  if (before == nullptr) {
427    before = InvalidSegment(before_) ? before_ : LeastRecentlyUsedDST(after);
428  }
429  if (after == nullptr) {
430    after = InvalidSegment(after_) && before != after_
431                ? after_
432                : LeastRecentlyUsedDST(before);
433  }
434
435  DCHECK_NOT_NULL(before);
436  DCHECK_NOT_NULL(after);
437  DCHECK(before != after);
438  DCHECK(InvalidSegment(before) || before->start_sec <= time_sec);
439  DCHECK(InvalidSegment(after) || time_sec < after->start_sec);
440  DCHECK(InvalidSegment(before) || InvalidSegment(after) ||
441         before->end_sec < after->start_sec);
442
443  before_ = before;
444  after_ = after;
445}
446
447DateCache::DST* DateCache::LeastRecentlyUsedDST(DST* skip) {
448  DST* result = nullptr;
449  for (int i = 0; i < kDSTSize; ++i) {
450    if (&dst_[i] == skip) continue;
451    if (result == nullptr || result->last_used > dst_[i].last_used) {
452      result = &dst_[i];
453    }
454  }
455  ClearSegment(result);
456  return result;
457}
458
459namespace {
460
461// ES6 section 20.3.1.1 Time Values and Time Range
462const double kMinYear = -1000000.0;
463const double kMaxYear = -kMinYear;
464const double kMinMonth = -10000000.0;
465const double kMaxMonth = -kMinMonth;
466
467const double kMsPerDay = 86400000.0;
468
469const double kMsPerSecond = 1000.0;
470const double kMsPerMinute = 60000.0;
471const double kMsPerHour = 3600000.0;
472
473}  // namespace
474
475double MakeDate(double day, double time) {
476  if (std::isfinite(day) && std::isfinite(time)) {
477    return time + day * kMsPerDay;
478  }
479  return std::numeric_limits<double>::quiet_NaN();
480}
481
482double MakeDay(double year, double month, double date) {
483  if ((kMinYear <= year && year <= kMaxYear) &&
484      (kMinMonth <= month && month <= kMaxMonth) && std::isfinite(date)) {
485    int y = FastD2I(year);
486    int m = FastD2I(month);
487    y += m / 12;
488    m %= 12;
489    if (m < 0) {
490      m += 12;
491      y -= 1;
492    }
493    DCHECK_LE(0, m);
494    DCHECK_LT(m, 12);
495
496    // kYearDelta is an arbitrary number such that:
497    // a) kYearDelta = -1 (mod 400)
498    // b) year + kYearDelta > 0 for years in the range defined by
499    //    ECMA 262 - 15.9.1.1, i.e. upto 100,000,000 days on either side of
500    //    Jan 1 1970. This is required so that we don't run into integer
501    //    division of negative numbers.
502    // c) there shouldn't be an overflow for 32-bit integers in the following
503    //    operations.
504    static const int kYearDelta = 399999;
505    static const int kBaseDay =
506        365 * (1970 + kYearDelta) + (1970 + kYearDelta) / 4 -
507        (1970 + kYearDelta) / 100 + (1970 + kYearDelta) / 400;
508    int day_from_year = 365 * (y + kYearDelta) + (y + kYearDelta) / 4 -
509                        (y + kYearDelta) / 100 + (y + kYearDelta) / 400 -
510                        kBaseDay;
511    if ((y % 4 != 0) || (y % 100 == 0 && y % 400 != 0)) {
512      static const int kDayFromMonth[] = {0,   31,  59,  90,  120, 151,
513                                          181, 212, 243, 273, 304, 334};
514      day_from_year += kDayFromMonth[m];
515    } else {
516      static const int kDayFromMonth[] = {0,   31,  60,  91,  121, 152,
517                                          182, 213, 244, 274, 305, 335};
518      day_from_year += kDayFromMonth[m];
519    }
520    return static_cast<double>(day_from_year - 1) + DoubleToInteger(date);
521  }
522  return std::numeric_limits<double>::quiet_NaN();
523}
524
525double MakeTime(double hour, double min, double sec, double ms) {
526  if (std::isfinite(hour) && std::isfinite(min) && std::isfinite(sec) &&
527      std::isfinite(ms)) {
528    double const h = DoubleToInteger(hour);
529    double const m = DoubleToInteger(min);
530    double const s = DoubleToInteger(sec);
531    double const milli = DoubleToInteger(ms);
532    return h * kMsPerHour + m * kMsPerMinute + s * kMsPerSecond + milli;
533  }
534  return std::numeric_limits<double>::quiet_NaN();
535}
536
537namespace {
538
539const char* kShortWeekDays[] = {"Sun", "Mon", "Tue", "Wed",
540                                "Thu", "Fri", "Sat"};
541const char* kShortMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
542                              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
543
544template <class... Args>
545DateBuffer FormatDate(const char* format, Args... args) {
546  DateBuffer buffer;
547  SmallStringOptimizedAllocator<DateBuffer::kInlineSize> allocator(&buffer);
548  StringStream sstream(&allocator);
549  sstream.Add(format, args...);
550  buffer.resize_no_init(sstream.length());
551  return buffer;
552}
553
554}  // namespace
555
556DateBuffer ToDateString(double time_val, DateCache* date_cache,
557                        ToDateStringMode mode) {
558  if (std::isnan(time_val)) {
559    return FormatDate("Invalid Date");
560  }
561  int64_t time_ms = static_cast<int64_t>(time_val);
562  int64_t local_time_ms = mode != ToDateStringMode::kUTCDateAndTime
563                              ? date_cache->ToLocal(time_ms)
564                              : time_ms;
565  int year, month, day, weekday, hour, min, sec, ms;
566  date_cache->BreakDownTime(local_time_ms, &year, &month, &day, &weekday, &hour,
567                            &min, &sec, &ms);
568  int timezone_offset = -date_cache->TimezoneOffset(time_ms);
569  int timezone_hour = std::abs(timezone_offset) / 60;
570  int timezone_min = std::abs(timezone_offset) % 60;
571  const char* local_timezone = date_cache->LocalTimezone(time_ms);
572  switch (mode) {
573    case ToDateStringMode::kLocalDate:
574      return FormatDate((year < 0) ? "%s %s %02d %05d" : "%s %s %02d %04d",
575                        kShortWeekDays[weekday], kShortMonths[month], day,
576                        year);
577    case ToDateStringMode::kLocalTime:
578      return FormatDate("%02d:%02d:%02d GMT%c%02d%02d (%s)", hour, min, sec,
579                        (timezone_offset < 0) ? '-' : '+', timezone_hour,
580                        timezone_min, local_timezone);
581    case ToDateStringMode::kLocalDateAndTime:
582      return FormatDate(
583          (year < 0) ? "%s %s %02d %05d %02d:%02d:%02d GMT%c%02d%02d (%s)"
584                     : "%s %s %02d %04d %02d:%02d:%02d GMT%c%02d%02d (%s)",
585          kShortWeekDays[weekday], kShortMonths[month], day, year, hour, min,
586          sec, (timezone_offset < 0) ? '-' : '+', timezone_hour, timezone_min,
587          local_timezone);
588    case ToDateStringMode::kUTCDateAndTime:
589      return FormatDate((year < 0) ? "%s, %02d %s %05d %02d:%02d:%02d GMT"
590                                   : "%s, %02d %s %04d %02d:%02d:%02d GMT",
591                        kShortWeekDays[weekday], day, kShortMonths[month], year,
592                        hour, min, sec);
593  }
594  UNREACHABLE();
595}
596
597}  // namespace internal
598}  // namespace v8
599