diff --git a/Release/include/cpprest/asyncrt_utils.h b/Release/include/cpprest/asyncrt_utils.h index da1a62c159..a38702f412 100644 --- a/Release/include/cpprest/asyncrt_utils.h +++ b/Release/include/cpprest/asyncrt_utils.h @@ -630,15 +630,6 @@ class datetime static const interval_type _hourTicks = 60 * 60 * _secondTicks; static const interval_type _dayTicks = 24 * 60 * 60 * _secondTicks; -#ifdef _WIN32 - // void* to avoid pulling in windows.h - static _ASYNCRTIMP bool __cdecl system_type_to_datetime(/*SYSTEMTIME*/ void* psysTime, - uint64_t seconds, - datetime* pdt); -#else - static datetime timeval_to_datetime(const timeval& time); -#endif - // Private constructor. Use static methods to create an instance. datetime(interval_type interval) : m_interval(interval) {} @@ -699,7 +690,6 @@ class nonce_generator void set_length(int length) { m_length = length; } private: - static const utility::string_t c_allowed_chars; std::mt19937 m_random; int m_length; }; diff --git a/Release/src/pch/stdafx.h b/Release/src/pch/stdafx.h index 7cb9504b2e..d999befe5b 100644 --- a/Release/src/pch/stdafx.h +++ b/Release/src/pch/stdafx.h @@ -61,7 +61,6 @@ #undef BOOST_NO_CXX11_NULLPTR #endif #include "boost/bind/bind.hpp" -#include "boost/date_time/posix_time/posix_time_types.hpp" #include "boost/thread/condition_variable.hpp" #include "boost/thread/mutex.hpp" #include diff --git a/Release/src/utilities/asyncrt_utils.cpp b/Release/src/utilities/asyncrt_utils.cpp index 96f3d9af93..7867e6ec55 100644 --- a/Release/src/utilities/asyncrt_utils.cpp +++ b/Release/src/utilities/asyncrt_utils.cpp @@ -15,20 +15,9 @@ #include #include -#include +#include #include - -#ifndef _WIN32 -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-local-typedef" -#endif -#include -#include -#if defined(__clang__) -#pragma clang diagnostic pop -#endif -#endif +#include using namespace web; using namespace utility; @@ -629,18 +618,7 @@ std::string __cdecl conversions::to_utf8string(const utf16string& value) { retur utf16string __cdecl conversions::to_utf16string(const std::string& value) { return utf8_to_utf16(value); } -#ifndef WIN32 -datetime datetime::timeval_to_datetime(const timeval& time) -{ - const uint64_t epoch_offset = 11644473600LL; // diff between windows and unix epochs (seconds) - uint64_t result = epoch_offset + 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(result); -} -#endif - -static bool is_digit(utility::char_t c) { return c >= _XPLATSTR('0') && c <= _XPLATSTR('9'); } +static const uint64_t ntToUnixOffsetSeconds = 11644473600U; // diff between windows and unix epochs (seconds) datetime __cdecl datetime::utc_now() { @@ -656,450 +634,649 @@ datetime __cdecl datetime::utc_now() #else // LINUX struct timeval time; gettimeofday(&time, nullptr); - return timeval_to_datetime(time); + uint64_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(result); #endif } +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"; + utility::string_t datetime::to_string(date_format format) const { -#ifdef _WIN32 - int status; - - ULARGE_INTEGER largeInt; - largeInt.QuadPart = m_interval; - - FILETIME ft; - ft.dwHighDateTime = largeInt.HighPart; - ft.dwLowDateTime = largeInt.LowPart; - - SYSTEMTIME systemTime; - if (!FileTimeToSystemTime((const FILETIME*)&ft, &systemTime)) + const uint64_t input = m_interval / _secondTicks; // convert to seconds + const int frac_sec = static_cast(m_interval % _secondTicks); + const time_t time = static_cast(input - ntToUnixOffsetSeconds); + 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 { - throw utility::details::create_system_error(GetLastError()); + throw std::invalid_argument("gmtime_r/s failed on the time supplied"); } - std::wstring result; - if (format == RFC_1123) + char outBuffer[38]; // Thu, 01 Jan 1970 00:00:00 GMT\0 + // 1970-01-01T00:00:00.1234567Z\0 + char* outCursor = outBuffer; + switch (format) { - { - wchar_t dateStr[18] = {0}; -#if _WIN32_WINNT < _WIN32_WINNT_VISTA - status = GetDateFormatW( - LOCALE_INVARIANT, 0, &systemTime, L"ddd',' dd MMM yyyy", dateStr, sizeof(dateStr) / sizeof(wchar_t)); -#else - status = GetDateFormatEx(LOCALE_NAME_INVARIANT, - 0, - &systemTime, - L"ddd',' dd MMM yyyy", - dateStr, - sizeof(dateStr) / sizeof(wchar_t), - NULL); -#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA - if (status == 0) + case RFC_1123: +#ifdef _MSC_VER + 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); +#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); +#endif // _MSC_VER + outCursor += 25; + memcpy(outCursor, " GMT", 4); + outCursor += 4; + return utility::string_t(outBuffer, outCursor); + case ISO_8601: +#ifdef _MSC_VER + 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); +#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); +#endif // _MSC_VER + outCursor += 19; + if (frac_sec != 0) { - throw utility::details::create_system_error(GetLastError()); - } - - result += dateStr; - result += L' '; - } + // 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); +#else // ^^^ _MSC_VER // !_MSC_VER vvv + size_t appended = sprintf(outCursor, ".%07d", frac_sec); +#endif // _MSC_VER + while (outCursor[appended - 1] == '0') + { + --appended; // trim trailing zeros + } - { - wchar_t timeStr[10] = {0}; -#if _WIN32_WINNT < _WIN32_WINNT_VISTA - status = GetTimeFormatW(LOCALE_INVARIANT, - TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, - &systemTime, - L"HH':'mm':'ss", - timeStr, - sizeof(timeStr) / sizeof(wchar_t)); -#else - status = GetTimeFormatEx(LOCALE_NAME_INVARIANT, - TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, - &systemTime, - L"HH':'mm':'ss", - timeStr, - sizeof(timeStr) / sizeof(wchar_t)); -#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA - if (status == 0) - { - throw utility::details::create_system_error(GetLastError()); + outCursor += appended; } - result += timeStr; - result += L" GMT"; - } + *outCursor = 'Z'; + ++outCursor; + return utility::string_t(outBuffer, outCursor); + default: throw std::invalid_argument("Unrecognized date format."); } - else if (format == ISO_8601) - { - const size_t buffSize = 64; - { - wchar_t dateStr[buffSize] = {0}; -#if _WIN32_WINNT < _WIN32_WINNT_VISTA - status = GetDateFormatW(LOCALE_INVARIANT, 0, &systemTime, L"yyyy-MM-dd", dateStr, buffSize); -#else - status = GetDateFormatEx(LOCALE_NAME_INVARIANT, 0, &systemTime, L"yyyy-MM-dd", dateStr, buffSize, NULL); -#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA - if (status == 0) - { - throw utility::details::create_system_error(GetLastError()); - } - - result += dateStr; - result += L'T'; - } - - { - wchar_t timeStr[buffSize] = {0}; -#if _WIN32_WINNT < _WIN32_WINNT_VISTA - status = GetTimeFormatW(LOCALE_INVARIANT, - TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, - &systemTime, - L"HH':'mm':'ss", - timeStr, - buffSize); -#else - status = GetTimeFormatEx(LOCALE_NAME_INVARIANT, - TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, - &systemTime, - L"HH':'mm':'ss", - timeStr, - buffSize); -#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA - if (status == 0) - { - throw utility::details::create_system_error(GetLastError()); - } - - result += timeStr; - } +} - uint64_t frac_sec = largeInt.QuadPart % _secondTicks; - if (frac_sec > 0) +template +static bool string_starts_with(const CharT* haystack, const char* needle) +{ + while (*needle) + { + if (*haystack != static_cast(*needle)) { - // Append fractional second, which is a 7-digit value with no trailing zeros - // This way, '1200' becomes '00012' - wchar_t buf[9] = {0}; - size_t appended = swprintf_s(buf, 9, L".%07ld", static_cast(frac_sec)); - while (buf[appended - 1] == L'0') - --appended; // trim trailing zeros - result.append(buf, appended); + return false; } - result += L'Z'; + ++haystack; + ++needle; } - return result; -#else // LINUX - uint64_t input = m_interval; - uint64_t frac_sec = input % _secondTicks; - input /= _secondTicks; // convert to seconds - time_t time = (time_t)input - (time_t)11644473600LL; // diff between windows and unix epochs (seconds) - - struct tm datetime; - gmtime_r(&time, &datetime); + return true; +} - const int max_dt_length = 64; - char output[max_dt_length + 1] = {0}; +#define ascii_isdigit(c) ((unsigned char)((unsigned char)(c) - '0') <= 9) +#define ascii_isdigit6(c) ((unsigned char)((unsigned char)(c) - '0') <= 6) +#define ascii_isdigit5(c) ((unsigned char)((unsigned char)(c) - '0') <= 5) +#define ascii_isdigit3(c) ((unsigned char)((unsigned char)(c) - '0') <= 3) +#define ascii_isdigit2(c) ((unsigned char)((unsigned char)(c) - '0') <= 2) +#define ascii_isdigit1(c) ((unsigned char)((unsigned char)(c) - '0') <= 1) + +static const unsigned char max_days_in_month[12] = { + 31, // Jan + 00, // Feb, special handling for leap years + 31, // Mar + 30, // Apr + 31, // May + 30, // Jun + 31, // Jul + 31, // Aug + 30, // Sep + 31, // Oct + 30, // Nov + 31 // Dec +}; - if (format != RFC_1123 && frac_sec > 0) - { - // Append fractional second, which is a 7-digit value with no trailing zeros - // This way, '1200' becomes '00012' - const int max_frac_length = 8; - char buf[max_frac_length + 1] = {0}; - snprintf(buf, sizeof(buf), ".%07ld", (long int)frac_sec); - // trim trailing zeros - for (int i = max_frac_length - 1; buf[i] == '0'; i--) - buf[i] = '\0'; - // format the datetime into a separate buffer - char datetime_str[max_dt_length - max_frac_length - 1 + 1] = {0}; - strftime(datetime_str, sizeof(datetime_str), "%Y-%m-%dT%H:%M:%S", &datetime); - // now print this buffer into the output buffer - snprintf(output, sizeof(output), "%s%sZ", datetime_str, buf); +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)); } else { - strftime( - output, sizeof(output), format == RFC_1123 ? "%a, %d %b %Y %H:%M:%S GMT" : "%Y-%m-%dT%H:%M:%SZ", &datetime); + maxDaysThisMonth = max_days_in_month[month]; } - return std::string(output); -#endif + return day >= 1 && day <= maxDaysThisMonth; } -#ifdef _WIN32 -bool __cdecl datetime::system_type_to_datetime(void* pvsysTime, uint64_t seconds, datetime* pdt) +template +static int atoi2(const CharT* str) { - SYSTEMTIME* psysTime = (SYSTEMTIME*)pvsysTime; - FILETIME fileTime; + return (static_cast(str[0]) - '0') * 10 + (static_cast(str[1]) - '0'); +} - if (SystemTimeToFileTime(psysTime, &fileTime)) +static const time_t maxTimeT = sizeof(time_t) == 4 ? (time_t)INT_MAX : (time_t)LLONG_MAX; + +static time_t timezone_adjust(time_t result, unsigned char chSign, int adjustHours, int adjustMinutes) +{ + if (adjustHours > 23) { - ULARGE_INTEGER largeInt; - largeInt.LowPart = fileTime.dwLowDateTime; - largeInt.HighPart = fileTime.dwHighDateTime; + return (time_t)-1; + } - // Add hundredths of nanoseconds - largeInt.QuadPart += seconds; + // adjustMinutes > 59 is impossible due to digit 5 check + const int tzAdjust = adjustMinutes * 60 + adjustHours * 60 * 60; + if (chSign == '-') + { + if (maxTimeT - result < tzAdjust) + { + return (time_t)-1; + } - *pdt = datetime(largeInt.QuadPart); - return true; + result += tzAdjust; } - return false; -} -#endif - -// Take a string that represents a fractional second and return the number of ticks -// This is equivalent to doing atof on the string and multiplying by 10000000, -// but does not lose precision -template -uint64_t timeticks_from_second(StringIterator begin, StringIterator end) -{ - int size = (int)(end - begin); - _ASSERTE(begin[0] == U('.')); - uint64_t ufrac_second = 0; - for (int i = 1; i <= 7; ++i) + else { - ufrac_second *= 10; - int add = i < size ? begin[i] - U('0') : 0; - ufrac_second += add; + if (tzAdjust > result) + { + return (time_t)-1; + } + + result -= tzAdjust; } - return ufrac_second; + + return result; } -void extract_fractional_second(const utility::string_t& dateString, - utility::string_t& resultString, - uint64_t& ufrac_second) +static time_t make_gm_time(struct tm* t) { - resultString = dateString; - // First, the string must be strictly longer than 2 characters, and the trailing character must be 'Z' - if (resultString.size() > 2 && resultString[resultString.size() - 1] == U('Z')) +#ifdef _MSC_VER + return _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; { - // Second, find the last non-digit by scanning the string backwards - auto last_non_digit = std::find_if_not(resultString.rbegin() + 1, resultString.rend(), is_digit); - if (last_non_digit < resultString.rend() - 1) + boost::lock_guard lock(env_var_lock); + std::string prev_env; + auto prev_env_cstr = getenv("TZ"); + if (prev_env_cstr != nullptr) { - // Finally, make sure the last non-digit is a dot: - auto last_dot = last_non_digit.base() - 1; - if (*last_dot == U('.')) - { - // Got it! Now extract the fractional second - auto last_before_Z = std::end(resultString) - 1; - ufrac_second = timeticks_from_second(last_dot, last_before_Z); - // And erase it from the string - resultString.erase(last_dot, last_before_Z); - } + 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 time; +#else // ^^^ ANDROID // Other POSIX platforms vvv + return timegm(t); +#endif // _MSC_VER } +/* +https://tools.ietf.org/html/rfc822 +https://tools.ietf.org/html/rfc1123 + +date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + +day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + +date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 +RFC1123 changes this to: +date = 1*2DIGIT month 2*4DIGIT ; day month year + ; e.g. 20 Jun 1982 +This implementation only accepts 4 digit years. + +month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + +time = hour zone ; ANSI and Military + +hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + +zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + +// military time deleted by RFC 1123 + + / ( ("+" / "-") 4DIGIT ) ; Local differential + ; hours+min. (HHMM) +*/ + + datetime __cdecl datetime::from_string(const utility::string_t& dateString, date_format format) { - // avoid floating point math to preserve precision - uint64_t ufrac_second = 0; - -#ifdef _WIN32 datetime result; + time_t seconds; + uint64_t frac_sec = 0; + struct tm t{}; + auto str = dateString.c_str(); if (format == RFC_1123) { - SYSTEMTIME sysTime = {0}; - - std::wstring month(3, L'\0'); - std::wstring unused(3, L'\0'); - - const wchar_t* formatString = L"%3c, %2d %3c %4d %2d:%2d:%2d %3c"; - auto n = swscanf_s(dateString.c_str(), - formatString, - unused.data(), - unused.size(), - &sysTime.wDay, - month.data(), - month.size(), - &sysTime.wYear, - &sysTime.wHour, - &sysTime.wMinute, - &sysTime.wSecond, - unused.data(), - unused.size()); - - if (n == 8) - { - std::wstring monthnames[12] = { - L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec"}; - auto loc = - std::find_if(monthnames, monthnames + 12, [&month](const std::wstring& m) { return m == month; }); - - if (loc != monthnames + 12) + int parsedWeekday = -1; + for (int day = 0; day < 7; ++day) + { + if (string_starts_with(str, dayNames + day * 4) && str[3] == _XPLATSTR(',') && str[4] == _XPLATSTR(' ')) { - sysTime.wMonth = (short)((loc - monthnames) + 1); - if (system_type_to_datetime(&sysTime, ufrac_second, &result)) - { - return result; - } + parsedWeekday = day; + str += 5; // parsed day of week + break; } } - } - else if (format == ISO_8601) - { - // Unlike FILETIME, SYSTEMTIME does not have enough precision to hold seconds in 100 nanosecond - // increments. Therefore, start with seconds and milliseconds set to 0, then add them separately - - // Try to extract the fractional second from the timestamp - utility::string_t input; - extract_fractional_second(dateString, input, ufrac_second); - { - SYSTEMTIME sysTime = {0}; - const wchar_t* formatString = L"%4d-%2d-%2dT%2d:%2d:%2dZ"; - auto n = swscanf_s(input.c_str(), - formatString, - &sysTime.wYear, - &sysTime.wMonth, - &sysTime.wDay, - &sysTime.wHour, - &sysTime.wMinute, - &sysTime.wSecond); - - if (n == 3 || n == 6) + + if (ascii_isdigit3(str[0]) && ascii_isdigit(str[1]) && str[2] == _XPLATSTR(' ')) + { + t.tm_mday = 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'); + str += 2; // parsed day + } + else + { + return result; + } + + t.tm_mon = -1; + for (int month = 0; month < 12; ++month) + { + if (string_starts_with(str, monthNames + month * 4)) { - if (system_type_to_datetime(&sysTime, ufrac_second, &result)) - { - return result; - } + t.tm_mon = month; + break; } } + + if (t.tm_mon == -1 || str[3] != _XPLATSTR(' ')) { - SYSTEMTIME sysTime = {0}; - DWORD date = 0; + return result; + } - const wchar_t* formatString = L"%8dT%2d:%2d:%2dZ"; - auto n = swscanf_s(input.c_str(), formatString, &date, &sysTime.wHour, &sysTime.wMinute, &sysTime.wSecond); + str += 4; // parsed month - if (n == 1 || n == 4) - { - sysTime.wDay = date % 100; - date /= 100; - sysTime.wMonth = date % 100; - date /= 100; - sysTime.wYear = (WORD)date; + if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]) || + str[4] != ' ') + { + return result; + } - if (system_type_to_datetime(&sysTime, ufrac_second, &result)) - { - 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) + { + return result; + } + + // days in month validity check + if (!validate_day_month(t.tm_mday, t.tm_mon, t.tm_year)) + { + return result; + } + + t.tm_year -= 1900; + str += 5; // parsed year + + if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]) || str[2] != _XPLATSTR(':') || !ascii_isdigit5(str[3]) || + !ascii_isdigit(str[4])) + { + return result; } + + t.tm_hour = atoi2(str); + if (t.tm_hour > 23) { - SYSTEMTIME sysTime = {0}; - GetSystemTime(&sysTime); // Fill date portion with today's information - sysTime.wSecond = 0; - sysTime.wMilliseconds = 0; + return result; + } - const wchar_t* formatString = L"%2d:%2d:%2dZ"; - auto n = swscanf_s(input.c_str(), formatString, &sysTime.wHour, &sysTime.wMinute, &sysTime.wSecond); + str += 3; // parsed hour + t.tm_min = atoi2(str); + str += 2; // parsed mins - if (n == 3) + if (str[0] == ':') + { + if (!ascii_isdigit6(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(' ')) { - if (system_type_to_datetime(&sysTime, ufrac_second, &result)) - { - return result; - } + return result; } + + t.tm_sec = atoi2(str + 1); + str += 4; // parsed seconds + } + else if (str[0] == _XPLATSTR(' ')) + { + t.tm_sec = 0; + str += 1; // parsed seconds + } + else + { + return result; } - } - return datetime(); -#else - std::string input(dateString); + if (t.tm_sec > 60) + { // 60 to allow leap seconds + return result; + } - struct tm output = tm(); + t.tm_isdst = 0; + seconds = make_gm_time(&t); + if (seconds < 0) + { + return result; + } - if (format == RFC_1123) - { - strptime(input.data(), "%a, %d %b %Y %H:%M:%S GMT", &output); + if (parsedWeekday >= 0 && parsedWeekday != t.tm_wday) + { + return result; + } + + if (!string_starts_with(str, "GMT") && !string_starts_with(str, "UT")) + { + // some timezone adjustment necessary + auto tzCh = _XPLATSTR('-'); + int tzHours; + int tzMinutes = 0; + if (string_starts_with(str, "EDT")) + { + tzHours = 4; + } + else if (string_starts_with(str, "EST") || string_starts_with(str, "CDT")) + { + tzHours = 5; + } + else if (string_starts_with(str, "CST") || string_starts_with(str, "MDT")) + { + tzHours = 6; + } + else if (string_starts_with(str, "MST") || string_starts_with(str, "PDT")) + { + tzHours = 7; + } + else if (string_starts_with(str, "PST")) + { + tzHours = 8; + } + else if ((tzCh == _XPLATSTR('+') || tzCh == _XPLATSTR('-')) && ascii_isdigit2(str[1]) && + ascii_isdigit(str[2]) && ascii_isdigit5(str[3]) && ascii_isdigit(str[4])) + { + tzCh = str[0]; + tzHours = atoi2(str + 1); + tzMinutes = atoi2(str + 3); + } + else + { + return result; + } + + seconds = timezone_adjust(seconds, static_cast(tzCh), tzHours, tzMinutes); + if (seconds < 0) + { + return result; + } + } } - else + else if (format == ISO_8601) { - // Try to extract the fractional second from the timestamp - utility::string_t input; - extract_fractional_second(dateString, input, ufrac_second); + // parse year + if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3])) + { + return result; + } - auto result = strptime(input.data(), "%Y-%m-%dT%H:%M:%SZ", &output); + 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) + { + return result; + } - if (result == nullptr) + str += 4; + if (*str == _XPLATSTR('-')) { - result = strptime(input.data(), "%Y%m%dT%H:%M:%SZ", &output); + ++str; } - if (result == nullptr) + + // parse month + if (!ascii_isdigit1(str[0]) || !ascii_isdigit(str[1])) { - // Fill the date portion with the epoch, - // strptime will do the rest - memset(&output, 0, sizeof(struct tm)); - output.tm_year = 70; - output.tm_mon = 1; - output.tm_mday = 1; - result = strptime(input.data(), "%H:%M:%SZ", &output); + return result; } - if (result == nullptr) + + t.tm_mon = atoi2(str); + if (t.tm_mon < 1 || t.tm_mon > 12) { - result = strptime(input.data(), "%Y-%m-%d", &output); + return result; } - if (result == nullptr) + + t.tm_mon -= 1; + str += 2; + + if (*str == _XPLATSTR('-')) { - result = strptime(input.data(), "%Y%m%d", &output); + ++str; } - if (result == nullptr) + + // parse day + if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1])) { - return datetime(); + return result; } - } -#if (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; + t.tm_mday = atoi2(str); + if (!validate_day_month(t.tm_mday, t.tm_mon, t.tm_year)) + { + return result; + } - 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) + t.tm_year -= 1900; + str += 2; + + if (str[0] != _XPLATSTR('T') && str[0] != _XPLATSTR('t')) { - prev_env = prev_env_cstr; + // No time + seconds = make_gm_time(&t); + if (seconds < 0) + { + return result; + } + + seconds += ntToUnixOffsetSeconds; + result.m_interval = static_cast(seconds) * _secondTicks; + return result; } - setenv("TZ", "UTC", 1); - time = mktime(&output); + ++str; // skip 'T' - if (prev_env_cstr) + // parse hour + if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1])) { - setenv("TZ", prev_env.c_str(), 1); + return result; + } + + t.tm_hour = atoi2(str); + str += 2; + if (t.tm_hour > 23) + { + return result; + } + + if (*str == _XPLATSTR(':')) + { + ++str; + } + + // parse minute + if (!ascii_isdigit5(str[0]) || !ascii_isdigit(str[1])) + { + 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 + // check above + + str += 2; + + if (*str == _XPLATSTR(':')) + { + ++str; + } + + // parse seconds + if (!ascii_isdigit6(str[0]) || !ascii_isdigit(str[1])) + { + return result; + } + + t.tm_sec = atoi2(str); + // We allow 60 to account for leap seconds + if (t.tm_sec > 60) + { + return result; + } + + str += 2; + if (str[0] == _XPLATSTR('.') && ascii_isdigit(str[1])) + { + ++str; + int digits = 7; + for (;;) + { + frac_sec *= 10; + frac_sec += *str - _XPLATSTR('0'); + --digits; + ++str; + if (digits == 0) + { + while (ascii_isdigit(*str)) + { + // consume remaining fractional second digits we can't use + ++str; + } + + break; + } + + if (!ascii_isdigit(*str)) + { + // no more digits in the input, do the remaining multiplies we need + for (; digits != 0; --digits) + { + frac_sec *= 10; + } + + break; + } + } + } + + seconds = make_gm_time(&t); + if (seconds < 0) + { + return result; + } + + if (str[0] == _XPLATSTR('Z') || str[0] == _XPLATSTR('z')) + { + // no adjustment needed for zulu time + } + else if (str[0] == _XPLATSTR('+') || str[0] == _XPLATSTR('-')) + { + const unsigned char offsetDirection = static_cast(str[0]); + if (!ascii_isdigit2(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(':') || + !ascii_isdigit5(str[4]) || !ascii_isdigit(str[5])) + { + return result; + } + + seconds = timezone_adjust(seconds, offsetDirection, atoi2(str + 1), atoi2(str + 4)); + if (seconds < 0) + { + return result; + } } else { - unsetenv("TZ"); + // the timezone is malformed, but cpprestsdk currently accepts this as no timezone } } -#else - time_t time = timegm(&output); -#endif - - struct timeval tv = timeval(); - tv.tv_sec = time; - auto result = timeval_to_datetime(tv); + else + { + throw std::invalid_argument("unrecognized date format"); + } - // fractional seconds are already in correct format so just add them. - result = result + ufrac_second; + seconds += ntToUnixOffsetSeconds; + result.m_interval = static_cast(seconds) * _secondTicks + frac_sec; return result; -#endif } /// @@ -1181,38 +1358,32 @@ utility::seconds __cdecl timespan::xml_duration_to_seconds(const utility::string // The final S could be omitted int64_t numSecs = 0; - - utility::istringstream_t is(timespanString); - is.imbue(std::locale::classic()); - auto eof = std::char_traits::eof(); - - std::basic_istream::int_type c; - c = is.get(); // P - - while (c != eof) + auto cursor = timespanString.c_str(); + auto c = *cursor++; // skip 'P' + while (c) { int val = 0; - c = is.get(); + c = *cursor++; - while (is_digit((utility::char_t)c)) + while (ascii_isdigit(c)) { - val = val * 10 + (c - L'0'); - c = is.get(); + val = val * 10 + (c - _XPLATSTR('0')); + c = *cursor++; - if (c == '.') + if (c == _XPLATSTR('.')) { // decimal point is not handled do { - c = is.get(); - } while (is_digit((utility::char_t)c)); + c = *cursor++; + } while (ascii_isdigit(c)); } } - if (c == L'D') numSecs += val * 24 * 3600; // days - if (c == L'H') numSecs += val * 3600; // Hours - if (c == L'M') numSecs += val * 60; // Minutes - if (c == L'S' || c == eof) + if (c == _XPLATSTR('D')) numSecs += val * 24 * 3600; // days + if (c == _XPLATSTR('H')) numSecs += val * 3600; // Hours + if (c == _XPLATSTR('M')) numSecs += val * 60; // Minutes + if (c == _XPLATSTR('S') || c == _XPLATSTR('\0')) { numSecs += val; // seconds break; @@ -1222,12 +1393,12 @@ utility::seconds __cdecl timespan::xml_duration_to_seconds(const utility::string return utility::seconds(numSecs); } -const utility::string_t nonce_generator::c_allowed_chars( - _XPLATSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")); +static const utility::char_t c_allowed_chars[] = + _XPLATSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); utility::string_t nonce_generator::generate() { - std::uniform_int_distribution<> distr(0, static_cast(c_allowed_chars.length() - 1)); + std::uniform_int_distribution<> distr(0, static_cast(sizeof(c_allowed_chars) / sizeof(utility::char_t)) - 1); utility::string_t result; result.reserve(length()); std::generate_n(std::back_inserter(result), length(), [&]() { return c_allowed_chars[distr(m_random)]; }); diff --git a/Release/tests/functional/utils/datetime.cpp b/Release/tests/functional/utils/datetime.cpp index a55d6326d4..ae7f7a5e43 100644 --- a/Release/tests/functional/utils/datetime.cpp +++ b/Release/tests/functional/utils/datetime.cpp @@ -74,16 +74,6 @@ SUITE(datetime) } } - TEST(parsing_time_extended) - { - // ISO 8601 - { - auto dt = utility::datetime::from_string(_XPLATSTR("14:30:01Z"), utility::datetime::ISO_8601); - - VERIFY_ARE_NOT_EQUAL(0u, dt.to_interval()); - } - } - void TestDateTimeRoundtrip(utility::string_t str, utility::string_t strExpected) { auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601); @@ -101,6 +91,8 @@ SUITE(datetime) TEST(parsing_time_roundtrip_datetime2) { + // lose the last '000' + TestDateTimeRoundtrip(_XPLATSTR("2013-11-19T14:30:59.1234567000Z"), _XPLATSTR("2013-11-19T14:30:59.1234567Z")); // lose the last '999' without rounding up TestDateTimeRoundtrip(_XPLATSTR("2013-11-19T14:30:59.1234567999Z"), _XPLATSTR("2013-11-19T14:30:59.1234567Z")); } @@ -129,16 +121,260 @@ SUITE(datetime) TestDateTimeRoundtrip(_XPLATSTR("2013-11-19T14:30:59.5Z")); } - TEST(parsing_time_roundtrip_datetime_invalid1, - "Ignore:Linux", - "Codeplex issue #115", - "Ignore:Apple", - "Codeplex issue #115") + void TestRfc1123IsTimeT(const utility::char_t* str, time_t t) + { + datetime dt = datetime::from_string(str, utility::datetime::RFC_1123); + uint64_t interval = dt.to_interval(); + VERIFY_ARE_EQUAL(0, interval % 10000000); + interval /= 10000000; + interval -= 11644473600; // NT epoch adjustment + VERIFY_ARE_EQUAL(static_cast(t), interval); + } + + 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); + } + + 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); +#ifndef _USE_32BIT_TIME_T + 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); +#endif // _USE_32BIT_TIME_T + 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); + } + + 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); +#ifndef _USE_32BIT_TIME_T + TestRfc1123IsTimeT(_XPLATSTR("01 Jan 2100 00:00:00 GMT"), (time_t) 4102444800); +#endif // _USE_32BIT_TIME_T + 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); + } + + 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); + } + + TEST(parsing_time_rfc1123_invalid_cases) + { + const utility::string_t bad_strings[] = { + _XPLATSTR("Ahu, 01 Jan 1970 00:00:00 GMT"), // bad letters in each place + _XPLATSTR("TAu, 01 Jan 1970 00:00:00 GMT"), + _XPLATSTR("ThA, 01 Jan 1970 00:00:00 GMT"), + _XPLATSTR("ThuA 01 Jan 1970 00:00:00 GMT"), + _XPLATSTR("Thu,A01 Jan 1970 00:00:00 GMT"), + _XPLATSTR("Thu, A1 Jan 1970 00:00:00 GMT"), + _XPLATSTR("Thu, 0A Jan 1970 00:00:00 GMT"), + _XPLATSTR("Thu, 01AJan 1970 00:00:00 GMT"), + _XPLATSTR("Thu, 01 Aan 1970 00:00:00 GMT"), + _XPLATSTR("Thu, 01 JAn 1970 00:00:00 GMT"), + _XPLATSTR("Thu, 01 JaA 1970 00:00:00 GMT"), + _XPLATSTR("Thu, 01 JanA1970 00:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan A970 00:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1A70 00:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 19A0 00:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 197A 00:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970A00:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 A0:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 0A:00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 00A00:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:A0:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:0A:00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:00A00 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:A0 GMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:0A GMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:00AGMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:00 AMT"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:00 GAT"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:00 GMA"), + _XPLATSTR(""), // truncation + _XPLATSTR("T"), + _XPLATSTR("Th"), + _XPLATSTR("Thu"), + _XPLATSTR("Thu,"), + _XPLATSTR("Thu, "), + _XPLATSTR("Thu, 0"), + _XPLATSTR("Thu, 01"), + _XPLATSTR("Thu, 01 "), + _XPLATSTR("Thu, 01 J"), + _XPLATSTR("Thu, 01 Ja"), + _XPLATSTR("Thu, 01 Jan"), + _XPLATSTR("Thu, 01 Jan "), + _XPLATSTR("Thu, 01 Jan 1"), + _XPLATSTR("Thu, 01 Jan 19"), + _XPLATSTR("Thu, 01 Jan 197"), + _XPLATSTR("Thu, 01 Jan 1970"), + _XPLATSTR("Thu, 01 Jan 1970 "), + _XPLATSTR("Thu, 01 Jan 1970 0"), + _XPLATSTR("Thu, 01 Jan 1970 00"), + _XPLATSTR("Thu, 01 Jan 1970 00:"), + _XPLATSTR("Thu, 01 Jan 1970 00:0"), + _XPLATSTR("Thu, 01 Jan 1970 00:00"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:0"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:00"), + _XPLATSTR("Thu, 01 Jan 1970 00:00:00 "), + _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 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"), + _XPLATSTR("32 Jul 1971 00:00:00 GMT"), + _XPLATSTR("32 Aug 1971 00:00:00 GMT"), + _XPLATSTR("31 Sep 1971 00:00:00 GMT"), + _XPLATSTR("32 Oct 1971 00:00:00 GMT"), + _XPLATSTR("31 Nov 1971 00:00:00 GMT"), + _XPLATSTR("32 Dec 1971 00:00:00 GMT"), + _XPLATSTR("01 Jan 1971 70:00:00 GMT"), // hour too big + _XPLATSTR("01 Jan 1971 24:00:00 GMT"), + _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 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"), + }; + + for (const auto& str : bad_strings) + { + auto dt = utility::datetime::from_string(str, utility::datetime::RFC_1123); + VERIFY_ARE_EQUAL(0, dt.to_interval()); + } + } + + TEST(parsing_time_iso8601_boundary_cases) + { + // 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 +#ifndef _USE_32BIT_TIME_T + 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")); +#endif // _USE_32BIT_TIME_T + } + + TEST(parsing_time_iso8601_uses_each_timezone_digit) + { + TestDateTimeRoundtrip(_XPLATSTR("2019-01-14T23:16:21+00:00"), _XPLATSTR("2019-01-14T23:16:21Z")); + TestDateTimeRoundtrip(_XPLATSTR("2019-01-14T23:16:21-00:01"), _XPLATSTR("2019-01-14T23:17:21Z")); + TestDateTimeRoundtrip(_XPLATSTR("2019-01-14T23:16:21+00:01"), _XPLATSTR("2019-01-14T23:15:21Z")); + TestDateTimeRoundtrip(_XPLATSTR("2019-01-14T23:16:21-01:00"), _XPLATSTR("2019-01-15T00:16:21Z")); + TestDateTimeRoundtrip(_XPLATSTR("2019-01-14T23:16:21+01:00"), _XPLATSTR("2019-01-14T22:16:21Z")); + } + + TEST(parsing_time_iso8601_uses_each_digit) + { + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T00:00:01Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T00:01:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T01:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-02T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-02-01T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1971-01-01T00:00:00Z")); + + TestDateTimeRoundtrip(_XPLATSTR("1999-01-01T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-12-01T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-09-01T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-30T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-31T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T23:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T19:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T00:59:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T00:00:59Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T00:00:60Z"), _XPLATSTR("1970-01-01T00:01:00Z")); // leap seconds + } + + TEST(parsing_time_iso8601_accepts_month_max_days) + { + TestDateTimeRoundtrip(_XPLATSTR("1970-01-31T00:00:00Z")); // jan + TestDateTimeRoundtrip(_XPLATSTR("2019-02-28T00:00:00Z")); // non leap year allows feb 28 + TestDateTimeRoundtrip(_XPLATSTR("2020-02-29T00:00:00Z")); // leap year allows feb 29 + TestDateTimeRoundtrip(_XPLATSTR("1970-03-31T00:00:00Z")); // mar + TestDateTimeRoundtrip(_XPLATSTR("1970-04-30T00:00:00Z")); // apr + TestDateTimeRoundtrip(_XPLATSTR("1970-05-31T00:00:00Z")); // may + TestDateTimeRoundtrip(_XPLATSTR("1970-06-30T00:00:00Z")); // jun + TestDateTimeRoundtrip(_XPLATSTR("1970-07-31T00:00:00Z")); // jul + TestDateTimeRoundtrip(_XPLATSTR("1970-08-31T00:00:00Z")); // aug + TestDateTimeRoundtrip(_XPLATSTR("1970-09-30T00:00:00Z")); // sep + TestDateTimeRoundtrip(_XPLATSTR("1970-10-31T00:00:00Z")); // oct + TestDateTimeRoundtrip(_XPLATSTR("1970-11-30T00:00:00Z")); // nov + TestDateTimeRoundtrip(_XPLATSTR("1970-12-31T00:00:00Z")); // dec + } + + TEST(parsing_time_iso8601_accepts_lowercase_t_z) + { + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01t00:00:00Z"), _XPLATSTR("1970-01-01T00:00:00Z")); + TestDateTimeRoundtrip(_XPLATSTR("1970-01-01T00:00:00z"), _XPLATSTR("1970-01-01T00:00:00Z")); + } + + TEST(parsing_time_roundtrip_datetime_accepts_invalid_no_trailing_timezone) { // No digits after the dot, or non-digits. This is not a valid input, but we should not choke on it, // Simply ignore the bad fraction const utility::string_t bad_strings[] = {_XPLATSTR("2013-11-19T14:30:59.Z"), - _XPLATSTR("2013-11-19T14:30:59.1a2Z")}; + _XPLATSTR("2013-11-19T14:30:59.a12Z")}; utility::string_t str_corrected = _XPLATSTR("2013-11-19T14:30:59Z"); for (const auto& str : bad_strings) @@ -151,12 +387,87 @@ SUITE(datetime) TEST(parsing_time_roundtrip_datetime_invalid2) { - // Variouls unsupported cases. In all cases, we have produce an empty date time + // Various unsupported cases. In all cases, we have produce an empty date time const utility::string_t bad_strings[] = { - _XPLATSTR(""), // empty - _XPLATSTR(".Z"), // too short - _XPLATSTR(".Zx"), // no trailing Z - _XPLATSTR("3.14Z") // not a valid date + _XPLATSTR(""), // empty + _XPLATSTR(".Z"), // too short + _XPLATSTR(".Zx"), // no trailing Z + _XPLATSTR("3.14Z") // not a valid date + _XPLATSTR("a971-01-01T00:00:00Z"), // any non digits or valid separators + _XPLATSTR("1a71-01-01T00:00:00Z"), + _XPLATSTR("19a1-01-01T00:00:00Z"), + _XPLATSTR("197a-01-01T00:00:00Z"), + _XPLATSTR("1971a01-01T00:00:00Z"), + _XPLATSTR("1971-a1-01T00:00:00Z"), + _XPLATSTR("1971-0a-01T00:00:00Z"), + _XPLATSTR("1971-01a01T00:00:00Z"), + _XPLATSTR("1971-01-a1T00:00:00Z"), + _XPLATSTR("1971-01-0aT00:00:00Z"), + // _XPLATSTR("1971-01-01a00:00:00Z"), parsed as complete date + _XPLATSTR("1971-01-01Ta0:00:00Z"), + _XPLATSTR("1971-01-01T0a:00:00Z"), + _XPLATSTR("1971-01-01T00a00:00Z"), + _XPLATSTR("1971-01-01T00:a0:00Z"), + _XPLATSTR("1971-01-01T00:0a:00Z"), + _XPLATSTR("1971-01-01T00:00a00Z"), + _XPLATSTR("1971-01-01T00:00:a0Z"), + _XPLATSTR("1971-01-01T00:00:0aZ"), + // "1971-01-01T00:00:00a", accepted as per invalid_no_trailing_timezone above + _XPLATSTR("1"), // truncation + _XPLATSTR("19"), + _XPLATSTR("197"), + _XPLATSTR("1970"), + _XPLATSTR("1970-"), + _XPLATSTR("1970-0"), + _XPLATSTR("1970-01"), + _XPLATSTR("1970-01-"), + _XPLATSTR("1970-01-0"), + // _XPLATSTR("1970-01-01"), complete date + _XPLATSTR("1970-01-01T"), + _XPLATSTR("1970-01-01T0"), + _XPLATSTR("1970-01-01T00"), + _XPLATSTR("1970-01-01T00:"), + _XPLATSTR("1970-01-01T00:0"), + _XPLATSTR("1970-01-01T00:00"), + _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("1971-00-01T00:00:00Z"), // month too small + _XPLATSTR("1971-20-01T00:00:00Z"), // month too big + _XPLATSTR("1971-13-01T00:00:00Z"), + _XPLATSTR("1971-01-00T00:00:00Z"), // day too small + _XPLATSTR("1971-01-32T00:00:00Z"), // day too big + _XPLATSTR("1971-02-30T00:00:00Z"), // day too big for feb + _XPLATSTR("1971-02-30T00:00:00Z"), // day too big for feb (non-leap year) + _XPLATSTR("1971-03-32T00:00:00Z"), // other months + _XPLATSTR("1971-04-31T00:00:00Z"), + _XPLATSTR("1971-05-32T00:00:00Z"), + _XPLATSTR("1971-06-31T00:00:00Z"), + _XPLATSTR("1971-07-32T00:00:00Z"), + _XPLATSTR("1971-08-32T00:00:00Z"), + _XPLATSTR("1971-09-31T00:00:00Z"), + _XPLATSTR("1971-10-32T00:00:00Z"), + _XPLATSTR("1971-11-31T00:00:00Z"), + _XPLATSTR("1971-12-32T00:00:00Z"), + _XPLATSTR("1971-01-01T70:00:00Z"), // hour too big + _XPLATSTR("1971-01-01T24:00:00Z"), + _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 +#ifdef _USE_32BIT_TIME_T + _XPLATSTR("3000-01-01T00:00:01Z"), // overflow +#endif + _XPLATSTR("3001-01-01T00:00:00Z"), + _XPLATSTR("1970-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"), }; for (const auto& str : bad_strings) @@ -166,16 +477,6 @@ SUITE(datetime) } } - TEST(parsing_time_roundtrip_time) - { - // time only without date - utility::string_t str = _XPLATSTR("14:30:59.1234567Z"); - auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601); - utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601); - // Must look for a substring now, since the date part is filled with today's date - VERIFY_IS_TRUE(str2.find(str) != std::string::npos); - } - } // SUITE(datetime) } // namespace utils_tests