• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
15 namespace v8 {
16 namespace internal {
17 
18 static const int kDaysIn4Years = 4 * 365 + 1;
19 static const int kDaysIn100Years = 25 * kDaysIn4Years - 1;
20 static const int kDaysIn400Years = 4 * kDaysIn100Years + 1;
21 static const int kDays1970to2000 = 30 * 365 + 7;
22 static const int kDaysOffset =
23     1000 * kDaysIn400Years + 5 * kDaysIn400Years - kDays1970to2000;
24 static const int kYearsOffset = 400000;
25 static const char kDaysInMonths[] = {31, 28, 31, 30, 31, 30,
26                                      31, 31, 30, 31, 30, 31};
27 
DateCache()28 DateCache::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 
ResetDateCache(base::TimezoneCache::TimeZoneDetection time_zone_detection)40 void 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)
TimeClip(double time)68 double 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 
ClearSegment(DST * segment)75 void 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 
YearMonthDayFromDays(int days,int * year,int * month,int * day)82 void 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 
DaysFromYearMonth(int year,int month)161 int 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 
BreakDownTime(int64_t time_ms,int * year,int * month,int * day,int * weekday,int * hour,int * min,int * sec,int * ms)200 void 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
GetLocalOffsetFromOS(int64_t time_ms,bool is_utc)215 int 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 
ExtendTheAfterSegment(int time_sec,int offset_ms)278 void 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 
DaylightSavingsOffsetInMs(int64_t time_ms)297 int 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 
ProbeDST(int time_sec)407 void 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 
LeastRecentlyUsedDST(DST * skip)447 DateCache::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 
459 namespace {
460 
461 // ES6 section 20.3.1.1 Time Values and Time Range
462 const double kMinYear = -1000000.0;
463 const double kMaxYear = -kMinYear;
464 const double kMinMonth = -10000000.0;
465 const double kMaxMonth = -kMinMonth;
466 
467 const double kMsPerDay = 86400000.0;
468 
469 const double kMsPerSecond = 1000.0;
470 const double kMsPerMinute = 60000.0;
471 const double kMsPerHour = 3600000.0;
472 
473 }  // namespace
474 
MakeDate(double day,double time)475 double 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 
MakeDay(double year,double month,double date)482 double 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 
MakeTime(double hour,double min,double sec,double ms)525 double 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 
537 namespace {
538 
539 const char* kShortWeekDays[] = {"Sun", "Mon", "Tue", "Wed",
540                                 "Thu", "Fri", "Sat"};
541 const char* kShortMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
542                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
543 
544 template <class... Args>
FormatDate(const char * format,Args...args)545 DateBuffer 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 
ToDateString(double time_val,DateCache * date_cache,ToDateStringMode mode)556 DateBuffer 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