blob: d4e349d35a1a33bc5f06c1281ffb0f9d840abde4 [file] [log] [blame]
/*
* 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 "platform/DateComponents.h"
#include <limits.h>
#include "platform/wtf/ASCIICType.h"
#include "platform/wtf/DateMath.h"
#include "platform/wtf/MathExtras.h"
#include "platform/wtf/text/WTFString.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<int>(fmod(ms_in_day, kMsPerSecond));
double value = std::floor(ms_in_day / kMsPerSecond);
second_ = static_cast<int>(fmod(value, kSecondsPerMinute));
value = std::floor(value / kSecondsPerMinute);
minute_ = static_cast<int>(fmod(value, kMinutesPerHour));
hour_ = static_cast<int>(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<int>(double_year);
int month = static_cast<int>(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();
// Fallback to None.
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