/* * Copyright (C) 2009 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "third_party/blink/renderer/platform/date_components.h" #include #include "third_party/blink/renderer/platform/wtf/ascii_ctype.h" #include "third_party/blink/renderer/platform/wtf/date_math.h" #include "third_party/blink/renderer/platform/wtf/math_extras.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" namespace blink { // HTML5 specification defines minimum week of year is one. const int DateComponents::kMinimumWeekNumber = 1; // HTML5 specification defines maximum week of year is 53. const int DateComponents::kMaximumWeekNumber = 53; static const int kMaximumMonthInMaximumYear = 8; // This is September, since months are 0 based. static const int kMaximumDayInMaximumMonth = 13; static const int kMaximumWeekInMaximumYear = 37; // The week of 275760-09-13 static const int kDaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 'month' is 0-based. static int MaxDayOfMonth(int year, int month) { if (month != 1) // February? return kDaysInMonth[month]; return IsLeapYear(year) ? 29 : 28; } // 'month' is 0-based. static int DayOfWeek(int year, int month, int day) { int shifted_month = month + 2; // 2:January, 3:Feburuary, 4:March, ... // Zeller's congruence if (shifted_month <= 3) { shifted_month += 12; year--; } // 4:March, ..., 14:January, 15:February int high_year = year / 100; int low_year = year % 100; // We add 6 to make the result Sunday-origin. int result = (day + 13 * shifted_month / 5 + low_year + low_year / 4 + high_year / 4 + 5 * high_year + 6) % 7; return result; } int DateComponents::WeekDay() const { return DayOfWeek(year_, month_, month_day_); } int DateComponents::MaxWeekNumberInYear() const { int day = DayOfWeek(year_, 0, 1); // January 1. return day == kThursday || (day == kWednesday && IsLeapYear(year_)) ? kMaximumWeekNumber : kMaximumWeekNumber - 1; } static unsigned CountDigits(const String& src, unsigned start) { unsigned index = start; for (; index < src.length(); ++index) { if (!IsASCIIDigit(src[index])) break; } return index - start; } // Very strict integer parser. Do not allow leading or trailing whitespace // unlike charactersToIntStrict(). static bool ToInt(const String& src, unsigned parse_start, unsigned parse_length, int& out) { if (parse_start + parse_length > src.length() || !parse_length) return false; int value = 0; unsigned current = parse_start; unsigned end = current + parse_length; // We don't need to handle negative numbers for ISO 8601. for (; current < end; ++current) { if (!IsASCIIDigit(src[current])) return false; int digit = src[current] - '0'; if (value > (INT_MAX - digit) / 10) // Check for overflow. return false; value = value * 10 + digit; } out = value; return true; } bool DateComponents::ParseYear(const String& src, unsigned start, unsigned& end) { unsigned digits_length = CountDigits(src, start); // Needs at least 4 digits according to the standard. if (digits_length < 4) return false; int year; if (!ToInt(src, start, digits_length, year)) return false; if (year < MinimumYear() || year > MaximumYear()) return false; year_ = year; end = start + digits_length; return true; } static bool WithinHTMLDateLimits(int year, int month) { if (year < DateComponents::MinimumYear()) return false; if (year < DateComponents::MaximumYear()) return true; return month <= kMaximumMonthInMaximumYear; } static bool WithinHTMLDateLimits(int year, int month, int month_day) { if (year < DateComponents::MinimumYear()) return false; if (year < DateComponents::MaximumYear()) return true; if (month < kMaximumMonthInMaximumYear) return true; return month_day <= kMaximumDayInMaximumMonth; } static bool WithinHTMLDateLimits(int year, int month, int month_day, int hour, int minute, int second, int millisecond) { if (year < DateComponents::MinimumYear()) return false; if (year < DateComponents::MaximumYear()) return true; if (month < kMaximumMonthInMaximumYear) return true; if (month_day < kMaximumDayInMaximumMonth) return true; if (month_day > kMaximumDayInMaximumMonth) return false; // (year, month, monthDay) = // (maximumYear, maximumMonthInMaximumYear, maximumDayInMaximumMonth) return !hour && !minute && !second && !millisecond; } bool DateComponents::AddDay(int day_diff) { DCHECK(month_day_); int day = month_day_ + day_diff; if (day > MaxDayOfMonth(year_, month_)) { day = month_day_; int year = year_; int month = month_; int max_day = MaxDayOfMonth(year, month); for (; day_diff > 0; --day_diff) { ++day; if (day > max_day) { day = 1; ++month; if (month >= 12) { // month is 0-origin. month = 0; ++year; } max_day = MaxDayOfMonth(year, month); } } if (!WithinHTMLDateLimits(year, month, day)) return false; year_ = year; month_ = month; } else if (day < 1) { int month = month_; int year = year_; day = month_day_; for (; day_diff < 0; ++day_diff) { --day; if (day < 1) { --month; if (month < 0) { month = 11; --year; } day = MaxDayOfMonth(year, month); } } if (!WithinHTMLDateLimits(year, month, day)) return false; year_ = year; month_ = month; } else { if (!WithinHTMLDateLimits(year_, month_, day)) return false; } month_day_ = day; return true; } bool DateComponents::AddMinute(int minute) { // This function is used to adjust timezone offset. So m_year, m_month, // m_monthDay have values between the lower and higher limits. DCHECK(WithinHTMLDateLimits(year_, month_, month_day_)); int carry; // minute can be negative or greater than 59. minute += minute_; if (minute > 59) { carry = minute / 60; minute = minute % 60; } else if (minute < 0) { carry = (59 - minute) / 60; minute += carry * 60; carry = -carry; DCHECK_GE(minute, 0); DCHECK_LE(minute, 59); } else { if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute, second_, millisecond_)) return false; minute_ = minute; return true; } int hour = hour_ + carry; if (hour > 23) { carry = hour / 24; hour = hour % 24; } else if (hour < 0) { carry = (23 - hour) / 24; hour += carry * 24; carry = -carry; DCHECK_GE(hour, 0); DCHECK_LE(hour, 23); } else { if (!WithinHTMLDateLimits(year_, month_, month_day_, hour, minute, second_, millisecond_)) return false; minute_ = minute; hour_ = hour; return true; } if (!AddDay(carry)) return false; if (!WithinHTMLDateLimits(year_, month_, month_day_, hour, minute, second_, millisecond_)) return false; minute_ = minute; hour_ = hour; return true; } // Parses a timezone part, and adjust year, month, monthDay, hour, minute, // second, millisecond. bool DateComponents::ParseTimeZone(const String& src, unsigned start, unsigned& end) { if (start >= src.length()) return false; unsigned index = start; if (src[index] == 'Z') { end = index + 1; return true; } bool minus; if (src[index] == '+') minus = false; else if (src[index] == '-') minus = true; else return false; ++index; int hour; int minute; if (!ToInt(src, index, 2, hour) || hour < 0 || hour > 23) return false; index += 2; if (index >= src.length() || src[index] != ':') return false; ++index; if (!ToInt(src, index, 2, minute) || minute < 0 || minute > 59) return false; index += 2; if (minus) { hour = -hour; minute = -minute; } // Subtract the timezone offset. if (!AddMinute(-(hour * 60 + minute))) return false; end = index; return true; } bool DateComponents::ParseMonth(const String& src, unsigned start, unsigned& end) { unsigned index; if (!ParseYear(src, start, index)) return false; if (index >= src.length() || src[index] != '-') return false; ++index; int month; if (!ToInt(src, index, 2, month) || month < 1 || month > 12) return false; --month; if (!WithinHTMLDateLimits(year_, month)) return false; month_ = month; end = index + 2; type_ = kMonth; return true; } bool DateComponents::ParseDate(const String& src, unsigned start, unsigned& end) { unsigned index; if (!ParseMonth(src, start, index)) return false; // '-' and 2-digits are needed. if (index + 2 >= src.length()) return false; if (src[index] != '-') return false; ++index; int day; if (!ToInt(src, index, 2, day) || day < 1 || day > MaxDayOfMonth(year_, month_)) return false; if (!WithinHTMLDateLimits(year_, month_, day)) return false; month_day_ = day; end = index + 2; type_ = kDate; return true; } bool DateComponents::ParseWeek(const String& src, unsigned start, unsigned& end) { unsigned index; if (!ParseYear(src, start, index)) return false; // 4 characters ('-' 'W' digit digit) are needed. if (index + 3 >= src.length()) return false; if (src[index] != '-') return false; ++index; if (src[index] != 'W') return false; ++index; int week; if (!ToInt(src, index, 2, week) || week < kMinimumWeekNumber || week > MaxWeekNumberInYear()) return false; if (year_ == MaximumYear() && week > kMaximumWeekInMaximumYear) return false; week_ = week; end = index + 2; type_ = kWeek; return true; } bool DateComponents::ParseTime(const String& src, unsigned start, unsigned& end) { int hour; if (!ToInt(src, start, 2, hour) || hour < 0 || hour > 23) return false; unsigned index = start + 2; if (index >= src.length()) return false; if (src[index] != ':') return false; ++index; int minute; if (!ToInt(src, index, 2, minute) || minute < 0 || minute > 59) return false; index += 2; int second = 0; int millisecond = 0; // Optional second part. // Do not return with false because the part is optional. if (index + 2 < src.length() && src[index] == ':') { if (ToInt(src, index + 1, 2, second) && second >= 0 && second <= 59) { index += 3; // Optional fractional second part. if (index < src.length() && src[index] == '.') { unsigned digits_length = CountDigits(src, index + 1); if (digits_length > 0) { ++index; bool ok; if (digits_length == 1) { ok = ToInt(src, index, 1, millisecond); millisecond *= 100; } else if (digits_length == 2) { ok = ToInt(src, index, 2, millisecond); millisecond *= 10; } else if (digits_length == 3) { ok = ToInt(src, index, 3, millisecond); } else { // digitsLength >= 4 return false; } DCHECK(ok); index += digits_length; } } } } hour_ = hour; minute_ = minute; second_ = second; millisecond_ = millisecond; end = index; type_ = kTime; return true; } bool DateComponents::ParseDateTimeLocal(const String& src, unsigned start, unsigned& end) { unsigned index; if (!ParseDate(src, start, index)) return false; if (index >= src.length()) return false; if (src[index] != 'T') return false; ++index; if (!ParseTime(src, index, end)) return false; if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute_, second_, millisecond_)) return false; type_ = kDateTimeLocal; return true; } static inline double PositiveFmod(double value, double divider) { double remainder = fmod(value, divider); return remainder < 0 ? remainder + divider : remainder; } void DateComponents::SetMillisecondsSinceMidnightInternal(double ms_in_day) { DCHECK_GE(ms_in_day, 0); DCHECK_LT(ms_in_day, kMsPerDay); millisecond_ = static_cast(fmod(ms_in_day, kMsPerSecond)); double value = std::floor(ms_in_day / kMsPerSecond); second_ = static_cast(fmod(value, kSecondsPerMinute)); value = std::floor(value / kSecondsPerMinute); minute_ = static_cast(fmod(value, kMinutesPerHour)); hour_ = static_cast(value / kMinutesPerHour); } bool DateComponents::SetMillisecondsSinceEpochForDateInternal(double ms) { year_ = MsToYear(ms); int year_day = DayInYear(ms, year_); month_ = MonthFromDayInYear(year_day, IsLeapYear(year_)); month_day_ = DayInMonthFromDayInYear(year_day, IsLeapYear(year_)); return true; } bool DateComponents::SetMillisecondsSinceEpochForDate(double ms) { type_ = kInvalid; if (!std::isfinite(ms)) return false; if (!SetMillisecondsSinceEpochForDateInternal(round(ms))) return false; if (!WithinHTMLDateLimits(year_, month_, month_day_)) return false; type_ = kDate; return true; } bool DateComponents::SetMillisecondsSinceEpochForDateTime(double ms) { type_ = kInvalid; if (!std::isfinite(ms)) return false; ms = round(ms); SetMillisecondsSinceMidnightInternal(PositiveFmod(ms, kMsPerDay)); if (!SetMillisecondsSinceEpochForDateInternal(ms)) return false; if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute_, second_, millisecond_)) return false; type_ = kDateTime; return true; } bool DateComponents::SetMillisecondsSinceEpochForDateTimeLocal(double ms) { // Internal representation of DateTimeLocal is the same as DateTime except // m_type. if (!SetMillisecondsSinceEpochForDateTime(ms)) return false; type_ = kDateTimeLocal; return true; } bool DateComponents::SetMillisecondsSinceEpochForMonth(double ms) { type_ = kInvalid; if (!std::isfinite(ms)) return false; if (!SetMillisecondsSinceEpochForDateInternal(round(ms))) return false; if (!WithinHTMLDateLimits(year_, month_)) return false; type_ = kMonth; return true; } bool DateComponents::SetMillisecondsSinceMidnight(double ms) { type_ = kInvalid; if (!std::isfinite(ms)) return false; SetMillisecondsSinceMidnightInternal(PositiveFmod(round(ms), kMsPerDay)); type_ = kTime; return true; } bool DateComponents::SetMonthsSinceEpoch(double months) { if (!std::isfinite(months)) return false; months = round(months); double double_month = PositiveFmod(months, 12); double double_year = 1970 + (months - double_month) / 12; if (double_year < MinimumYear() || MaximumYear() < double_year) return false; int year = static_cast(double_year); int month = static_cast(double_month); if (!WithinHTMLDateLimits(year, month)) return false; year_ = year; month_ = month; type_ = kMonth; return true; } // Offset from January 1st to Monday of the ISO 8601's first week. // ex. If January 1st is Friday, such Monday is 3 days later. Returns 3. static int OffsetTo1stWeekStart(int year) { int offset_to1st_week_start = 1 - DayOfWeek(year, 0, 1); if (offset_to1st_week_start <= -4) offset_to1st_week_start += 7; return offset_to1st_week_start; } bool DateComponents::SetMillisecondsSinceEpochForWeek(double ms) { type_ = kInvalid; if (!std::isfinite(ms)) return false; ms = round(ms); year_ = MsToYear(ms); if (year_ < MinimumYear() || year_ > MaximumYear()) return false; int year_day = DayInYear(ms, year_); int offset = OffsetTo1stWeekStart(year_); if (year_day < offset) { // The day belongs to the last week of the previous year. year_--; if (year_ <= MinimumYear()) return false; week_ = MaxWeekNumberInYear(); } else { week_ = ((year_day - offset) / 7) + 1; if (week_ > MaxWeekNumberInYear()) { year_++; week_ = 1; } if (year_ > MaximumYear() || (year_ == MaximumYear() && week_ > kMaximumWeekInMaximumYear)) return false; } type_ = kWeek; return true; } bool DateComponents::SetWeek(int year, int week_number) { type_ = kInvalid; if (year < MinimumYear() || year > MaximumYear()) return false; year_ = year; if (week_number < 1 || week_number > MaxWeekNumberInYear()) return false; week_ = week_number; type_ = kWeek; return true; } double DateComponents::MillisecondsSinceEpochForTime() const { DCHECK(type_ == kTime || type_ == kDateTime || type_ == kDateTimeLocal); return ((hour_ * kMinutesPerHour + minute_) * kSecondsPerMinute + second_) * kMsPerSecond + millisecond_; } double DateComponents::MillisecondsSinceEpoch() const { switch (type_) { case kDate: return DateToDaysFrom1970(year_, month_, month_day_) * kMsPerDay; case kDateTime: case kDateTimeLocal: return DateToDaysFrom1970(year_, month_, month_day_) * kMsPerDay + MillisecondsSinceEpochForTime(); case kMonth: return DateToDaysFrom1970(year_, month_, 1) * kMsPerDay; case kTime: return MillisecondsSinceEpochForTime(); case kWeek: return (DateToDaysFrom1970(year_, 0, 1) + OffsetTo1stWeekStart(year_) + (week_ - 1) * 7) * kMsPerDay; case kInvalid: break; } NOTREACHED(); return InvalidMilliseconds(); } double DateComponents::MonthsSinceEpoch() const { DCHECK_EQ(type_, kMonth); return (year_ - 1970) * 12 + month_; } String DateComponents::ToStringForTime(SecondFormat format) const { DCHECK(type_ == kDateTime || type_ == kDateTimeLocal || type_ == kTime); SecondFormat effective_format = format; if (millisecond_) effective_format = kMillisecond; else if (format == kNone && second_) effective_format = kSecond; switch (effective_format) { default: NOTREACHED(); FALLTHROUGH; case kNone: return String::Format("%02d:%02d", hour_, minute_); case kSecond: return String::Format("%02d:%02d:%02d", hour_, minute_, second_); case kMillisecond: return String::Format("%02d:%02d:%02d.%03d", hour_, minute_, second_, millisecond_); } } String DateComponents::ToString(SecondFormat format) const { switch (type_) { case kDate: return String::Format("%04d-%02d-%02d", year_, month_ + 1, month_day_); case kDateTime: return String::Format("%04d-%02d-%02dT", year_, month_ + 1, month_day_) + ToStringForTime(format) + String("Z"); case kDateTimeLocal: return String::Format("%04d-%02d-%02dT", year_, month_ + 1, month_day_) + ToStringForTime(format); case kMonth: return String::Format("%04d-%02d", year_, month_ + 1); case kTime: return ToStringForTime(format); case kWeek: return String::Format("%04d-W%02d", year_, week_); case kInvalid: break; } NOTREACHED(); return String("(Invalid DateComponents)"); } } // namespace blink