blob: eab1f40435ce64003b0da3c6c28e8710e2598eb8 [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 "config.h"
#include "DateComponents.h"
#include "ParsingUtilities.h"
#include <limits.h>
#include <wtf/ASCIICType.h>
#include <wtf/DateMath.h>
#include <wtf/MathExtras.h>
#include <wtf/text/StringConcatenateNumbers.h>
#include <wtf/text/StringParsingBuffer.h>
namespace WebCore {
// HTML defines minimum week of year is one.
static constexpr int minimumWeekNumber = 1;
// HTML defines maximum week of year is 53.
static constexpr int maximumWeekNumber = 53;
static constexpr int maximumMonthInMaximumYear = 8; // This is September, since months are 0 based.
static constexpr int maximumDayInMaximumMonth = 13;
static constexpr int maximumWeekInMaximumYear = 37; // The week of 275760-09-13
static constexpr int daysInMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static bool isLeapYear(int year)
{
if (year % 4)
return false;
if (!(year % 400))
return true;
if (!(year % 100))
return false;
return true;
}
// 'month' is 0-based.
static int maxDayOfMonth(int year, int month)
{
if (month != 1) // February?
return daysInMonth[month];
return isLeapYear(year) ? 29 : 28;
}
// 'month' is 0-based.
static int dayOfWeek(int year, int month, int day)
{
int shiftedMonth = month + 2;
// 2:January, 3:Feburuary, 4:March, ...
// Zeller's congruence
if (shiftedMonth <= 3) {
shiftedMonth += 12;
year--;
}
// 4:March, ..., 14:January, 15:February
int highYear = year / 100;
int lowYear = year % 100;
// We add 6 to make the result Sunday-origin.
int result = (day + 13 * shiftedMonth / 5 + lowYear + lowYear / 4 + highYear / 4 + 5 * highYear + 6) % 7;
return result;
}
int DateComponents::maxWeekNumberInYear() const
{
int day = dayOfWeek(m_year, 0, 1); // January 1.
return day == Thursday || (day == Wednesday && isLeapYear(m_year)) ? maximumWeekNumber : maximumWeekNumber - 1;
}
template<typename CharacterType> static unsigned countDigits(StringParsingBuffer<CharacterType> buffer)
{
auto begin = buffer.position();
skipWhile<isASCIIDigit>(buffer);
return buffer.position() - begin;
}
// Differences from parseInteger<int>: Takes StringParsingBuffer. Does not allow leading or trailing spaces. Does not allow leading "+".
template<typename CharacterType> static std::optional<int> parseInt(StringParsingBuffer<CharacterType>& buffer, unsigned maximumNumberOfDigitsToParse)
{
if (maximumNumberOfDigitsToParse > buffer.lengthRemaining() || !maximumNumberOfDigitsToParse)
return std::nullopt;
// We don't need to handle negative numbers for ISO 8601.
int value = 0;
unsigned digitsRemaining = 0;
while (digitsRemaining < maximumNumberOfDigitsToParse) {
if (!isASCIIDigit(*buffer))
return std::nullopt;
int digit = *buffer - '0';
if (value > (INT_MAX - digit) / 10) // Check for overflow.
return std::nullopt;
value = value * 10 + digit;
++digitsRemaining;
++buffer;
}
return value;
}
template<typename CharacterType> static std::optional<int> parseIntWithinLimits(StringParsingBuffer<CharacterType>& buffer, unsigned maximumNumberOfDigitsToParse, int minimumValue, int maximumValue)
{
auto value = parseInt(buffer, maximumNumberOfDigitsToParse);
if (!(value && *value >= minimumValue && *value <= maximumValue))
return std::nullopt;
return value;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#months
template<typename CharacterType> bool DateComponents::parseYear(StringParsingBuffer<CharacterType>& buffer)
{
unsigned digitsLength = countDigits(buffer);
// Needs at least 4 digits according to the standard.
if (digitsLength < 4)
return false;
auto year = parseIntWithinLimits(buffer, digitsLength, minimumYear(), maximumYear());
if (!year)
return false;
m_year = *year;
return true;
}
static bool withinHTMLDateLimits(int year, int month)
{
if (year < DateComponents::minimumYear())
return false;
if (year < DateComponents::maximumYear())
return true;
return month <= maximumMonthInMaximumYear;
}
static bool withinHTMLDateLimits(int year, int month, int monthDay)
{
if (year < DateComponents::minimumYear())
return false;
if (year < DateComponents::maximumYear())
return true;
if (month < maximumMonthInMaximumYear)
return true;
return monthDay <= maximumDayInMaximumMonth;
}
static bool withinHTMLDateLimits(int year, int month, int monthDay, int hour, int minute, int second, int millisecond)
{
if (year < DateComponents::minimumYear())
return false;
if (year < DateComponents::maximumYear())
return true;
if (month < maximumMonthInMaximumYear)
return true;
if (monthDay < maximumDayInMaximumMonth)
return true;
if (monthDay > maximumDayInMaximumMonth)
return false;
// (year, month, monthDay) = (maximumYear, maximumMonthInMaximumYear, maximumDayInMaximumMonth)
return !hour && !minute && !second && !millisecond;
}
template<typename F> static std::optional<DateComponents> createFromString(StringView source, F&& parseFunction)
{
if (source.isEmpty())
return std::nullopt;
return readCharactersForParsing(source, [&](auto buffer) -> std::optional<DateComponents> {
DateComponents date;
if (!parseFunction(buffer, date) || !buffer.atEnd())
return std::nullopt;
return date;
});
}
std::optional<DateComponents> DateComponents::fromParsingMonth(StringView source)
{
return createFromString(source, [] (auto& buffer, auto& date) {
return date.parseMonth(buffer);
});
}
std::optional<DateComponents> DateComponents::fromParsingDate(StringView source)
{
return createFromString(source, [] (auto& buffer, auto& date) {
return date.parseDate(buffer);
});
}
std::optional<DateComponents> DateComponents::fromParsingWeek(StringView source)
{
return createFromString(source, [] (auto& buffer, auto& date) {
return date.parseWeek(buffer);
});
}
std::optional<DateComponents> DateComponents::fromParsingTime(StringView source)
{
return createFromString(source, [] (auto& buffer, auto& date) {
return date.parseTime(buffer);
});
}
std::optional<DateComponents> DateComponents::fromParsingDateTimeLocal(StringView source)
{
return createFromString(source, [] (auto& buffer, auto& date) {
return date.parseDateTimeLocal(buffer);
});
}
bool DateComponents::addDay(int dayDiff)
{
ASSERT(m_monthDay);
int day = m_monthDay + dayDiff;
if (day > maxDayOfMonth(m_year, m_month)) {
day = m_monthDay;
int year = m_year;
int month = m_month;
int maxDay = maxDayOfMonth(year, month);
for (; dayDiff > 0; --dayDiff) {
++day;
if (day > maxDay) {
day = 1;
++month;
if (month >= 12) { // month is 0-origin.
month = 0;
++year;
}
maxDay = maxDayOfMonth(year, month);
}
}
if (!withinHTMLDateLimits(year, month, day))
return false;
m_year = year;
m_month = month;
} else if (day < 1) {
int month = m_month;
int year = m_year;
day = m_monthDay;
for (; dayDiff < 0; ++dayDiff) {
--day;
if (day < 1) {
--month;
if (month < 0) {
month = 11;
--year;
}
day = maxDayOfMonth(year, month);
}
}
if (!withinHTMLDateLimits(year, month, day))
return false;
m_year = year;
m_month = month;
} else {
if (!withinHTMLDateLimits(m_year, m_month, day))
return false;
}
m_monthDay = 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.
ASSERT(withinHTMLDateLimits(m_year, m_month, m_monthDay));
int carry;
// minute can be negative or greater than 59.
minute += m_minute;
if (minute > 59) {
carry = minute / 60;
minute = minute % 60;
} else if (minute < 0) {
carry = (59 - minute) / 60;
minute += carry * 60;
carry = -carry;
ASSERT(minute >= 0 && minute <= 59);
} else {
if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, minute, m_second, m_millisecond))
return false;
m_minute = minute;
return true;
}
int hour = m_hour + carry;
if (hour > 23) {
carry = hour / 24;
hour = hour % 24;
} else if (hour < 0) {
carry = (23 - hour) / 24;
hour += carry * 24;
carry = -carry;
ASSERT(hour >= 0 && hour <= 23);
} else {
if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond))
return false;
m_minute = minute;
m_hour = hour;
return true;
}
if (!addDay(carry))
return false;
if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond))
return false;
m_minute = minute;
m_hour = hour;
return true;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#time-zones
// Parses a timezone part, and adjust year, month, monthDay, hour, minute, second, millisecond.
template<typename CharacterType> bool DateComponents::parseTimeZone(StringParsingBuffer<CharacterType>& buffer)
{
if (skipExactly(buffer, 'Z'))
return true;
bool minus;
if (skipExactly(buffer, '+'))
minus = false;
else if (skipExactly(buffer, '-'))
minus = true;
else
return false;
auto hour = parseIntWithinLimits(buffer, 2, 0, 23);
if (!hour)
return false;
if (!skipExactly(buffer, ':'))
return false;
auto minute = parseIntWithinLimits(buffer, 2, 0, 59);
if (!minute)
return false;
if (minus) {
*hour = -*hour;
*minute = -*minute;
}
// Subtract the timezone offset.
if (!addMinute(-(*hour * 60 + *minute)))
return false;
return true;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#months
template<typename CharacterType> bool DateComponents::parseMonth(StringParsingBuffer<CharacterType>& buffer)
{
if (!parseYear(buffer))
return false;
if (!skipExactly(buffer, '-'))
return false;
auto month = parseIntWithinLimits(buffer, 2, 1, 12);
if (!month)
return false;
--*month;
if (!withinHTMLDateLimits(m_year, *month))
return false;
m_month = *month;
m_type = DateComponentsType::Month;
return true;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates
template<typename CharacterType> bool DateComponents::parseDate(StringParsingBuffer<CharacterType>& buffer)
{
if (!parseMonth(buffer))
return false;
if (!skipExactly(buffer, '-'))
return false;
auto day = parseIntWithinLimits(buffer, 2, 1, maxDayOfMonth(m_year, m_month));
if (!day)
return false;
if (!withinHTMLDateLimits(m_year, m_month, *day))
return false;
m_monthDay = *day;
m_type = DateComponentsType::Date;
return true;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks
template<typename CharacterType> bool DateComponents::parseWeek(StringParsingBuffer<CharacterType>& buffer)
{
if (!parseYear(buffer))
return false;
if (!skipExactly(buffer, '-'))
return false;
if (!skipExactly(buffer, 'W'))
return false;
auto week = parseIntWithinLimits(buffer, 2, minimumWeekNumber, maxWeekNumberInYear());
if (!week)
return false;
if (m_year == maximumYear() && *week > maximumWeekInMaximumYear)
return false;
m_week = *week;
m_type = DateComponentsType::Week;
return true;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#times
template<typename CharacterType> bool DateComponents::parseTime(StringParsingBuffer<CharacterType>& buffer)
{
auto hour = parseIntWithinLimits(buffer, 2, 0, 23);
if (!hour)
return false;
if (!skipExactly(buffer, ':'))
return false;
auto minute = parseIntWithinLimits(buffer, 2, 0, 59);
if (!minute)
return false;
// Optional second part.
// Do not return with false because the part is optional.
std::optional<int> second;
std::optional<int> millisecond;
auto temporaryBuffer = buffer;
if (skipExactly(temporaryBuffer, ':')) {
second = parseIntWithinLimits(temporaryBuffer, 2, 0, 59);
if (second) {
// Only after successfully parsing and validating 'second', do we increment the real buffer.
buffer += temporaryBuffer.position() - buffer.position();
// Optional fractional second part.
if (skipExactly(temporaryBuffer, '.')) {
unsigned digitsLength = countDigits(temporaryBuffer);
if (digitsLength > 0) {
if (digitsLength == 1) {
millisecond = parseInt(temporaryBuffer, 1);
*millisecond *= 100;
} else if (digitsLength == 2) {
millisecond = parseInt(temporaryBuffer, 2);
*millisecond *= 10;
} else {
// Regardless of the number of digits, we only ever parse at most 3. All other
// digits after that are ignored, but the buffer is incremented as if they were
// all parsed.
millisecond = parseInt(temporaryBuffer, 3);
}
// Due to the countDigits above, the parseInt calls should all be successful.
ASSERT(millisecond);
// Only after successfully parsing and validating 'millisecond', do we increment the real buffer.
buffer += 1 + digitsLength;
}
}
}
}
m_hour = *hour;
m_minute = *minute;
m_second = second.value_or(0);
m_millisecond = millisecond.value_or(0);
m_type = DateComponentsType::Time;
return true;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#local-dates-and-times
template<typename CharacterType> bool DateComponents::parseDateTimeLocal(StringParsingBuffer<CharacterType>& buffer)
{
if (!parseDate(buffer))
return false;
if (!skipExactly(buffer, 'T'))
return false;
if (!parseTime(buffer))
return false;
if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
return false;
m_type = DateComponentsType::DateTimeLocal;
return true;
}
static inline double positiveFmod(double value, double divider)
{
double remainder = fmod(value, divider);
return remainder < 0 ? remainder + divider : remainder;
}
template<typename F> static std::optional<DateComponents> createFromTimeOffset(double timeOffset, F&& function)
{
DateComponents result;
if (!function(result, timeOffset))
return std::nullopt;
return result;
}
std::optional<DateComponents> DateComponents::fromMillisecondsSinceEpochForDate(double ms)
{
return createFromTimeOffset(ms, [] (auto& date, auto ms) {
return date.setMillisecondsSinceEpochForDate(ms);
});
}
std::optional<DateComponents> DateComponents::fromMillisecondsSinceEpochForDateTimeLocal(double ms)
{
return createFromTimeOffset(ms, [] (auto& date, auto ms) {
return date.setMillisecondsSinceEpochForDateTimeLocal(ms);
});
}
std::optional<DateComponents> DateComponents::fromMillisecondsSinceEpochForMonth(double ms)
{
return createFromTimeOffset(ms, [] (auto& date, auto ms) {
return date.setMillisecondsSinceEpochForMonth(ms);
});
}
std::optional<DateComponents> DateComponents::fromMillisecondsSinceEpochForWeek(double ms)
{
return createFromTimeOffset(ms, [] (auto& date, auto ms) {
return date.setMillisecondsSinceEpochForWeek(ms);
});
}
std::optional<DateComponents> DateComponents::fromMillisecondsSinceMidnight(double ms)
{
return createFromTimeOffset(ms, [] (auto& date, auto ms) {
return date.setMillisecondsSinceMidnight(ms);
});
}
std::optional<DateComponents> DateComponents::fromMonthsSinceEpoch(double months)
{
return createFromTimeOffset(months, [] (auto& date, auto months) {
return date.setMonthsSinceEpoch(months);
});
}
void DateComponents::setMillisecondsSinceMidnightInternal(double msInDay)
{
ASSERT(msInDay >= 0 && msInDay < msPerDay);
m_millisecond = static_cast<int>(fmod(msInDay, msPerSecond));
double value = floor(msInDay / msPerSecond);
m_second = static_cast<int>(fmod(value, secondsPerMinute));
value = floor(value / secondsPerMinute);
m_minute = static_cast<int>(fmod(value, minutesPerHour));
m_hour = static_cast<int>(value / minutesPerHour);
}
bool DateComponents::setMillisecondsSinceEpochForDateInternal(double ms)
{
m_year = msToYear(ms);
int yearDay = dayInYear(ms, m_year);
m_month = monthFromDayInYear(yearDay, isLeapYear(m_year));
m_monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(m_year));
return true;
}
bool DateComponents::setMillisecondsSinceEpochForDate(double ms)
{
m_type = DateComponentsType::Invalid;
if (!std::isfinite(ms))
return false;
if (!setMillisecondsSinceEpochForDateInternal(round(ms)))
return false;
if (!withinHTMLDateLimits(m_year, m_month, m_monthDay))
return false;
m_type = DateComponentsType::Date;
return true;
}
bool DateComponents::setMillisecondsSinceEpochForDateTimeLocal(double ms)
{
m_type = DateComponentsType::Invalid;
if (!std::isfinite(ms))
return false;
ms = round(ms);
setMillisecondsSinceMidnightInternal(positiveFmod(ms, msPerDay));
if (!setMillisecondsSinceEpochForDateInternal(ms))
return false;
if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
return false;
m_type = DateComponentsType::DateTimeLocal;
return true;
}
bool DateComponents::setMillisecondsSinceEpochForMonth(double ms)
{
m_type = DateComponentsType::Invalid;
if (!std::isfinite(ms))
return false;
if (!setMillisecondsSinceEpochForDateInternal(round(ms)))
return false;
if (!withinHTMLDateLimits(m_year, m_month))
return false;
m_type = DateComponentsType::Month;
return true;
}
bool DateComponents::setMillisecondsSinceMidnight(double ms)
{
m_type = DateComponentsType::Invalid;
if (!std::isfinite(ms))
return false;
setMillisecondsSinceMidnightInternal(positiveFmod(round(ms), msPerDay));
m_type = DateComponentsType::Time;
return true;
}
bool DateComponents::setMonthsSinceEpoch(double months)
{
if (!std::isfinite(months))
return false;
months = round(months);
double doubleMonth = positiveFmod(months, 12);
double doubleYear = 1970 + (months - doubleMonth) / 12;
if (doubleYear < minimumYear() || maximumYear() < doubleYear)
return false;
int year = static_cast<int>(doubleYear);
int month = static_cast<int>(doubleMonth);
if (!withinHTMLDateLimits(year, month))
return false;
m_year = year;
m_month = month;
m_type = DateComponentsType::Month;
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 offsetTo1stWeekStart = 1 - dayOfWeek(year, 0, 1);
if (offsetTo1stWeekStart <= -4)
offsetTo1stWeekStart += 7;
return offsetTo1stWeekStart;
}
bool DateComponents::setMillisecondsSinceEpochForWeek(double ms)
{
m_type = DateComponentsType::Invalid;
if (!std::isfinite(ms))
return false;
ms = round(ms);
m_year = msToYear(ms);
if (m_year < minimumYear() || m_year > maximumYear())
return false;
int yearDay = dayInYear(ms, m_year);
int offset = offsetTo1stWeekStart(m_year);
if (yearDay < offset) {
// The day belongs to the last week of the previous year.
m_year--;
if (m_year <= minimumYear())
return false;
m_week = maxWeekNumberInYear();
} else {
m_week = ((yearDay - offset) / 7) + 1;
if (m_week > maxWeekNumberInYear()) {
m_year++;
m_week = 1;
}
if (m_year > maximumYear() || (m_year == maximumYear() && m_week > maximumWeekInMaximumYear))
return false;
}
m_type = DateComponentsType::Week;
return true;
}
double DateComponents::millisecondsSinceEpochForTime() const
{
ASSERT(m_type == DateComponentsType::Time || m_type == DateComponentsType::DateTimeLocal);
return ((m_hour * minutesPerHour + m_minute) * secondsPerMinute + m_second) * msPerSecond + m_millisecond;
}
double DateComponents::millisecondsSinceEpoch() const
{
switch (m_type) {
case DateComponentsType::Date:
return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay;
case DateComponentsType::DateTimeLocal:
return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay + millisecondsSinceEpochForTime();
case DateComponentsType::Month:
return dateToDaysFrom1970(m_year, m_month, 1) * msPerDay;
case DateComponentsType::Time:
return millisecondsSinceEpochForTime();
case DateComponentsType::Week:
return (dateToDaysFrom1970(m_year, 0, 1) + offsetTo1stWeekStart(m_year) + (m_week - 1) * 7) * msPerDay;
case DateComponentsType::Invalid:
break;
}
ASSERT_NOT_REACHED();
return invalidMilliseconds();
}
double DateComponents::monthsSinceEpoch() const
{
ASSERT(m_type == DateComponentsType::Month);
return (m_year - 1970) * 12 + m_month;
}
String DateComponents::toStringForTime(SecondFormat format) const
{
ASSERT(m_type == DateComponentsType::DateTimeLocal || m_type == DateComponentsType::Time);
SecondFormat effectiveFormat = format;
if (m_millisecond)
effectiveFormat = SecondFormat::Millisecond;
else if (format == SecondFormat::None && m_second)
effectiveFormat = SecondFormat::Second;
switch (effectiveFormat) {
default:
ASSERT_NOT_REACHED();
#if !ASSERT_ENABLED
FALLTHROUGH; // To None.
#endif
case SecondFormat::None:
return makeString(pad('0', 2, m_hour), ':', pad('0', 2, m_minute));
case SecondFormat::Second:
return makeString(pad('0', 2, m_hour), ':', pad('0', 2, m_minute), ':', pad('0', 2, m_second));
case SecondFormat::Millisecond:
return makeString(pad('0', 2, m_hour), ':', pad('0', 2, m_minute), ':', pad('0', 2, m_second), '.', pad('0', 3, m_millisecond));
}
}
String DateComponents::toString(SecondFormat format) const
{
switch (m_type) {
case DateComponentsType::Date:
return makeString(pad('0', 4, m_year), '-', pad('0', 2, m_month + 1), '-', pad('0', 2, m_monthDay));
case DateComponentsType::DateTimeLocal:
return makeString(pad('0', 4, m_year), '-', pad('0', 2, m_month + 1), '-', pad('0', 2, m_monthDay), 'T', toStringForTime(format));
case DateComponentsType::Month:
return makeString(pad('0', 4, m_year), '-', pad('0', 2, m_month + 1));
case DateComponentsType::Time:
return toStringForTime(format);
case DateComponentsType::Week:
return makeString(pad('0', 4, m_year), "-W", pad('0', 2, m_week));
case DateComponentsType::Invalid:
break;
}
ASSERT_NOT_REACHED();
return "(Invalid DateComponents)"_str;
}
} // namespace WebCore