blob: e32083496255d1b8627151aa32118a33efdcabb0 [file] [log] [blame]
/*
* Copyright (C) 2021 Sony Interactive Entertainment Inc.
* Copyright (C) 2021 Apple Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "ISO8601.h"
#include "IntlObject.h"
#include "ParseInt.h"
#include <limits>
#include <wtf/CheckedArithmetic.h>
#include <wtf/DateMath.h>
#include <wtf/WallTime.h>
#include <wtf/text/StringParsingBuffer.h>
#include <wtf/unicode/CharacterNames.h>
namespace JSC {
namespace ISO8601 {
static constexpr int64_t nsPerHour = 1000LL * 1000 * 1000 * 60 * 60;
static constexpr int64_t nsPerMinute = 1000LL * 1000 * 1000 * 60;
static constexpr int64_t nsPerSecond = 1000LL * 1000 * 1000;
static constexpr int64_t nsPerMillisecond = 1000LL * 1000;
static constexpr int64_t nsPerMicrosecond = 1000LL;
std::optional<TimeZoneID> parseTimeZoneName(StringView string)
{
const auto& timeZones = intlAvailableTimeZones();
for (unsigned index = 0; index < timeZones.size(); ++index) {
if (equalIgnoringASCIICase(timeZones[index], string))
return index;
}
return std::nullopt;
}
template<typename CharType>
static int32_t parseDecimalInt32(const CharType* characters, unsigned length)
{
int32_t result = 0;
for (unsigned index = 0; index < length; ++index) {
ASSERT(isASCIIDigit(characters[index]));
result = (result * 10) + characters[index] - '0';
}
return result;
}
// DurationHandleFractions ( fHours, minutes, fMinutes, seconds, fSeconds, milliseconds, fMilliseconds, microseconds, fMicroseconds, nanoseconds, fNanoseconds )
// https://tc39.es/proposal-temporal/#sec-temporal-durationhandlefractions
static void handleFraction(Duration& duration, int factor, StringView fractionString, TemporalUnit fractionType)
{
ASSERT(fractionString.length() && fractionString.length() <= 9 && fractionString.isAllASCII());
ASSERT(fractionType == TemporalUnit::Hour || fractionType == TemporalUnit::Minute || fractionType == TemporalUnit::Second);
if (fractionType == TemporalUnit::Second) {
Vector<LChar, 9> padded(9, '0');
for (unsigned i = 0; i < fractionString.length(); i++)
padded[i] = fractionString[i];
duration.setMilliseconds(factor * parseDecimalInt32(padded.data(), 3));
duration.setMicroseconds(factor * parseDecimalInt32(padded.data() + 3, 3));
duration.setNanoseconds(factor * parseDecimalInt32(padded.data() + 6, 3));
return;
}
double fraction = factor * parseInt(fractionString, 10) / std::pow(10, fractionString.length());
if (!fraction)
return;
if (fractionType == TemporalUnit::Hour) {
fraction *= 60;
duration.setMinutes(std::trunc(fraction));
fraction = std::fmod(fraction, 1);
if (!fraction)
return;
}
fraction *= 60;
duration.setSeconds(std::trunc(fraction));
fraction = std::fmod(fraction, 1);
if (!fraction)
return;
fraction *= 1000;
duration.setMilliseconds(std::trunc(fraction));
fraction = std::fmod(fraction, 1);
if (!fraction)
return;
fraction *= 1000;
duration.setMicroseconds(std::trunc(fraction));
fraction = std::fmod(fraction, 1);
if (!fraction)
return;
duration.setNanoseconds(std::trunc(fraction * 1000));
}
// ParseTemporalDurationString ( isoString )
// https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
template<typename CharacterType>
static std::optional<Duration> parseDuration(StringParsingBuffer<CharacterType>& buffer)
{
// ISO 8601 duration strings are like "-P1Y2M3W4DT5H6M7.123456789S". Notes:
// - case insensitive
// - sign: + - −(U+2212)
// - separator: . ,
// - T is present iff there is a time part
// - integral parts can have any number of digits but fractional parts have at most 9
// - hours and minutes can have fractional parts too, but only as the LAST part of the string
if (buffer.lengthRemaining() < 3)
return std::nullopt;
Duration result;
int factor = 1;
if (*buffer == '+')
buffer.advance();
else if (*buffer == '-' || *buffer == minusSign) {
factor = -1;
buffer.advance();
}
if (toASCIIUpper(*buffer) != 'P')
return std::nullopt;
buffer.advance();
for (unsigned datePartIndex = 0; datePartIndex < 4 && buffer.hasCharactersRemaining() && isASCIIDigit(*buffer); buffer.advance()) {
unsigned digits = 1;
while (digits < buffer.lengthRemaining() && isASCIIDigit(buffer[digits]))
digits++;
double integer = factor * parseInt({ buffer.position(), digits }, 10);
buffer.advanceBy(digits);
if (buffer.atEnd())
return std::nullopt;
switch (toASCIIUpper(*buffer)) {
case 'Y':
if (datePartIndex)
return std::nullopt;
result.setYears(integer);
datePartIndex = 1;
break;
case 'M':
if (datePartIndex >= 2)
return std::nullopt;
result.setMonths(integer);
datePartIndex = 2;
break;
case 'W':
if (datePartIndex >= 3)
return std::nullopt;
result.setWeeks(integer);
datePartIndex = 3;
break;
case 'D':
result.setDays(integer);
datePartIndex = 4;
break;
default:
return std::nullopt;
}
}
if (buffer.atEnd())
return result;
if (buffer.lengthRemaining() < 3 || toASCIIUpper(*buffer) != 'T')
return std::nullopt;
buffer.advance();
for (unsigned timePartIndex = 0; timePartIndex < 3 && buffer.hasCharactersRemaining() && isASCIIDigit(*buffer); buffer.advance()) {
unsigned digits = 1;
while (digits < buffer.lengthRemaining() && isASCIIDigit(buffer[digits]))
digits++;
double integer = factor * parseInt({ buffer.position(), digits }, 10);
buffer.advanceBy(digits);
if (buffer.atEnd())
return std::nullopt;
StringView fractionalPart;
if (*buffer == '.' || *buffer == ',') {
buffer.advance();
digits = 0;
while (digits < buffer.lengthRemaining() && isASCIIDigit(buffer[digits]))
digits++;
if (!digits || digits > 9)
return std::nullopt;
fractionalPart = { buffer.position(), digits };
buffer.advanceBy(digits);
if (buffer.atEnd())
return std::nullopt;
}
switch (toASCIIUpper(*buffer)) {
case 'H':
if (timePartIndex)
return std::nullopt;
result.setHours(integer);
if (fractionalPart) {
handleFraction(result, factor, fractionalPart, TemporalUnit::Hour);
timePartIndex = 3;
} else
timePartIndex = 1;
break;
case 'M':
if (timePartIndex >= 2)
return std::nullopt;
result.setMinutes(integer);
if (fractionalPart) {
handleFraction(result, factor, fractionalPart, TemporalUnit::Minute);
timePartIndex = 3;
} else
timePartIndex = 2;
break;
case 'S':
result.setSeconds(integer);
if (fractionalPart)
handleFraction(result, factor, fractionalPart, TemporalUnit::Second);
timePartIndex = 3;
break;
default:
return std::nullopt;
}
}
if (buffer.hasCharactersRemaining())
return std::nullopt;
return result;
}
std::optional<Duration> parseDuration(StringView string)
{
return readCharactersForParsing(string, [](auto buffer) -> std::optional<Duration> {
return parseDuration(buffer);
});
}
enum class Second60Mode { Accept, Reject };
template<typename CharacterType>
static std::optional<PlainTime> parseTimeSpec(StringParsingBuffer<CharacterType>& buffer, Second60Mode second60Mode)
{
// https://tc39.es/proposal-temporal/#prod-TimeSpec
// TimeSpec :
// TimeHour
// TimeHour : TimeMinute
// TimeHour TimeMinute
// TimeHour : TimeMinute : TimeSecond TimeFraction[opt]
// TimeHour TimeMinute TimeSecond TimeFraction[opt]
//
// TimeSecond can be 60. And if it is 60, we interpret it as 59.
// https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
if (buffer.lengthRemaining() < 2)
return std::nullopt;
unsigned hour = 0;
ASSERT(buffer.lengthRemaining() >= 2);
auto firstHourCharacter = *buffer;
if (firstHourCharacter >= '0' && firstHourCharacter <= '2') {
buffer.advance();
auto secondHourCharacter = *buffer;
if (!isASCIIDigit(secondHourCharacter))
return std::nullopt;
hour = (secondHourCharacter - '0') + 10 * (firstHourCharacter - '0');
if (hour >= 24)
return std::nullopt;
buffer.advance();
} else
return std::nullopt;
if (buffer.atEnd())
return PlainTime(hour, 0, 0, 0, 0, 0);
bool splitByColon = false;
if (*buffer == ':') {
splitByColon = true;
buffer.advance();
} else if (!(*buffer >= '0' && *buffer <= '5'))
return PlainTime(hour, 0, 0, 0, 0, 0);
unsigned minute = 0;
if (buffer.lengthRemaining() < 2)
return std::nullopt;
auto firstMinuteCharacter = *buffer;
if (firstMinuteCharacter >= '0' && firstMinuteCharacter <= '5') {
buffer.advance();
auto secondMinuteCharacter = *buffer;
if (!isASCIIDigit(secondMinuteCharacter))
return std::nullopt;
minute = (secondMinuteCharacter - '0') + 10 * (firstMinuteCharacter - '0');
ASSERT(minute < 60);
buffer.advance();
} else
return std::nullopt;
if (buffer.atEnd())
return PlainTime(hour, minute, 0, 0, 0, 0);
if (splitByColon) {
if (*buffer == ':')
buffer.advance();
else
return PlainTime(hour, minute, 0, 0, 0, 0);
} else if (!(*buffer >= '0' && (second60Mode == Second60Mode::Accept ? (*buffer <= '6') : (*buffer <= '5'))))
return PlainTime(hour, minute, 0, 0, 0, 0);
unsigned second = 0;
if (buffer.lengthRemaining() < 2)
return std::nullopt;
auto firstSecondCharacter = *buffer;
if (firstSecondCharacter >= '0' && firstSecondCharacter <= '5') {
buffer.advance();
auto secondSecondCharacter = *buffer;
if (!isASCIIDigit(secondSecondCharacter))
return std::nullopt;
second = (secondSecondCharacter - '0') + 10 * (firstSecondCharacter - '0');
ASSERT(second < 60);
buffer.advance();
} else if (second60Mode == Second60Mode::Accept && firstSecondCharacter == '6') {
buffer.advance();
auto secondSecondCharacter = *buffer;
if (secondSecondCharacter != '0')
return std::nullopt;
second = 59;
buffer.advance();
} else
return std::nullopt;
if (buffer.atEnd())
return PlainTime(hour, minute, second, 0, 0, 0);
if (*buffer != '.' && *buffer != ',')
return PlainTime(hour, minute, second, 0, 0, 0);
buffer.advance();
unsigned digits = 0;
unsigned maxCount = std::min(buffer.lengthRemaining(), 9u);
for (; digits < maxCount; ++digits) {
if (!isASCIIDigit(buffer[digits]))
break;
}
if (!digits)
return std::nullopt;
Vector<LChar, 9> padded(9, '0');
for (unsigned i = 0; i < digits; ++i)
padded[i] = buffer[i];
buffer.advanceBy(digits);
unsigned millisecond = parseDecimalInt32(padded.data(), 3);
unsigned microsecond = parseDecimalInt32(padded.data() + 3, 3);
unsigned nanosecond = parseDecimalInt32(padded.data() + 6, 3);
return PlainTime(hour, minute, second, millisecond, microsecond, nanosecond);
}
template<typename CharacterType>
static std::optional<int64_t> parseTimeZoneNumericUTCOffset(StringParsingBuffer<CharacterType>& buffer)
{
// TimeZoneNumericUTCOffset :
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute : TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
//
// This is the same to
// TimeZoneUTCOffsetSign TimeSpec
//
// Maximum and minimum values are ±23:59:59.999999999 = ±86399999999999ns, which can be represented by int64_t / double's integer part.
// sign and hour.
if (buffer.lengthRemaining() < 3)
return std::nullopt;
int64_t factor = 1;
if (*buffer == '+')
buffer.advance();
else if (*buffer == '-' || *buffer == minusSign) {
factor = -1;
buffer.advance();
} else
return std::nullopt;
auto plainTime = parseTimeSpec(buffer, Second60Mode::Reject);
if (!plainTime)
return std::nullopt;
int64_t hour = plainTime->hour();
int64_t minute = plainTime->minute();
int64_t second = plainTime->second();
int64_t millisecond = plainTime->millisecond();
int64_t microsecond = plainTime->microsecond();
int64_t nanosecond = plainTime->nanosecond();
return (nsPerHour * hour + nsPerMinute * minute + nsPerSecond * second + nsPerMillisecond * millisecond + nsPerMicrosecond * microsecond + nanosecond) * factor;
}
std::optional<int64_t> parseTimeZoneNumericUTCOffset(StringView string)
{
return readCharactersForParsing(string, [](auto buffer) -> std::optional<int64_t> {
auto result = parseTimeZoneNumericUTCOffset(buffer);
if (!buffer.atEnd())
return std::nullopt;
return result;
});
}
template<typename CharacterType>
static std::optional<std::variant<Vector<LChar>, int64_t>> parseTimeZoneBracketedAnnotation(StringParsingBuffer<CharacterType>& buffer)
{
// https://tc39.es/proposal-temporal/#prod-TimeZoneBracketedAnnotation
// TimeZoneBracketedAnnotation :
// [ TimeZoneBracketedName ]
//
// TimeZoneBracketedName :
// TimeZoneIANAName
// Etc/GMT ASCIISign Hour
// TimeZoneUTCOffsetName
if (buffer.lengthRemaining() < 3)
return std::nullopt;
if (*buffer != '[')
return std::nullopt;
buffer.advance();
switch (static_cast<UChar>(*buffer)) {
case '+':
case '-':
case minusSign: {
// TimeZoneUTCOffsetName is the same to TimeZoneNumericUTCOffset.
auto offset = parseTimeZoneNumericUTCOffset(buffer);
if (!offset)
return std::nullopt;
if (buffer.atEnd())
return std::nullopt;
if (*buffer != ']')
return std::nullopt;
buffer.advance();
return offset.value();
}
case 'E': {
// "Etc/GMT+20" and "]" => length is 11.
if (buffer.lengthRemaining() >= 11) {
if (buffer[0] == 'E' && buffer[1] == 't' && buffer[2] == 'c' && buffer[3] == '/' && buffer[4] == 'G' && buffer[5] == 'M' && buffer[6] == 'T') {
auto signCharacter = buffer[7];
// Not including minusSign since it is ASCIISign.
if (signCharacter == '+' || signCharacter == '-') {
// Etc/GMT+01 is UTC-01:00. This sign is intentionally inverted.
// https://en.wikipedia.org/wiki/Tz_database#Area
int64_t factor = signCharacter == '+' ? -1 : 1;
int64_t hour = 0;
auto firstHourCharacter = buffer[8];
if (firstHourCharacter >= '0' && firstHourCharacter <= '2') {
auto secondHourCharacter = buffer[9];
if (isASCIIDigit(secondHourCharacter)) {
hour = (secondHourCharacter - '0') + 10 * (firstHourCharacter - '0');
if (hour < 24 && buffer[10] == ']') {
buffer.advanceBy(11);
return nsPerHour * hour * factor;
}
}
}
}
}
}
FALLTHROUGH;
}
default: {
// TZLeadingChar :
// Alpha
// .
// _
//
// TZChar :
// Alpha
// .
// -
// _
//
// TimeZoneIANANameComponent :
// TZLeadingChar TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] but not one of . or ..
//
// TimeZoneIANAName :
// TimeZoneIANANameComponent
// TimeZoneIANAName / TimeZoneIANANameComponent
unsigned nameLength = 0;
{
unsigned index = 0;
for (; index < buffer.lengthRemaining(); ++index) {
auto character = buffer[index];
if (character == ']')
break;
if (!isASCIIAlpha(character) && character != '.' && character != '_' && character != '-' && character != '/')
return std::nullopt;
}
if (!index)
return std::nullopt;
nameLength = index;
}
auto isValidComponent = [&](unsigned start, unsigned end) {
unsigned componentLength = end - start;
if (!componentLength)
return false;
if (componentLength > 14)
return false;
if (componentLength == 1 && buffer[start] == '.')
return false;
if (componentLength == 2 && buffer[start] == '.' && buffer[start + 1] == '.')
return false;
return true;
};
unsigned currentNameComponentStartIndex = 0;
bool isLeadingCharacterInNameComponent = true;
for (unsigned index = 0; index < nameLength; ++index) {
auto character = buffer[index];
if (isLeadingCharacterInNameComponent) {
if (!(isASCIIAlpha(character) || character == '.' || character == '_'))
return std::nullopt;
currentNameComponentStartIndex = index;
isLeadingCharacterInNameComponent = false;
continue;
}
if (character == '/') {
if (!isValidComponent(currentNameComponentStartIndex, index))
return std::nullopt;
isLeadingCharacterInNameComponent = true;
continue;
}
if (!(isASCIIAlpha(character) || character == '.' || character == '_' || character == '-'))
return std::nullopt;
}
if (isLeadingCharacterInNameComponent)
return std::nullopt;
if (!isValidComponent(currentNameComponentStartIndex, nameLength))
return std::nullopt;
Vector<LChar> result;
result.reserveInitialCapacity(nameLength);
for (unsigned index = 0; index < nameLength; ++index)
result.uncheckedAppend(buffer[index]);
buffer.advanceBy(nameLength);
if (buffer.atEnd())
return std::nullopt;
if (*buffer != ']')
return std::nullopt;
buffer.advance();
return result;
}
}
}
template<typename CharacterType>
static bool canBeTimeZone(const StringParsingBuffer<CharacterType>& buffer, CharacterType character)
{
switch (static_cast<UChar>(character)) {
// UTCDesignator
// https://tc39.es/proposal-temporal/#prod-UTCDesignator
case 'z':
case 'Z':
// TimeZoneUTCOffsetSign
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetSign
case '+':
case '-':
case minusSign:
// TimeZoneBracketedAnnotation
// https://tc39.es/proposal-temporal/#prod-TimeZoneBracketedAnnotation
case '[': {
// We should reject calendar extension case.
// https://tc39.es/proposal-temporal/#prod-Calendar
// Calendar :
// [u-ca= CalendarName]
if (buffer.lengthRemaining() >= 6 && buffer[1] == 'u' && buffer[2] == '-' && buffer[3] == 'c' && buffer[4] == 'a' && buffer[5] == '=')
return false;
return true;
}
default:
return false;
}
}
template<typename CharacterType>
static std::optional<TimeZoneRecord> parseTimeZone(StringParsingBuffer<CharacterType>& buffer)
{
if (buffer.atEnd())
return std::nullopt;
switch (static_cast<UChar>(*buffer)) {
// UTCDesignator
// https://tc39.es/proposal-temporal/#prod-UTCDesignator
case 'z':
case 'Z': {
buffer.advance();
if (!buffer.atEnd() && *buffer == '[') {
auto timeZone = parseTimeZoneBracketedAnnotation(buffer);
if (!timeZone)
return std::nullopt;
return TimeZoneRecord { true, std::nullopt, WTFMove(timeZone.value()) };
}
return TimeZoneRecord { true, std::nullopt, { } };
}
// TimeZoneUTCOffsetSign
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetSign
case '+':
case '-':
case minusSign: {
auto offset = parseTimeZoneNumericUTCOffset(buffer);
if (!offset)
return std::nullopt;
if (!buffer.atEnd() && *buffer == '[') {
auto timeZone = parseTimeZoneBracketedAnnotation(buffer);
if (!timeZone)
return std::nullopt;
return TimeZoneRecord { false, offset.value(), WTFMove(timeZone.value()) };
}
return TimeZoneRecord { false, offset.value(), { } };
}
// TimeZoneBracketedAnnotation
// https://tc39.es/proposal-temporal/#prod-TimeZoneBracketedAnnotation
case '[': {
auto timeZone = parseTimeZoneBracketedAnnotation(buffer);
if (!timeZone)
return std::nullopt;
return TimeZoneRecord { false, std::nullopt, WTFMove(timeZone.value()) };
}
default:
return std::nullopt;
}
}
template<typename CharacterType>
static std::optional<std::tuple<PlainTime, std::optional<TimeZoneRecord>>> parseTime(StringParsingBuffer<CharacterType>& buffer)
{
// https://tc39.es/proposal-temporal/#prod-Time
// Time :
// TimeSpec TimeZone[opt]
auto plainTime = parseTimeSpec(buffer, Second60Mode::Accept);
if (!plainTime)
return std::nullopt;
if (buffer.atEnd())
return std::tuple { plainTime.value(), std::nullopt };
if (canBeTimeZone(buffer, *buffer)) {
auto timeZone = parseTimeZone(buffer);
if (!timeZone)
return std::nullopt;
return std::tuple { plainTime.value(), timeZone };
}
return std::tuple { plainTime.value(), std::nullopt };
}
// https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth
static inline unsigned daysInMonth(int32_t year, unsigned month)
{
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
if (isLeapYear(year))
return 29;
return 28;
}
return 0;
}
template<typename CharacterType>
static std::optional<PlainDate> parseDate(StringParsingBuffer<CharacterType>& buffer)
{
// https://tc39.es/proposal-temporal/#prod-Date
// Date :
// DateYear - DateMonth - DateDay
// DateYear DateMonth DateDay
//
// DateYear :
// DateFourDigitYear
// DateExtendedYear
//
// DateFourDigitYear :
// Digit Digit Digit Digit
//
// DateExtendedYear :
// Sign Digit Digit Digit Digit Digit Digit
//
// DateMonth :
// 0 NonzeroDigit
// 10
// 11
// 12
//
// DateDay :
// 0 NonzeroDigit
// 1 Digit
// 2 Digit
// 30
// 31
if (buffer.atEnd())
return std::nullopt;
bool sixDigitsYear = false;
int yearFactor = 1;
if (*buffer == '+') {
buffer.advance();
sixDigitsYear = true;
} else if (*buffer == '-' || *buffer == minusSign) {
yearFactor = -1;
buffer.advance();
sixDigitsYear = true;
} else if (!isASCIIDigit(*buffer))
return std::nullopt;
int32_t year = 0;
if (sixDigitsYear) {
if (buffer.lengthRemaining() < 6)
return std::nullopt;
for (unsigned index = 0; index < 6; ++index) {
if (!isASCIIDigit(buffer[index]))
return std::nullopt;
}
year = parseDecimalInt32(buffer.position(), 6) * yearFactor;
buffer.advanceBy(6);
} else {
if (buffer.lengthRemaining() < 4)
return std::nullopt;
for (unsigned index = 0; index < 4; ++index) {
if (!isASCIIDigit(buffer[index]))
return std::nullopt;
}
year = parseDecimalInt32(buffer.position(), 4);
buffer.advanceBy(4);
}
if (buffer.atEnd())
return std::nullopt;
bool splitByHyphen = false;
if (*buffer == '-') {
splitByHyphen = true;
buffer.advance();
if (buffer.lengthRemaining() < 5)
return std::nullopt;
} else {
if (buffer.lengthRemaining() < 4)
return std::nullopt;
}
// We ensured that buffer has enough length for month and day. We do not need to check length.
unsigned month = 0;
auto firstMonthCharacter = *buffer;
if (firstMonthCharacter == '0' || firstMonthCharacter == '1') {
buffer.advance();
auto secondMonthCharacter = *buffer;
if (!isASCIIDigit(secondMonthCharacter))
return std::nullopt;
month = (secondMonthCharacter - '0') + 10 * (firstMonthCharacter - '0');
if (!month || month > 12)
return std::nullopt;
buffer.advance();
} else
return std::nullopt;
if (splitByHyphen) {
if (*buffer == '-')
buffer.advance();
else
return std::nullopt;
}
unsigned day = 0;
auto firstDayCharacter = *buffer;
if (firstDayCharacter >= '0' && firstDayCharacter <= '3') {
buffer.advance();
auto secondDayCharacter = *buffer;
if (!isASCIIDigit(secondDayCharacter))
return std::nullopt;
day = (secondDayCharacter - '0') + 10 * (firstDayCharacter - '0');
if (!day || day > daysInMonth(year, month))
return std::nullopt;
buffer.advance();
} else
return std::nullopt;
return PlainDate(year, month, day);
}
template<typename CharacterType>
static std::optional<std::tuple<PlainDate, std::optional<PlainTime>, std::optional<TimeZoneRecord>>> parseDateTime(StringParsingBuffer<CharacterType>& buffer)
{
// https://tc39.es/proposal-temporal/#prod-DateTime
// DateTime :
// Date TimeSpecSeparator[opt] TimeZone[opt]
//
// TimeSpecSeparator :
// DateTimeSeparator TimeSpec
auto plainDate = parseDate(buffer);
if (!plainDate)
return std::nullopt;
if (buffer.atEnd())
return std::tuple { plainDate.value(), std::nullopt, std::nullopt };
if (*buffer == ' ' || *buffer == 'T' || *buffer == 't') {
buffer.advance();
auto plainTimeAndTimeZone = parseTime(buffer);
if (!plainTimeAndTimeZone)
return std::nullopt;
auto [plainTime, timeZone] = plainTimeAndTimeZone.value();
return std::tuple { plainDate.value(), plainTime, timeZone };
}
if (canBeTimeZone(buffer, *buffer)) {
auto timeZone = parseTimeZone(buffer);
if (!timeZone)
return std::nullopt;
return std::tuple { plainDate.value(), std::nullopt, timeZone };
}
return std::tuple { plainDate.value(), std::nullopt, std::nullopt };
}
std::optional<std::tuple<PlainTime, std::optional<TimeZoneRecord>>> parseTime(StringView string)
{
return readCharactersForParsing(string, [](auto buffer) -> std::optional<std::tuple<PlainTime, std::optional<TimeZoneRecord>>> {
auto result = parseTime(buffer);
if (!buffer.atEnd())
return std::nullopt;
return result;
});
}
std::optional<std::tuple<PlainDate, std::optional<PlainTime>, std::optional<TimeZoneRecord>>> parseDateTime(StringView string)
{
return readCharactersForParsing(string, [](auto buffer) -> std::optional<std::tuple<PlainDate, std::optional<PlainTime>, std::optional<TimeZoneRecord>>> {
auto result = parseDateTime(buffer);
if (!buffer.atEnd())
return std::nullopt;
return result;
});
}
std::optional<ExactTime> parseInstant(StringView string)
{
// https://tc39.es/proposal-temporal/#prod-TemporalInstantString
// TemporalInstantString :
// Date TimeZoneOffsetRequired
// Date DateTimeSeparator TimeSpec TimeZoneOffsetRequired
// https://tc39.es/proposal-temporal/#prod-TimeZoneOffsetRequired
// TimeZoneOffsetRequired :
// TimeZoneUTCOffset TimeZoneBracketedAnnotation_opt
return readCharactersForParsing(string, [](auto buffer) -> std::optional<ExactTime> {
auto datetime = parseDateTime(buffer);
if (!datetime)
return std::nullopt;
auto [date, maybeTime, maybeTimeZone] = datetime.value();
if (!maybeTimeZone || (!maybeTimeZone->m_z && !maybeTimeZone->m_offset))
return std::nullopt;
// FIXME: parse calendar annotation
if (!buffer.atEnd())
return std::nullopt;
PlainTime time;
if (maybeTime)
time = maybeTime.value();
int64_t offset = maybeTimeZone->m_z ? 0 : *maybeTimeZone->m_offset;
return { ExactTime::fromISOPartsAndOffset(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.millisecond(), time.microsecond(), time.nanosecond(), offset) };
});
}
// https://tc39.es/proposal-temporal/#sec-temporal-formattimezoneoffsetstring
String formatTimeZoneOffsetString(int64_t offset)
{
bool negative = false;
if (offset < 0) {
negative = true;
offset = -offset; // This is OK since offset range is much narrower than [INT64_MIN, INT64_MAX] range.
}
int64_t nanoseconds = offset % nsPerSecond;
int64_t seconds = (offset / nsPerSecond) % 60;
int64_t minutes = (offset / nsPerMinute) % 60;
int64_t hours = offset / nsPerHour;
if (nanoseconds) {
// Since nsPerSecond is 1000000000, stringified nanoseconds takes at most 9 characters (999999999).
auto fraction = numberToStringUnsigned<Vector<LChar, 9>>(nanoseconds);
unsigned paddingLength = 9 - fraction.size();
unsigned index = fraction.size();
std::optional<unsigned> validLength;
while (index--) {
if (fraction[index] != '0') {
validLength = index + 1;
break;
}
}
if (validLength)
fraction.resize(validLength.value());
else
fraction.clear();
return makeString(negative ? '-' : '+', pad('0', 2, hours), ':', pad('0', 2, minutes), ':', pad('0', 2, seconds), '.', pad('0', paddingLength, emptyString()), fraction);
}
if (seconds)
return makeString(negative ? '-' : '+', pad('0', 2, hours), ':', pad('0', 2, minutes), ':', pad('0', 2, seconds));
return makeString(negative ? '-' : '+', pad('0', 2, hours), ':', pad('0', 2, minutes));
}
String temporalTimeToString(PlainTime plainTime, std::tuple<Precision, unsigned> precision)
{
auto [precisionType, precisionValue] = precision;
ASSERT(precisionType == Precision::Auto || precisionValue < 10);
if (precisionType == Precision::Minute)
return makeString(pad('0', 2, plainTime.hour()), ':', pad('0', 2, plainTime.minute()));
int64_t milliseconds = plainTime.millisecond();
int64_t microseconds = plainTime.microsecond();
int64_t nanoseconds = plainTime.nanosecond();
int64_t fractionNanoseconds = milliseconds * nsPerMillisecond + microseconds * nsPerMicrosecond + nanoseconds;
if (precisionType == Precision::Auto) {
if (!fractionNanoseconds)
return makeString(pad('0', 2, plainTime.hour()), ':', pad('0', 2, plainTime.minute()), ':', pad('0', 2, plainTime.second()));
auto fraction = numberToStringUnsigned<Vector<LChar, 9>>(fractionNanoseconds);
unsigned paddingLength = 9 - fraction.size();
unsigned index = fraction.size();
std::optional<unsigned> validLength;
while (index--) {
if (fraction[index] != '0') {
validLength = index + 1;
break;
}
}
if (validLength)
fraction.resize(validLength.value());
else
fraction.clear();
return makeString(pad('0', 2, plainTime.hour()), ':', pad('0', 2, plainTime.minute()), ':', pad('0', 2, plainTime.second()), '.', pad('0', paddingLength, emptyString()), fraction);
}
if (!precisionValue)
return makeString(pad('0', 2, plainTime.hour()), ':', pad('0', 2, plainTime.minute()), ':', pad('0', 2, plainTime.second()));
auto fraction = numberToStringUnsigned<Vector<LChar, 9>>(fractionNanoseconds);
unsigned paddingLength = 9 - fraction.size();
paddingLength = std::min(paddingLength, precisionValue);
precisionValue -= paddingLength;
fraction.resize(precisionValue);
return makeString(pad('0', 2, plainTime.hour()), ':', pad('0', 2, plainTime.minute()), ':', pad('0', 2, plainTime.second()), '.', pad('0', paddingLength, emptyString()), fraction);
}
// IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )
// https://tc39.es/proposal-temporal/#sec-temporal-isvalidduration
bool isValidDuration(const Duration& duration)
{
int sign = 0;
for (auto value : duration) {
if (!std::isfinite(value) || (value < 0 && sign > 0) || (value > 0 && sign < 0))
return false;
if (!sign && value)
sign = value > 0 ? 1 : -1;
}
return true;
}
ExactTime ExactTime::fromISOPartsAndOffset(int32_t y, uint8_t mon, uint8_t d, unsigned h, unsigned min, unsigned s, unsigned ms, unsigned micros, unsigned ns, int64_t offset)
{
ASSERT(y >= -999999 && y <= 999999);
ASSERT(mon >= 1 && mon <= 12);
ASSERT(d >= 1 && d <= 31);
ASSERT(h <= 23);
ASSERT(min <= 59);
ASSERT(s <= 59);
ASSERT(ms <= 999);
ASSERT(micros <= 999);
ASSERT(ns <= 999);
double dateDays = dateToDaysFrom1970(y, mon - 1, d);
double timeMs = timeToMS(h, min, s, ms);
double utcMs = dateDays * msPerDay + timeMs;
Int128 utcNs = static_cast<Int128>(utcMs) * 1'000'000 + micros * 1000 + ns;
return ExactTime { utcNs - offset };
}
using CheckedInt128 = Checked<Int128, RecordOverflow>;
static CheckedInt128 checkedCastDoubleToInt128(double n)
{
// Based on __fixdfti() and __fixunsdfti() from compiler_rt:
// https://github.com/llvm/llvm-project/blob/f3671de5500ff1f8210419226a9603a7d83b1a31/compiler-rt/lib/builtins/fp_fixint_impl.inc
// https://github.com/llvm/llvm-project/blob/f3671de5500ff1f8210419226a9603a7d83b1a31/compiler-rt/lib/builtins/fp_fixuint_impl.inc
static constexpr int significandBits = std::numeric_limits<double>::digits - 1;
static constexpr int exponentBits = std::numeric_limits<uint64_t>::digits - std::numeric_limits<double>::digits;
static constexpr int exponentBias = std::numeric_limits<double>::max_exponent - 1;
static constexpr uint64_t implicitBit = uint64_t { 1 } << significandBits;
static constexpr uint64_t significandMask = implicitBit - uint64_t { 1 };
static constexpr uint64_t signMask = uint64_t { 1 } << (significandBits + exponentBits);
static constexpr uint64_t absMask = signMask - uint64_t { 1 };
// Break n into sign, exponent, significand parts.
const uint64_t bits = *reinterpret_cast<uint64_t*>(&n);
const uint64_t nAbs = bits & absMask;
const int sign = bits & signMask ? -1 : 1;
const int exponent = (nAbs >> significandBits) - exponentBias;
const uint64_t significand = (nAbs & significandMask) | implicitBit;
// If exponent is negative, the result is zero.
if (exponent < 0)
return { 0 };
// If the value is too large for the integer type, overflow.
if (exponent >= 128)
return { WTF::ResultOverflowed };
// If 0 <= exponent < significandBits, right shift to get the result.
// Otherwise, shift left.
Int128 result { significand };
if (exponent < significandBits)
result >>= significandBits - exponent;
else
result <<= exponent - significandBits;
result *= sign;
return { result };
}
std::optional<ExactTime> ExactTime::add(Duration duration) const
{
ASSERT(!duration.years());
ASSERT(!duration.months());
ASSERT(!duration.weeks());
ASSERT(!duration.days());
CheckedInt128 resultNs { m_epochNanoseconds };
// The duration's hours, minutes, seconds, and milliseconds should be
// able to be cast into a 64-bit int. 2*1e8 24-hour days is the maximum
// time span for exact time, so if we already know that the duration exceeds
// that, then we can bail out.
CheckedInt128 hours = checkedCastDoubleToInt128(duration.hours());
resultNs += hours * ExactTime::nsPerHour;
CheckedInt128 minutes = checkedCastDoubleToInt128(duration.minutes());
resultNs += minutes * ExactTime::nsPerMinute;
CheckedInt128 seconds = checkedCastDoubleToInt128(duration.seconds());
resultNs += seconds * ExactTime::nsPerSecond;
CheckedInt128 milliseconds = checkedCastDoubleToInt128(duration.milliseconds());
resultNs += milliseconds * ExactTime::nsPerMillisecond;
CheckedInt128 microseconds = checkedCastDoubleToInt128(duration.microseconds());
resultNs += microseconds * ExactTime::nsPerMicrosecond;
resultNs += checkedCastDoubleToInt128(duration.nanoseconds());
if (resultNs.hasOverflowed())
return std::nullopt;
ExactTime result { resultNs.value() };
if (!result.isValid())
return std::nullopt;
return result;
}
Int128 ExactTime::round(Int128 quantity, unsigned increment, TemporalUnit unit, RoundingMode roundingMode)
{
Int128 incrementNs { increment };
switch (unit) {
case TemporalUnit::Hour: incrementNs *= ExactTime::nsPerHour; break;
case TemporalUnit::Minute: incrementNs *= ExactTime::nsPerMinute; break;
case TemporalUnit::Second: incrementNs *= ExactTime::nsPerSecond; break;
case TemporalUnit::Millisecond: incrementNs *= ExactTime::nsPerMillisecond; break;
case TemporalUnit::Microsecond: incrementNs *= ExactTime::nsPerMicrosecond; break;
case TemporalUnit::Nanosecond: break;
default:
ASSERT_NOT_REACHED();
}
return roundNumberToIncrement(quantity, incrementNs, roundingMode);
}
// DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode )
// https://tc39.es/proposal-temporal/#sec-temporal-differenceinstant
Int128 ExactTime::difference(ExactTime other, unsigned increment, TemporalUnit unit, RoundingMode roundingMode) const
{
Int128 diff = other.m_epochNanoseconds - m_epochNanoseconds;
return round(diff, increment, unit, roundingMode);
}
ExactTime ExactTime::round(unsigned increment, TemporalUnit unit, RoundingMode roundingMode) const
{
return ExactTime { round(m_epochNanoseconds, increment, unit, roundingMode) };
}
ExactTime ExactTime::now()
{
return ExactTime { WTF::currentTimeInNanoseconds() };
}
} // namespace ISO8601
} // namespace JSC