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