diff --git a/Release/src/utilities/asyncrt_utils.cpp b/Release/src/utilities/asyncrt_utils.cpp index a2fe96b81e..7ab3463a33 100644 --- a/Release/src/utilities/asyncrt_utils.cpp +++ b/Release/src/utilities/asyncrt_utils.cpp @@ -618,7 +618,81 @@ std::string __cdecl conversions::to_utf8string(const utf16string& value) { retur utf16string __cdecl conversions::to_utf16string(const std::string& value) { return utf8_to_utf16(value); } -static const int64_t ntToUnixOffsetSeconds = 11644473600; // diff between windows and unix epochs (seconds) +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 const int SecondsInMinute = 60; +static const int SecondsInHour = SecondsInMinute * 60; +static const int SecondsInDay = SecondsInHour * 24; + +static const int DaysInYear = 365; +static const int DaysIn4Years = DaysInYear * 4 + 1; +static const int DaysIn100Years = DaysIn4Years * 25 - 1; +static const int DaysIn400Years = DaysIn100Years * 4 + 1; + +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(int yearsSince1900) +{ + int result = 0; + if (yearsSince1900 > 101) + { + result += 25; + yearsSince1900 -= 101; + } + + int year400 = yearsSince1900 / 400; + yearsSince1900 -= year400 * 400; + result += year400 * 97; + + int year100 = yearsSince1900 / 100; + yearsSince1900 -= year100 * 100; + result += year100 * 24; + + int year4 = yearsSince1900 / 4; + yearsSince1900 -= year4 * 4; + result += year4; + + return result; +} + +// The following table assumes no leap year; leap year is added separately +static const unsigned short cumulative_days_to_month[12] = { + 0, // Jan + 31, // Feb + 59, // Mar + 90, // Apr + 120, // May + 151, // Jun + 181, // Jul + 212, // Aug + 243, // Sep + 273, // Oct + 304, // Nov + 334 // Dec +}; + +static const unsigned short cumulative_days_to_month_leap[12] = { + 0, // Jan + 31, // Feb + 60, // Mar + 91, // Apr + 121, // May + 152, // Jun + 182, // Jul + 213, // Aug + 244, // Sep + 274, // Oct + 305, // Nov + 335 // Dec +}; datetime __cdecl datetime::utc_now() { @@ -634,7 +708,7 @@ datetime __cdecl datetime::utc_now() #else // LINUX struct timeval time; gettimeofday(&time, nullptr); - int64_t result = ntToUnixOffsetSeconds + time.tv_sec; + int64_t result = NtToUnixOffsetSeconds + time.tv_sec; result *= _secondTicks; // convert to 10e-7 result += time.tv_usec * 10; // convert and add microseconds, 10e-6 to 10e-7 return datetime(static_cast(result)); @@ -644,25 +718,69 @@ datetime __cdecl datetime::utc_now() static const char dayNames[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat"; static const char monthNames[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec"; +struct compute_year_result +{ + int year; + int secondsLeftThisYear; +}; + +static compute_year_result compute_year(int64_t secondsSince1900) +{ + int year = 0; + int64_t secondsLeft = secondsSince1900; + if (secondsSince1900 >= SecondsFrom1900To2001) + { + // After year 2001, shift there and start normal 400 year cycle + year += 101; + secondsLeft -= SecondsFrom1900To2001; + } + + int year400 = static_cast(secondsLeft / SecondsIn400Years); + secondsLeft -= year400 * SecondsIn400Years; + + int year100 = static_cast(secondsLeft / SecondsIn100Years); + secondsLeft -= year100 * SecondsIn100Years; + + int year4 = static_cast(secondsLeft / SecondsIn4Years); + int secondsInt = static_cast(secondsLeft - year4 * SecondsIn4Years); + + int year1 = secondsInt / SecondsInYear; + secondsInt -= year1 * SecondsInYear; + + year += year400 * 400 + year100 * 100 + year4 * 4 + year1; + return {year, secondsInt}; +} + utility::string_t datetime::to_string(date_format format) const { - const int64_t input = static_cast(m_interval / _secondTicks); // convert to seconds - const int frac_sec = static_cast(m_interval % _secondTicks); - const time_t time = static_cast(input - ntToUnixOffsetSeconds); - if (static_cast(time) > 253370764800ull) { + if (m_interval > INT64_C(2650467743990000000)) + { throw std::out_of_range("The requested year exceeds the year 9999."); } - struct tm t; -#ifdef _MSC_VER - if (gmtime_s(&t, &time) != 0) -#else // ^^^ _MSC_VER ^^^ // vvv !_MSC_VER vvv - if (gmtime_r(&time, &t) == 0) -#endif // _MSC_VER + const int64_t epochAdjusted = static_cast(m_interval) - NtTo1900OffsetInterval; + const int64_t secondsSince1900 = static_cast(epochAdjusted / _secondTicks); // convert to seconds + const int fracSec = static_cast(epochAdjusted % _secondTicks); + + const auto yearData = compute_year(secondsSince1900); + const int year = yearData.year; + const int yearDay = yearData.secondsLeftThisYear / SecondsInDay; + int leftover = yearData.secondsLeftThisYear % SecondsInDay; + const int hour = leftover / SecondsInHour; + leftover = leftover % SecondsInHour; + 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; + int month = 0; + while (month < 11 && monthTable[month + 1] <= yearDay) { - throw std::invalid_argument("gmtime_r/s failed on the time supplied"); + ++month; } + const auto monthDay = yearDay - monthTable[month] + 1; + const auto weekday = static_cast((secondsSince1900 / SecondsInDay + 3) % 7); + char outBuffer[38]; // Thu, 01 Jan 1970 00:00:00 GMT\0 // 1970-01-01T00:00:00.1234567Z\0 char* outCursor = outBuffer; @@ -673,23 +791,23 @@ utility::string_t datetime::to_string(date_format format) const sprintf_s(outCursor, 26, "%s, %02d %s %04d %02d:%02d:%02d", - dayNames + 4 * t.tm_wday, - t.tm_mday, - monthNames + 4 * t.tm_mon, - t.tm_year + 1900, - t.tm_hour, - t.tm_min, - t.tm_sec); + dayNames + 4 * weekday, + monthDay, + monthNames + 4 * month, + year + 1900, + hour, + minute, + leftover); #else // ^^^ _MSC_VER // !_MSC_VER vvv sprintf(outCursor, "%s, %02d %s %04d %02d:%02d:%02d", - dayNames + 4 * t.tm_wday, - t.tm_mday, - monthNames + 4 * t.tm_mon, - t.tm_year + 1900, - t.tm_hour, - t.tm_min, - t.tm_sec); + dayNames + 4 * weekday, + monthDay, + monthNames + 4 * month, + year + 1900, + hour, + minute, + leftover); #endif // _MSC_VER outCursor += 25; memcpy(outCursor, " GMT", 4); @@ -700,31 +818,25 @@ utility::string_t datetime::to_string(date_format format) const sprintf_s(outCursor, 20, "%04d-%02d-%02dT%02d:%02d:%02d", - t.tm_year + 1900, - t.tm_mon + 1, - t.tm_mday, - t.tm_hour, - t.tm_min, - t.tm_sec); + year + 1900, + month + 1, + monthDay, + hour, + minute, + leftover); #else // ^^^ _MSC_VER // !_MSC_VER vvv - sprintf(outCursor, - "%04d-%02d-%02dT%02d:%02d:%02d", - t.tm_year + 1900, - t.tm_mon + 1, - t.tm_mday, - t.tm_hour, - t.tm_min, - t.tm_sec); + sprintf( + outCursor, "%04d-%02d-%02dT%02d:%02d:%02d", year + 1900, month + 1, monthDay, hour, minute, leftover); #endif // _MSC_VER outCursor += 19; - if (frac_sec != 0) + if (fracSec != 0) { // Append fractional second, which is a 7-digit value with no trailing zeros // This way, '1200' becomes '00012' #ifdef _MSC_VER - size_t appended = sprintf_s(outCursor, 9, ".%07d", frac_sec); + size_t appended = sprintf_s(outCursor, 9, ".%07d", fracSec); #else // ^^^ _MSC_VER // !_MSC_VER vvv - size_t appended = sprintf(outCursor, ".%07d", frac_sec); + size_t appended = sprintf(outCursor, ".%07d", fracSec); #endif // _MSC_VER while (outCursor[appended - 1] == '0') { @@ -785,7 +897,7 @@ static bool validate_day_month(int day, int month, int year) int maxDaysThisMonth; if (month == 1) { // Feb needs leap year testing - maxDaysThisMonth = 28 + (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + maxDaysThisMonth = 28 + year_is_leap_year(year); } else { @@ -795,6 +907,11 @@ 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) +{ + return cumulative_days_to_month[month] + monthDay + (year_is_leap_year(year) && month > 1) - 1; +} + template static int atoi2(const CharT* str) { @@ -832,49 +949,6 @@ static int64_t timezone_adjust(int64_t result, unsigned char chSign, int adjustH return result; } -static int64_t make_gm_time(struct tm* t) -{ -#ifdef _MSC_VER - return static_cast(_mkgmtime(t)); -#elif (defined(ANDROID) || defined(__ANDROID__)) - // HACK: The (nonportable?) POSIX function timegm is not available in - // bionic. As a workaround[1][2], we set the C library timezone to - // UTC, call mktime, then set the timezone back. However, the C - // environment is fundamentally a shared global resource and thread- - // unsafe. We can protect our usage here, however any other code might - // manipulate the environment at the same time. - // - // [1] http://linux.die.net/man/3/timegm - // [2] http://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html - time_t time; - static boost::mutex env_var_lock; - { - boost::lock_guard lock(env_var_lock); - std::string prev_env; - auto prev_env_cstr = getenv("TZ"); - if (prev_env_cstr != nullptr) - { - prev_env = prev_env_cstr; - } - setenv("TZ", "UTC", 1); - - time = mktime(t); - - if (prev_env_cstr) - { - setenv("TZ", prev_env.c_str(), 1); - } - else - { - unsetenv("TZ"); - } - } - return static_cast(time); -#else // ^^^ ANDROID // Other POSIX platforms vvv - return static_cast(timegm(t)); -#endif // _MSC_VER -} - /* https://tools.ietf.org/html/rfc822 https://tools.ietf.org/html/rfc1123 @@ -918,31 +992,31 @@ zone = "UT" / "GMT" ; Universal Time datetime __cdecl datetime::from_string(const utility::string_t& dateString, date_format format) { datetime result; - int64_t seconds; - uint64_t frac_sec = 0; - struct tm t{}; + int64_t secondsSince1900; + uint64_t fracSec = 0; auto str = dateString.c_str(); if (format == RFC_1123) { - int parsedWeekday = -1; - for (int day = 0; day < 7; ++day) + int parsedWeekday = 0; + for (; parsedWeekday < 7; ++parsedWeekday) { - if (string_starts_with(str, dayNames + day * 4) && str[3] == _XPLATSTR(',') && str[4] == _XPLATSTR(' ')) + if (string_starts_with(str, dayNames + parsedWeekday * 4) && str[3] == _XPLATSTR(',') && + str[4] == _XPLATSTR(' ')) { - parsedWeekday = day; str += 5; // parsed day of week break; } } + int monthDay; if (ascii_isdigit3(str[0]) && ascii_isdigit(str[1]) && str[2] == _XPLATSTR(' ')) { - t.tm_mday = atoi2(str); // validity checked later - str += 3; // parsed day + monthDay = atoi2(str); // validity checked later + str += 3; // parsed day } else if (ascii_isdigit(str[0]) && str[1] == _XPLATSTR(' ')) { - t.tm_mday = str[0] - _XPLATSTR('0'); + monthDay = str[0] - _XPLATSTR('0'); str += 2; // parsed day } else @@ -950,44 +1024,54 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - t.tm_mon = -1; - for (int month = 0; month < 12; ++month) + if (monthDay == 0) + { + return result; + } + + int month = 0; + for (;;) { if (string_starts_with(str, monthNames + month * 4)) { - t.tm_mon = month; break; } + + ++month; + if (month == 12) + { + return result; + } } - if (t.tm_mon == -1 || str[3] != _XPLATSTR(' ')) + if (str[3] != _XPLATSTR(' ')) { return result; } str += 4; // parsed month - if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]) || + if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]) || str[4] != ' ') { return result; } - t.tm_year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + - (str[2] - _XPLATSTR('0')) * 10 + (str[3] - _XPLATSTR('0')); - if (t.tm_year < 1970 || t.tm_year > 3000) + int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 + + (str[3] - _XPLATSTR('0')); + if (year < 1900) { return result; } // days in month validity check - if (!validate_day_month(t.tm_mday, t.tm_mon, t.tm_year)) + if (!validate_day_month(monthDay, month, year)) { return result; } - t.tm_year -= 1900; str += 5; // parsed year + const int yearDay = get_year_day(month, monthDay, year); if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]) || str[2] != _XPLATSTR(':') || !ascii_isdigit5(str[3]) || !ascii_isdigit(str[4])) @@ -995,16 +1079,17 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - t.tm_hour = atoi2(str); - if (t.tm_hour > 23) + const int hour = atoi2(str); + if (hour > 23) { return result; } str += 3; // parsed hour - t.tm_min = atoi2(str); + const int minute = atoi2(str); str += 2; // parsed mins + int sec; if (str[0] == ':') { if (!ascii_isdigit6(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(' ')) @@ -1012,12 +1097,12 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - t.tm_sec = atoi2(str + 1); + sec = atoi2(str + 1); str += 4; // parsed seconds } else if (str[0] == _XPLATSTR(' ')) { - t.tm_sec = 0; + sec = 0; str += 1; // parsed seconds } else @@ -1025,23 +1110,27 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - if (t.tm_sec > 60) + if (sec > 60) { // 60 to allow leap seconds return result; } - t.tm_isdst = 0; - seconds = make_gm_time(&t); - if (seconds < 0) - { - return result; - } + year -= 1900; + int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay; - if (parsedWeekday >= 0 && parsedWeekday != t.tm_wday) + if (parsedWeekday != 7) { - return result; + const int actualWeekday = (daysSince1900 + 1) % 7; + + if (parsedWeekday != actualWeekday) + { + return result; + } } + secondsSince1900 = + static_cast(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; + if (!string_starts_with(str, "GMT") && !string_starts_with(str, "UT")) { // some timezone adjustment necessary @@ -1080,8 +1169,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - seconds = timezone_adjust(seconds, static_cast(tzCh), tzHours, tzMinutes); - if (seconds < 0) + secondsSince1900 = timezone_adjust(secondsSince1900, static_cast(tzCh), tzHours, tzMinutes); + if (secondsSince1900 < 0) { return result; } @@ -1090,14 +1179,14 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date else if (format == ISO_8601) { // parse year - if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3])) + if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3])) { return result; } - t.tm_year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + - (str[2] - _XPLATSTR('0')) * 10 + (str[3] - _XPLATSTR('0')); - if (t.tm_year < 1970 || t.tm_year > 3000) + int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 + + (str[3] - _XPLATSTR('0')); + if (year < 1900) { return result; } @@ -1114,13 +1203,13 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - t.tm_mon = atoi2(str); - if (t.tm_mon < 1 || t.tm_mon > 12) + int month = atoi2(str); + if (month < 1 || month > 12) { return result; } - t.tm_mon -= 1; + month -= 1; str += 2; if (*str == _XPLATSTR('-')) @@ -1134,26 +1223,25 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - t.tm_mday = atoi2(str); - if (!validate_day_month(t.tm_mday, t.tm_mon, t.tm_year)) + int monthDay = atoi2(str); + if (!validate_day_month(monthDay, month, year)) { return result; } - t.tm_year -= 1900; + const int yearDay = get_year_day(month, monthDay, year); + str += 2; + year -= 1900; + int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay; if (str[0] != _XPLATSTR('T') && str[0] != _XPLATSTR('t')) { // No time - seconds = make_gm_time(&t); - if (seconds < 0) - { - return result; - } + secondsSince1900 = static_cast(daysSince1900) * SecondsInDay; - seconds += ntToUnixOffsetSeconds; - result.m_interval = static_cast(seconds) * _secondTicks; + result.m_interval = + static_cast(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval); return result; } @@ -1165,9 +1253,9 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - t.tm_hour = atoi2(str); + const int hour = atoi2(str); str += 2; - if (t.tm_hour > 23) + if (hour > 23) { return result; } @@ -1182,8 +1270,9 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date { return result; } - t.tm_min = atoi2(str); - // t.tm_min > 59 is impossible because we checked that the first digit is <= 5 in the basic format + + const int minute = atoi2(str); + // minute > 59 is impossible because we checked that the first digit is <= 5 in the basic format // check above str += 2; @@ -1199,9 +1288,9 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - t.tm_sec = atoi2(str); + const int sec = atoi2(str); // We allow 60 to account for leap seconds - if (t.tm_sec > 60) + if (sec > 60) { return result; } @@ -1213,8 +1302,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date int digits = 7; for (;;) { - frac_sec *= 10; - frac_sec += *str - _XPLATSTR('0'); + fracSec *= 10; + fracSec += *str - _XPLATSTR('0'); --digits; ++str; if (digits == 0) @@ -1233,7 +1322,7 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date // no more digits in the input, do the remaining multiplies we need for (; digits != 0; --digits) { - frac_sec *= 10; + fracSec *= 10; } break; @@ -1241,11 +1330,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date } } - seconds = make_gm_time(&t); - if (seconds < 0) - { - return result; - } + secondsSince1900 = + static_cast(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; if (str[0] == _XPLATSTR('Z') || str[0] == _XPLATSTR('z')) { @@ -1260,8 +1346,8 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date return result; } - seconds = timezone_adjust(seconds, offsetDirection, atoi2(str + 1), atoi2(str + 4)); - if (seconds < 0) + secondsSince1900 = timezone_adjust(secondsSince1900, offsetDirection, atoi2(str + 1), atoi2(str + 4)); + if (secondsSince1900 < 0) { return result; } @@ -1276,8 +1362,7 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date throw std::invalid_argument("unrecognized date format"); } - seconds += ntToUnixOffsetSeconds; - result.m_interval = static_cast(seconds) * _secondTicks + frac_sec; + result.m_interval = static_cast(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval); return result; } @@ -1403,8 +1488,9 @@ utility::string_t nonce_generator::generate() std::uniform_int_distribution<> distr(0, chars_count - 1); utility::string_t result; result.reserve(length()); - std::generate_n(std::back_inserter(result), length(), - [&] { return static_cast(c_allowed_chars[distr(m_random)]); }); + std::generate_n(std::back_inserter(result), length(), [&] { + return static_cast(c_allowed_chars[distr(m_random)]); + }); return result; } diff --git a/Release/tests/functional/http/listener/request_handler_tests.cpp b/Release/tests/functional/http/listener/request_handler_tests.cpp index f0442d0a9f..afd9142eb7 100644 --- a/Release/tests/functional/http/listener/request_handler_tests.cpp +++ b/Release/tests/functional/http/listener/request_handler_tests.cpp @@ -445,14 +445,10 @@ SUITE(request_handler_tests) headers[U("Request")] = U("Upload"); headers[U("ImgNr")] = U("1"); - utility::char_t* pdata = new utility::char_t[nbytes]; - // this help recognizing the leaked memory in the CRT/VLD dump + std::string data; for (int j = 0; j < nbytes; j++) - pdata[j] = U('a') + (j % 26); - std::string data(pdata, pdata + nbytes); - delete[] pdata; - + data.push_back('a' + (j % 26)); VERIFY_ARE_EQUAL(0, p_client->request(methods::PUT, U("/path1"), headers, data)); p_client->next_response() .then([](test_response* p_response) { diff --git a/Release/tests/functional/http/utilities/test_http_server.cpp b/Release/tests/functional/http/utilities/test_http_server.cpp index 48a5d59115..e3a96570ab 100644 --- a/Release/tests/functional/http/utilities/test_http_server.cpp +++ b/Release/tests/functional/http/utilities/test_http_server.cpp @@ -404,6 +404,7 @@ class _test_http_server HTTP_RESPONSE response; ZeroMemory(&response, sizeof(HTTP_RESPONSE)); response.StatusCode = status_code; +#pragma warning(suppress: 4244) // intentionally narrow wchar_t -> char std::string reason(reason_phrase.begin(), reason_phrase.end()); response.pReason = reason.c_str(); response.ReasonLength = (USHORT)reason.length(); diff --git a/Release/tests/functional/utils/datetime.cpp b/Release/tests/functional/utils/datetime.cpp index acd6fddb02..871dc3462a 100644 --- a/Release/tests/functional/utils/datetime.cpp +++ b/Release/tests/functional/utils/datetime.cpp @@ -121,7 +121,17 @@ SUITE(datetime) TestDateTimeRoundtrip(_XPLATSTR("2013-11-19T14:30:59.5Z")); } - void TestRfc1123IsTimeT(const utility::char_t* str, time_t t) + 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")); + } + + void TestRfc1123IsTimeT(const utility::char_t* str, uint64_t t) { datetime dt = datetime::from_string(str, utility::datetime::RFC_1123); uint64_t interval = dt.to_interval(); @@ -133,77 +143,71 @@ SUITE(datetime) TEST(parsing_time_rfc1123_accepts_each_day) { - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:00 GMT"), (time_t)0); - TestRfc1123IsTimeT(_XPLATSTR("Fri, 02 Jan 1970 00:00:00 GMT"), (time_t)86400 * 1); - TestRfc1123IsTimeT(_XPLATSTR("Sat, 03 Jan 1970 00:00:00 GMT"), (time_t)86400 * 2); - TestRfc1123IsTimeT(_XPLATSTR("Sun, 04 Jan 1970 00:00:00 GMT"), (time_t)86400 * 3); - TestRfc1123IsTimeT(_XPLATSTR("Mon, 05 Jan 1970 00:00:00 GMT"), (time_t)86400 * 4); - TestRfc1123IsTimeT(_XPLATSTR("Tue, 06 Jan 1970 00:00:00 GMT"), (time_t)86400 * 5); - TestRfc1123IsTimeT(_XPLATSTR("Wed, 07 Jan 1970 00:00:00 GMT"), (time_t)86400 * 6); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:00 GMT"), 0); + TestRfc1123IsTimeT(_XPLATSTR("Fri, 02 Jan 1970 00:00:00 GMT"), 86400 * 1); + TestRfc1123IsTimeT(_XPLATSTR("Sat, 03 Jan 1970 00:00:00 GMT"), 86400 * 2); + TestRfc1123IsTimeT(_XPLATSTR("Sun, 04 Jan 1970 00:00:00 GMT"), 86400 * 3); + TestRfc1123IsTimeT(_XPLATSTR("Mon, 05 Jan 1970 00:00:00 GMT"), 86400 * 4); + TestRfc1123IsTimeT(_XPLATSTR("Tue, 06 Jan 1970 00:00:00 GMT"), 86400 * 5); + TestRfc1123IsTimeT(_XPLATSTR("Wed, 07 Jan 1970 00:00:00 GMT"), 86400 * 6); } TEST(parsing_time_rfc1123_boundary_cases) { - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:00 GMT"), (time_t)0); - TestRfc1123IsTimeT(_XPLATSTR("19 Jan 2038 03:14:06 GMT"), (time_t)INT_MAX - 1); - if (sizeof(time_t) == 8) - { - TestRfc1123IsTimeT(_XPLATSTR("19 Jan 2038 03:13:07 -0001"), (time_t)INT_MAX); - TestRfc1123IsTimeT(_XPLATSTR("19 Jan 2038 03:14:07 -0000"), (time_t)INT_MAX); - } - TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 +0000"), (time_t)1547507781); - TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 -0001"), (time_t)1547507841); - TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 +0001"), (time_t)1547507721); - TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 -0100"), (time_t)1547511381); - TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 +0100"), (time_t)1547504181); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:00 GMT"), 0); + TestRfc1123IsTimeT(_XPLATSTR("19 Jan 2038 03:14:06 GMT"), INT_MAX - 1); + TestRfc1123IsTimeT(_XPLATSTR("19 Jan 2038 03:13:07 -0001"), INT_MAX); + TestRfc1123IsTimeT(_XPLATSTR("19 Jan 2038 03:14:07 -0000"), INT_MAX); + TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 +0000"), 1547507781); + TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 -0001"), 1547507841); + TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 +0001"), 1547507721); + TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 -0100"), 1547511381); + TestRfc1123IsTimeT(_XPLATSTR("14 Jan 2019 23:16:21 +0100"), 1547504181); } TEST(parsing_time_rfc1123_uses_each_field) { - TestRfc1123IsTimeT(_XPLATSTR("02 Jan 1970 00:00:00 GMT"), (time_t)86400); - TestRfc1123IsTimeT(_XPLATSTR("12 Jan 1970 00:00:00 GMT"), (time_t)950400); - TestRfc1123IsTimeT(_XPLATSTR("01 Feb 1970 00:00:00 GMT"), (time_t)2678400); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 2000 00:00:00 GMT"), (time_t)946684800); - if (sizeof(time_t) == 8) - { - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 2100 00:00:00 GMT"), (time_t)4102444800); - } - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1990 00:00:00 GMT"), (time_t)631152000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1971 00:00:00 GMT"), (time_t)31536000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 10:00:00 GMT"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 01:00:00 GMT"), (time_t)3600); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:10:00 GMT"), (time_t)600); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:01:00 GMT"), (time_t)60); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:10 GMT"), (time_t)10); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:01 GMT"), (time_t)1); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 10:00:00 GMT"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 02:00:00 PST"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 03:00:00 PDT"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 03:00:00 MST"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 04:00:00 MDT"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 04:00:00 CST"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 05:00:00 CDT"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 05:00:00 EST"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 06:00:00 EDT"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 06:00:00 -0400"), (time_t)36000); - TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 05:59:00 -0401"), (time_t)36000); + TestRfc1123IsTimeT(_XPLATSTR("02 Jan 1970 00:00:00 GMT"), 86400); + TestRfc1123IsTimeT(_XPLATSTR("12 Jan 1970 00:00:00 GMT"), 950400); + TestRfc1123IsTimeT(_XPLATSTR("01 Feb 1970 00:00:00 GMT"), 2678400); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 2000 00:00:00 GMT"), 946684800); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 2100 00:00:00 GMT"), 4102444800); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1990 00:00:00 GMT"), 631152000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1971 00:00:00 GMT"), 31536000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 10:00:00 GMT"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 01:00:00 GMT"), 3600); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:10:00 GMT"), 600); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:01:00 GMT"), 60); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:10 GMT"), 10); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 00:00:01 GMT"), 1); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 10:00:00 GMT"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 02:00:00 PST"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 03:00:00 PDT"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 03:00:00 MST"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 04:00:00 MDT"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 04:00:00 CST"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 05:00:00 CDT"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 05:00:00 EST"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 06:00:00 EDT"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 06:00:00 -0400"), 36000); + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 1970 05:59:00 -0401"), 36000); } TEST(parsing_time_rfc1123_max_days) { - TestRfc1123IsTimeT(_XPLATSTR("31 Jan 1970 00:00:00 GMT"), (time_t)2592000); - TestRfc1123IsTimeT(_XPLATSTR("28 Feb 2019 00:00:00 GMT"), (time_t)1551312000); // non leap year allows feb 28 - TestRfc1123IsTimeT(_XPLATSTR("29 Feb 2020 00:00:00 GMT"), (time_t)1582934400); // leap year allows feb 29 - TestRfc1123IsTimeT(_XPLATSTR("31 Mar 1970 00:00:00 GMT"), (time_t)7689600); - TestRfc1123IsTimeT(_XPLATSTR("30 Apr 1970 00:00:00 GMT"), (time_t)10281600); - TestRfc1123IsTimeT(_XPLATSTR("31 May 1970 00:00:00 GMT"), (time_t)12960000); - TestRfc1123IsTimeT(_XPLATSTR("30 Jun 1970 00:00:00 GMT"), (time_t)15552000); - TestRfc1123IsTimeT(_XPLATSTR("31 Jul 1970 00:00:00 GMT"), (time_t)18230400); - TestRfc1123IsTimeT(_XPLATSTR("31 Aug 1970 00:00:00 GMT"), (time_t)20908800); - TestRfc1123IsTimeT(_XPLATSTR("30 Sep 1970 00:00:00 GMT"), (time_t)23500800); - TestRfc1123IsTimeT(_XPLATSTR("31 Oct 1970 00:00:00 GMT"), (time_t)26179200); - TestRfc1123IsTimeT(_XPLATSTR("30 Nov 1970 00:00:00 GMT"), (time_t)28771200); - TestRfc1123IsTimeT(_XPLATSTR("31 Dec 1970 00:00:00 GMT"), (time_t)31449600); + TestRfc1123IsTimeT(_XPLATSTR("31 Jan 1970 00:00:00 GMT"), 2592000); + TestRfc1123IsTimeT(_XPLATSTR("28 Feb 2019 00:00:00 GMT"), 1551312000); // non leap year allows feb 28 + TestRfc1123IsTimeT(_XPLATSTR("29 Feb 2020 00:00:00 GMT"), 1582934400); // leap year allows feb 29 + TestRfc1123IsTimeT(_XPLATSTR("31 Mar 1970 00:00:00 GMT"), 7689600); + TestRfc1123IsTimeT(_XPLATSTR("30 Apr 1970 00:00:00 GMT"), 10281600); + TestRfc1123IsTimeT(_XPLATSTR("31 May 1970 00:00:00 GMT"), 12960000); + TestRfc1123IsTimeT(_XPLATSTR("30 Jun 1970 00:00:00 GMT"), 15552000); + TestRfc1123IsTimeT(_XPLATSTR("31 Jul 1970 00:00:00 GMT"), 18230400); + TestRfc1123IsTimeT(_XPLATSTR("31 Aug 1970 00:00:00 GMT"), 20908800); + TestRfc1123IsTimeT(_XPLATSTR("30 Sep 1970 00:00:00 GMT"), 23500800); + TestRfc1123IsTimeT(_XPLATSTR("31 Oct 1970 00:00:00 GMT"), 26179200); + TestRfc1123IsTimeT(_XPLATSTR("30 Nov 1970 00:00:00 GMT"), 28771200); + TestRfc1123IsTimeT(_XPLATSTR("31 Dec 1970 00:00:00 GMT"), 31449600); } TEST(parsing_time_rfc1123_invalid_cases) @@ -268,8 +272,7 @@ 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 4970 00:00:00 GMT"), // year too big - _XPLATSTR("01 Jan 3001 00:00:00 GMT"), + _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 @@ -290,13 +293,14 @@ 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 1969 00:00:00 GMT"), // underflow - _XPLATSTR("01 Jan 1969 00:00:00 CEST"), // bad tz + _XPLATSTR("01 Jan 1899 00:00:00 GMT"), // underflow + _XPLATSTR("01 Jan 1969 00:00:00 CEST"), // bad tz _XPLATSTR("01 Jan 1970 00:00:00 +2400"), // bad tzoffsets _XPLATSTR("01 Jan 1970 00:00:00 -3000"), _XPLATSTR("01 Jan 1970 00:00:00 +2160"), _XPLATSTR("01 Jan 1970 00:00:00 -2400"), _XPLATSTR("01 Jan 1970 00:00:00 -2160"), + _XPLATSTR("00 Jan 1971 00:00:00 GMT"), // zero month day }; for (const auto& str : bad_strings) @@ -311,12 +315,9 @@ SUITE(datetime) // boundary cases: TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T00:00:00Z")); // epoch TestDateTimeRoundtrip(_XPLATSTR("2038-01-19T03:14:06+00:00"), _XPLATSTR("2038-01-19T03:14:06Z")); // INT_MAX - 1 - if (sizeof(time_t) == 8) - { - TestDateTimeRoundtrip(_XPLATSTR("2038-01-19T03:13:07-00:01"), - _XPLATSTR("2038-01-19T03:14:07Z")); // INT_MAX after subtacting 1 - TestDateTimeRoundtrip(_XPLATSTR("2038-01-19T03:14:07-00:00"), _XPLATSTR("2038-01-19T03:14:07Z")); - } + TestDateTimeRoundtrip(_XPLATSTR("2038-01-19T03:13:07-00:01"), + _XPLATSTR("2038-01-19T03:14:07Z")); // INT_MAX after subtacting 1 + TestDateTimeRoundtrip(_XPLATSTR("2038-01-19T03:14:07-00:00"), _XPLATSTR("2038-01-19T03:14:07Z")); } TEST(parsing_time_iso8601_uses_each_timezone_digit) @@ -435,8 +436,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("4970-01-01T00:00:00Z"), // year too big - _XPLATSTR("3001-01-01T00:00:00Z"), + _XPLATSTR("1899-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"), @@ -459,15 +459,15 @@ 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("1969-01-01T00:00:00Z"), // underflow - _XPLATSTR("3001-01-01T00:00:00Z"), // overflow - _XPLATSTR("1970-01-01T00:00:00+00:01"), // time zone underflow + _XPLATSTR("1899-01-01T00:00:00Z"), // underflow + _XPLATSTR("1900-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"), _XPLATSTR("1970-01-01T00:00:00+21:60"), _XPLATSTR("1970-01-01T00:00:00-24:00"), _XPLATSTR("1970-01-01T00:00:00-21:60"), + _XPLATSTR("1971-01-00"), // zero month day }; for (const auto& str : bad_strings)