diff --git a/Release/include/cpprest/asyncrt_utils.h b/Release/include/cpprest/asyncrt_utils.h index 1b54f1634a..3e4bfdd5c4 100644 --- a/Release/include/cpprest/asyncrt_utils.h +++ b/Release/include/cpprest/asyncrt_utils.h @@ -603,14 +603,21 @@ class datetime } } - datetime() : m_interval(0) {} + datetime() : m_interval(0) { } /// - /// Creates datetime from a string representing time in UTC in RFC 1123 format. + /// Creates datetime from a string representing time in UTC in RFC 1123 or ISO 8601 format. /// /// Returns a datetime of zero if not successful. static _ASYNCRTIMP datetime __cdecl from_string(const utility::string_t& timestring, date_format format = RFC_1123); + /// + /// Creates datetime from a string representing time in UTC in RFC 1123 or ISO 8601 format. + /// + /// Returns datetime::maximum() if not successful. + static _ASYNCRTIMP datetime __cdecl from_string_maximum_error(const utility::string_t& timestring, + date_format format = RFC_1123); + /// /// Returns a string representation of the datetime. /// @@ -621,6 +628,8 @@ class datetime /// interval_type to_interval() const { return m_interval; } + static datetime from_interval(interval_type interval) { return datetime(interval); } + datetime operator-(interval_type value) const { return datetime(m_interval - value); } datetime operator+(interval_type value) const { return datetime(m_interval + value); } @@ -628,13 +637,13 @@ class datetime bool operator==(datetime dt) const { return m_interval == dt.m_interval; } bool operator!=(const datetime& dt) const { return !(*this == dt); } - + bool operator>(const datetime& dt) const { return this->m_interval > dt.m_interval; } - + bool operator<(const datetime& dt) const { return this->m_interval < dt.m_interval; } - + bool operator>=(const datetime& dt) const { return this->m_interval >= dt.m_interval; } - + bool operator<=(const datetime& dt) const { return this->m_interval <= dt.m_interval; } static interval_type from_milliseconds(unsigned int milliseconds) { return milliseconds * _msTicks; } @@ -649,6 +658,8 @@ class datetime bool is_initialized() const { return m_interval != 0; } + static datetime maximum() { return datetime(static_cast(-1)); } + private: friend int operator-(datetime t1, datetime t2); @@ -659,7 +670,7 @@ class datetime static const interval_type _dayTicks = 24 * 60 * 60 * _secondTicks; // Private constructor. Use static methods to create an instance. - datetime(interval_type interval) : m_interval(interval) {} + datetime(interval_type interval) : m_interval(interval) { } // Storing as hundreds of nanoseconds 10e-7, i.e. 1 here equals 100ns. interval_type m_interval; diff --git a/Release/include/cpprest/streams.h b/Release/include/cpprest/streams.h index c2968b9d49..b6c3864028 100644 --- a/Release/include/cpprest/streams.h +++ b/Release/include/cpprest/streams.h @@ -17,6 +17,7 @@ #include "cpprest/astreambuf.h" #include +#include namespace Concurrency { @@ -1434,7 +1435,8 @@ static pplx::task _extract_result(std::shared_ptr<_double_stateexponent_number >= 0) { - result *= pow(FloatingPoint(10.0), state->exponent_number); + result *= static_cast( + std::pow(static_cast(10.0), static_cast(state->exponent_number))); #pragma push_macro("max") #undef max @@ -1447,7 +1449,8 @@ static pplx::task _extract_result(std::shared_ptr<_double_stateexponent_number); + result /= static_cast( + std::pow(static_cast(10.0), static_cast(-state->exponent_number))); if (!is_zero && result > -std::numeric_limits::denorm_min() && result < std::numeric_limits::denorm_min()) diff --git a/Release/src/utilities/asyncrt_utils.cpp b/Release/src/utilities/asyncrt_utils.cpp index 579fedc25f..cf747c666c 100644 --- a/Release/src/utilities/asyncrt_utils.cpp +++ b/Release/src/utilities/asyncrt_utils.cpp @@ -177,8 +177,8 @@ scoped_c_thread_locale::~scoped_c_thread_locale() } } #elif (defined(ANDROID) || defined(__ANDROID__)) -scoped_c_thread_locale::scoped_c_thread_locale() {} -scoped_c_thread_locale::~scoped_c_thread_locale() {} +scoped_c_thread_locale::scoped_c_thread_locale() { } +scoped_c_thread_locale::~scoped_c_thread_locale() { } #else scoped_c_thread_locale::scoped_c_thread_locale() : m_prevLocale(nullptr) { @@ -620,7 +620,11 @@ utf16string __cdecl conversions::to_utf16string(const std::string& value) { retu static const int64_t NtToUnixOffsetSeconds = 11644473600; // diff between windows and unix epochs (seconds) -static bool year_is_leap_year(int year) { return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); } +static bool year_is_leap_year_1601(int yearsSince1601) +{ + int decimalYear = yearsSince1601 + 1601; + return (decimalYear % 4 == 0 && (decimalYear % 100 != 0 || decimalYear % 400 == 0)); +} static const int SecondsInMinute = 60; static const int SecondsInHour = SecondsInMinute * 60; @@ -635,26 +639,18 @@ static const int SecondsInYear = SecondsInDay * DaysInYear; static const int SecondsIn4Years = SecondsInDay * DaysIn4Years; static const int64_t SecondsIn100Years = static_cast(SecondsInDay) * DaysIn100Years; static const int64_t SecondsIn400Years = static_cast(SecondsInDay) * DaysIn400Years; -static const int64_t SecondsFrom1900To2001 = INT64_C(3187296000); - -static const int64_t NtTo1900OffsetInterval = INT64_C(0x014F373BFDE04000); -static int count_leap_years(const int yearsSince1900) +static int count_leap_years_1601(int yearsSince1601) { - int tmpYears = yearsSince1900 + 299; // shift into 1601, the first 400 year cycle including 1900 - - int year400 = tmpYears / 400; - tmpYears -= year400 * 400; + int year400 = yearsSince1601 / 400; + yearsSince1601 -= year400 * 400; int result = year400 * 97; - int year100 = tmpYears / 100; - tmpYears -= year100 * 100; + int year100 = yearsSince1601 / 100; + yearsSince1601 -= year100 * 100; result += year100 * 24; - result += tmpYears / 4; - - // subtract off leap years from 1601 - result -= 72; + result += yearsSince1601 / 4; return result; } @@ -720,20 +716,16 @@ struct compute_year_result int secondsLeftThisYear; }; -static const int64_t secondsFrom1601To1900 = INT64_C(9435484800); - -static compute_year_result compute_year(int64_t secondsSince1900) +static compute_year_result compute_year_1601(int64_t secondsSince1601) { - int64_t secondsLeft = secondsSince1900 + secondsFrom1601To1900; // shift to start of this 400 year cycle - - int year400 = static_cast(secondsLeft / SecondsIn400Years); - secondsLeft -= year400 * SecondsIn400Years; + int year400 = static_cast(secondsSince1601 / SecondsIn400Years); + secondsSince1601 -= year400 * SecondsIn400Years; - int year100 = static_cast(secondsLeft / SecondsIn100Years); - secondsLeft -= year100 * SecondsIn100Years; + int year100 = static_cast(secondsSince1601 / SecondsIn100Years); + secondsSince1601 -= year100 * SecondsIn100Years; - int year4 = static_cast(secondsLeft / SecondsIn4Years); - int secondsInt = static_cast(secondsLeft - year4 * SecondsIn4Years); + int year4 = static_cast(secondsSince1601 / SecondsIn4Years); + int secondsInt = static_cast(secondsSince1601 - year4 * SecondsIn4Years); int year1 = secondsInt / SecondsInYear; if (year1 == 4) @@ -743,23 +735,44 @@ static compute_year_result compute_year(int64_t secondsSince1900) } secondsInt -= year1 * SecondsInYear; - - // shift back to 1900 base from 1601: - return {year400 * 400 + year100 * 100 + year4 * 4 + year1 - 299, secondsInt}; + return {year400 * 400 + year100 * 100 + year4 * 4 + year1, secondsInt}; } +// The constant below was calculated by running the following test program on a Windows machine: +// #include +// #include + +// int main() { +// SYSTEMTIME st; +// st.wYear = 9999; +// st.wMonth = 12; +// st.wDayOfWeek = 5; +// st.wDay = 31; +// st.wHour = 23; +// st.wMinute = 59; +// st.wSecond = 59; +// st.wMilliseconds = 999; + +// unsigned long long ft; +// if (SystemTimeToFileTime(&st, reinterpret_cast(&ft))) { +// printf("0x%016llX\n", ft); +// } else { +// puts("failed!"); +// } +// } + utility::string_t datetime::to_string(date_format format) const { - if (m_interval > INT64_C(2650467743990000000)) + const int64_t interval = static_cast(m_interval); + if (interval > INT64_C(0x24C85A5ED1C018F0)) { throw std::out_of_range("The requested year exceeds the year 9999."); } - const int64_t epochAdjusted = static_cast(m_interval) - NtTo1900OffsetInterval; - const int64_t secondsSince1900 = epochAdjusted / _secondTicks; // convert to seconds - const int fracSec = static_cast(epochAdjusted % _secondTicks); + const int64_t secondsSince1601 = interval / _secondTicks; // convert to seconds + const int fracSec = static_cast(interval % _secondTicks); - const auto yearData = compute_year(secondsSince1900); + const auto yearData = compute_year_1601(secondsSince1601); const int year = yearData.year; const int yearDay = yearData.secondsLeftThisYear / SecondsInDay; int leftover = yearData.secondsLeftThisYear % SecondsInDay; @@ -768,7 +781,7 @@ utility::string_t datetime::to_string(date_format format) const const int minute = leftover / SecondsInMinute; leftover = leftover % SecondsInMinute; - const auto& monthTable = year_is_leap_year(year) ? cumulative_days_to_month_leap : cumulative_days_to_month; + const auto& monthTable = year_is_leap_year_1601(year) ? cumulative_days_to_month_leap : cumulative_days_to_month; int month = 0; while (month < 11 && monthTable[month + 1] <= yearDay) { @@ -776,7 +789,7 @@ utility::string_t datetime::to_string(date_format format) const } const auto monthDay = yearDay - monthTable[month] + 1; - const auto weekday = static_cast((secondsSince1900 / SecondsInDay + 1) % 7); + const auto weekday = static_cast((secondsSince1601 / SecondsInDay + 1) % 7); char outBuffer[38]; // Thu, 01 Jan 1970 00:00:00 GMT\0 // 1970-01-01T00:00:00.1234567Z\0 @@ -791,7 +804,7 @@ utility::string_t datetime::to_string(date_format format) const dayNames + 4 * weekday, monthDay, monthNames + 4 * month, - year + 1900, + year + 1601, hour, minute, leftover); @@ -801,7 +814,7 @@ utility::string_t datetime::to_string(date_format format) const dayNames + 4 * weekday, monthDay, monthNames + 4 * month, - year + 1900, + year + 1601, hour, minute, leftover); @@ -815,7 +828,7 @@ utility::string_t datetime::to_string(date_format format) const sprintf_s(outCursor, 20, "%04d-%02d-%02dT%02d:%02d:%02d", - year + 1900, + year + 1601, month + 1, monthDay, hour, @@ -823,7 +836,7 @@ utility::string_t datetime::to_string(date_format format) const leftover); #else // ^^^ _MSC_VER // !_MSC_VER vvv sprintf( - outCursor, "%04d-%02d-%02dT%02d:%02d:%02d", year + 1900, month + 1, monthDay, hour, minute, leftover); + outCursor, "%04d-%02d-%02dT%02d:%02d:%02d", year + 1601, month + 1, monthDay, hour, minute, leftover); #endif // _MSC_VER outCursor += 19; if (fracSec != 0) @@ -889,12 +902,12 @@ static const unsigned char max_days_in_month[12] = { 31 // Dec }; -static bool validate_day_month(int day, int month, int year) +static bool validate_day_month_1601(int day, int month, int year) { int maxDaysThisMonth; if (month == 1) { // Feb needs leap year testing - maxDaysThisMonth = 28 + year_is_leap_year(year); + maxDaysThisMonth = 28 + year_is_leap_year_1601(year); } else { @@ -904,9 +917,9 @@ static bool validate_day_month(int day, int month, int year) return day >= 1 && day <= maxDaysThisMonth; } -static int get_year_day(int month, int monthDay, int year) +static int get_year_day_1601(int month, int monthDay, int year) { - return cumulative_days_to_month[month] + monthDay + (year_is_leap_year(year) && month > 1) - 1; + return cumulative_days_to_month[month] + monthDay + (year_is_leap_year_1601(year) && month > 1) - 1; } template @@ -985,11 +998,21 @@ zone = "UT" / "GMT" ; Universal Time ; hours+min. (HHMM) */ - datetime __cdecl datetime::from_string(const utility::string_t& dateString, date_format format) { - datetime result; - int64_t secondsSince1900; + auto result = from_string_maximum_error(dateString, format); + if (result == datetime::maximum()) + { + return datetime(); + } + + return result; +} + +datetime __cdecl datetime::from_string_maximum_error(const utility::string_t& dateString, date_format format) +{ + datetime result = datetime::maximum(); + int64_t secondsSince1601; uint64_t fracSec = 0; auto str = dateString.c_str(); if (format == RFC_1123) @@ -1056,19 +1079,21 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 + (str[3] - _XPLATSTR('0')); - if (year < 1900) + if (year < 1601) { return result; } + year -= 1601; + // days in month validity check - if (!validate_day_month(monthDay, month, year)) + if (!validate_day_month_1601(monthDay, month, year)) { return result; } str += 5; // parsed year - const int yearDay = get_year_day(month, monthDay, year); + const int yearDay = get_year_day_1601(month, monthDay, year); if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]) || str[2] != _XPLATSTR(':') || !ascii_isdigit5(str[3]) || !ascii_isdigit(str[4])) @@ -1112,12 +1137,11 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - year -= 1900; - int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay; + int daysSince1601 = year * DaysInYear + count_leap_years_1601(year) + yearDay; if (parsedWeekday != 7) { - const int actualWeekday = (daysSince1900 + 1) % 7; + const int actualWeekday = (daysSince1601 + 1) % 7; if (parsedWeekday != actualWeekday) { @@ -1125,8 +1149,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date } } - secondsSince1900 = - static_cast(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; + secondsSince1601 = + static_cast(daysSince1601) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; if (!string_starts_with(str, "GMT") && !string_starts_with(str, "UT")) { @@ -1166,8 +1190,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - secondsSince1900 = timezone_adjust(secondsSince1900, static_cast(tzCh), tzHours, tzMinutes); - if (secondsSince1900 < 0) + secondsSince1601 = timezone_adjust(secondsSince1601, static_cast(tzCh), tzHours, tzMinutes); + if (secondsSince1601 < 0) { return result; } @@ -1183,11 +1207,13 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 + (str[3] - _XPLATSTR('0')); - if (year < 1900) + if (year < 1601) { return result; } + year -= 1601; + str += 4; if (*str == _XPLATSTR('-')) { @@ -1221,24 +1247,22 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date } int monthDay = atoi2(str); - if (!validate_day_month(monthDay, month, year)) + if (!validate_day_month_1601(monthDay, month, year)) { return result; } - const int yearDay = get_year_day(month, monthDay, year); + const int yearDay = get_year_day_1601(month, monthDay, year); str += 2; - year -= 1900; - int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay; + int daysSince1601 = year * DaysInYear + count_leap_years_1601(year) + yearDay; if (str[0] != _XPLATSTR('T') && str[0] != _XPLATSTR('t')) { // No time - secondsSince1900 = static_cast(daysSince1900) * SecondsInDay; + secondsSince1601 = static_cast(daysSince1601) * SecondsInDay; - result.m_interval = - static_cast(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval); + result.m_interval = static_cast(secondsSince1601 * _secondTicks + fracSec); return result; } @@ -1327,8 +1351,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date } } - secondsSince1900 = - static_cast(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; + secondsSince1601 = + static_cast(daysSince1601) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; if (str[0] == _XPLATSTR('Z') || str[0] == _XPLATSTR('z')) { @@ -1343,8 +1367,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - secondsSince1900 = timezone_adjust(secondsSince1900, offsetDirection, atoi2(str + 1), atoi2(str + 4)); - if (secondsSince1900 < 0) + secondsSince1601 = timezone_adjust(secondsSince1601, offsetDirection, atoi2(str + 1), atoi2(str + 4)); + if (secondsSince1601 < 0) { return result; } @@ -1359,7 +1383,7 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date throw std::invalid_argument("unrecognized date format"); } - result.m_interval = static_cast(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval); + result.m_interval = static_cast(secondsSince1601 * _secondTicks + fracSec); return result; } diff --git a/Release/tests/functional/utils/datetime.cpp b/Release/tests/functional/utils/datetime.cpp index a584b1e3c2..22954ca942 100644 --- a/Release/tests/functional/utils/datetime.cpp +++ b/Release/tests/functional/utils/datetime.cpp @@ -10,6 +10,7 @@ ****/ #include "stdafx.h" + #include #include @@ -81,6 +82,10 @@ SUITE(datetime) auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601); utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601); VERIFY_ARE_EQUAL(str2, strExpected); + + auto dt_me = utility::datetime::from_string_maximum_error(str, utility::datetime::ISO_8601); + utility::string_t str3 = dt_me.to_string(utility::datetime::ISO_8601); + VERIFY_ARE_EQUAL(str3, strExpected); } void TestDateTimeRoundtrip(utility::string_t str) { TestDateTimeRoundtrip(str, str); } @@ -123,32 +128,26 @@ SUITE(datetime) TestDateTimeRoundtrip(_XPLATSTR("2013-11-19T14:30:59.5Z")); } - TEST(parsing_time_roundtrip_year_1900) - { - TestDateTimeRoundtrip(_XPLATSTR("1900-01-01T00:00:00Z")); - } + TEST(parsing_time_roundtrip_year_1900) { TestDateTimeRoundtrip(_XPLATSTR("1900-01-01T00:00:00Z")); } - TEST(parsing_time_roundtrip_year_9999) - { - TestDateTimeRoundtrip(_XPLATSTR("9999-12-31T23:59:59Z")); - } + TEST(parsing_time_roundtrip_year_9999) { TestDateTimeRoundtrip(_XPLATSTR("9999-12-31T23:59:59Z")); } - TEST(parsing_time_roundtrip_year_2016) - { - TestDateTimeRoundtrip(_XPLATSTR("2016-12-31T20:59:59Z")); - } + TEST(parsing_time_roundtrip_year_2016) { TestDateTimeRoundtrip(_XPLATSTR("2016-12-31T20:59:59Z")); } - TEST(parsing_time_roundtrip_year_2020) - { - TestDateTimeRoundtrip(_XPLATSTR("2020-12-31T20:59:59Z")); - } + TEST(parsing_time_roundtrip_year_2020) { TestDateTimeRoundtrip(_XPLATSTR("2020-12-31T20:59:59Z")); } - TEST(parsing_time_roundtrip_year_2021) - { - TestDateTimeRoundtrip(_XPLATSTR("2021-01-01T20:59:59Z")); - } + TEST(parsing_time_roundtrip_year_2021) { TestDateTimeRoundtrip(_XPLATSTR("2021-01-01T20:59:59Z")); } + + TEST(parsing_time_roundtrip_year_1601) { TestDateTimeRoundtrip(_XPLATSTR("1601-01-01T00:00:00Z")); } - TEST(emitting_time_correct_day) { + TEST(parsing_time_roundtrip_year_1602) { TestDateTimeRoundtrip(_XPLATSTR("1602-01-01T00:00:00Z")); } + + TEST(parsing_time_roundtrip_year_1603) { TestDateTimeRoundtrip(_XPLATSTR("1603-01-01T00:00:00Z")); } + + TEST(parsing_time_roundtrip_year_1604) { TestDateTimeRoundtrip(_XPLATSTR("1604-01-01T00:00:00Z")); } + + TEST(emitting_time_correct_day) + { const auto test = utility::datetime() + UINT64_C(132004507640000000); // 2019-04-22T23:52:44 is a Monday const auto actual = test.to_string(utility::datetime::RFC_1123); const utility::string_t expected(_XPLATSTR("Mon")); @@ -296,13 +295,13 @@ SUITE(datetime) _XPLATSTR("Thu, 01 Jan 1970 00:00:00 G"), _XPLATSTR("Thu, 01 Jan 1970 00:00:00 GM"), _XPLATSTR("Fri, 01 Jan 1970 00:00:00 GMT"), // wrong day - _XPLATSTR("01 Jan 1899 00:00:00 GMT"), // year too small - _XPLATSTR("01 Xxx 1971 00:00:00 GMT"), // month bad - _XPLATSTR("00 Jan 1971 00:00:00 GMT"), // day too small - _XPLATSTR("32 Jan 1971 00:00:00 GMT"), // day too big - _XPLATSTR("30 Feb 1971 00:00:00 GMT"), // day too big for feb - _XPLATSTR("30 Feb 1971 00:00:00 GMT"), // day too big for feb (non-leap year) - _XPLATSTR("32 Mar 1971 00:00:00 GMT"), // other months + _XPLATSTR("01 Jan 1600 00:00:00 GMT"), // year too small + _XPLATSTR("01 Xxx 1971 00:00:00 GMT"), // month bad + _XPLATSTR("00 Jan 1971 00:00:00 GMT"), // day too small + _XPLATSTR("32 Jan 1971 00:00:00 GMT"), // day too big + _XPLATSTR("30 Feb 1971 00:00:00 GMT"), // day too big for feb + _XPLATSTR("30 Feb 1971 00:00:00 GMT"), // day too big for feb (non-leap year) + _XPLATSTR("32 Mar 1971 00:00:00 GMT"), // other months _XPLATSTR("31 Apr 1971 00:00:00 GMT"), _XPLATSTR("32 May 1971 00:00:00 GMT"), _XPLATSTR("31 Jun 1971 00:00:00 GMT"), @@ -317,8 +316,8 @@ SUITE(datetime) _XPLATSTR("01 Jan 1971 00:60:00 GMT"), // minute too big _XPLATSTR("01 Jan 1971 00:00:70 GMT"), // second too big _XPLATSTR("01 Jan 1971 00:00:61 GMT"), - _XPLATSTR("01 Jan 1899 00:00:00 GMT"), // underflow - _XPLATSTR("01 Jan 1969 00:00:00 CEST"), // bad tz + _XPLATSTR("01 Jan 1600 00:00:00 GMT"), // underflow + _XPLATSTR("01 Jan 1969 00:00:00 CEST"), // bad tz _XPLATSTR("14 Jan 2019 23:16:21 G0100"), // bad tzoffsets _XPLATSTR("01 Jan 1970 00:00:00 +2400"), _XPLATSTR("01 Jan 1970 00:00:00 -3000"), @@ -332,6 +331,8 @@ SUITE(datetime) { auto dt = utility::datetime::from_string(str, utility::datetime::RFC_1123); VERIFY_ARE_EQUAL(0, dt.to_interval()); + auto dt_me = utility::datetime::from_string_maximum_error(str, utility::datetime::RFC_1123); + VERIFY_ARE_EQUAL(utility::datetime::maximum(), dt_me); } } @@ -461,7 +462,7 @@ SUITE(datetime) _XPLATSTR("1970-01-01T00:00:"), _XPLATSTR("1970-01-01T00:00:0"), // _XPLATSTR("1970-01-01T00:00:00"), // accepted as invalid timezone above - _XPLATSTR("1899-01-01T00:00:00Z"), // year too small + _XPLATSTR("1600-01-01T00:00:00Z"), // year too small _XPLATSTR("1971-00-01T00:00:00Z"), // month too small _XPLATSTR("1971-20-01T00:00:00Z"), // month too big _XPLATSTR("1971-13-01T00:00:00Z"), @@ -484,8 +485,8 @@ SUITE(datetime) _XPLATSTR("1971-01-01T00:60:00Z"), // minute too big _XPLATSTR("1971-01-01T00:00:70Z"), // second too big _XPLATSTR("1971-01-01T00:00:61Z"), - _XPLATSTR("1899-01-01T00:00:00Z"), // underflow - _XPLATSTR("1900-01-01T00:00:00+00:01"), // time zone underflow + _XPLATSTR("1600-01-01T00:00:00Z"), // underflow + _XPLATSTR("1601-01-01T00:00:00+00:01"), // time zone underflow // _XPLATSTR("1970-01-01T00:00:00.Z"), // accepted as invalid timezone above _XPLATSTR("1970-01-01T00:00:00+24:00"), // bad tzoffsets _XPLATSTR("1970-01-01T00:00:00-30:00"), @@ -499,9 +500,55 @@ SUITE(datetime) { auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601); VERIFY_ARE_EQUAL(dt.to_interval(), 0); + auto dt_me = utility::datetime::from_string_maximum_error(str, utility::datetime::ISO_8601); + VERIFY_ARE_EQUAL(dt_me, utility::datetime::maximum()); } } + TEST(can_emit_nt_epoch_zero_rfc_1123) + { + auto result = utility::datetime {}.to_string(utility::datetime::RFC_1123); + VERIFY_ARE_EQUAL(_XPLATSTR("Mon, 01 Jan 1601 00:00:00 GMT"), result); + } + + TEST(can_emit_nt_epoch_zero_iso_8601) + { + auto result = utility::datetime {}.to_string(utility::datetime::ISO_8601); + VERIFY_ARE_EQUAL(_XPLATSTR("1601-01-01T00:00:00Z"), result); + } + + TEST(can_emit_year_9999_rfc_1123) + { + auto result = + utility::datetime::from_interval(INT64_C(0x24C85A5ED1C018F0)).to_string(utility::datetime::RFC_1123); + VERIFY_ARE_EQUAL(_XPLATSTR("Fri, 31 Dec 9999 23:59:59 GMT"), result); + } + + TEST(can_emit_year_9999_iso_8601) + { + auto result = + utility::datetime::from_interval(INT64_C(0x24C85A5ED1C018F0)).to_string(utility::datetime::ISO_8601); + VERIFY_ARE_EQUAL(_XPLATSTR("9999-12-31T23:59:59.999Z"), result); + } + + TEST(can_parse_nt_epoch_zero_rfc_1123) + { + auto dt = + utility::datetime::from_string(_XPLATSTR("Mon, 01 Jan 1601 00:00:00 GMT"), utility::datetime::RFC_1123); + VERIFY_ARE_EQUAL(0U, dt.to_interval()); + auto dt_me = utility::datetime::from_string_maximum_error(_XPLATSTR("Mon, 01 Jan 1601 00:00:00 GMT"), + utility::datetime::RFC_1123); + VERIFY_ARE_EQUAL(0U, dt_me.to_interval()); + } + + TEST(can_parse_nt_epoch_zero_iso_8601) + { + auto dt = utility::datetime::from_string(_XPLATSTR("1601-01-01T00:00:00Z"), utility::datetime::ISO_8601); + VERIFY_ARE_EQUAL(0U, dt.to_interval()); + auto dt_me = utility::datetime::from_string_maximum_error(_XPLATSTR("1601-01-01T00:00:00Z"), + utility::datetime::ISO_8601); + VERIFY_ARE_EQUAL(0U, dt_me.to_interval()); + } } // SUITE(datetime) } // namespace utils_tests diff --git a/vcpkg b/vcpkg index 6709d3d7d0..b759049a36 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 6709d3d7d0cba96508ba3606f810ab562ea32556 +Subproject commit b759049a36728d18260963799a56e6b19cb4a2ef