Add DateTime: supporting standardized string date and time representations. (#718)
This commit is contained in:
parent
cd2a8a3812
commit
6ae0c0daac
@ -26,21 +26,23 @@ add_library (
|
||||
src/context.cpp
|
||||
src/credentials/credentials.cpp
|
||||
src/credentials/policy/policies.cpp
|
||||
src/datetime.cpp
|
||||
src/http/body_stream.cpp
|
||||
${CURL_TRANSPORT_ADAPTER_SRC}
|
||||
src/http/http.cpp
|
||||
src/http/logging_policy.cpp
|
||||
src/http/policy.cpp
|
||||
src/http/request.cpp
|
||||
src/http/raw_response.cpp
|
||||
src/http/request.cpp
|
||||
src/http/retry_policy.cpp
|
||||
src/http/transport_policy.cpp
|
||||
src/http/telemetry_policy.cpp
|
||||
src/http/transport_policy.cpp
|
||||
src/http/url.cpp
|
||||
${BUILD_WIN_TRANSPORT}
|
||||
src/logging/logging.cpp
|
||||
src/strings.cpp
|
||||
src/version.cpp)
|
||||
src/version.cpp
|
||||
)
|
||||
|
||||
target_include_directories (${TARGET_NAME} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc> $<INSTALL_INTERFACE:include/az_core>)
|
||||
|
||||
|
||||
174
sdk/core/azure-core/inc/azure/core/datetime.hpp
Normal file
174
sdk/core/azure-core/inc/azure/core/datetime.hpp
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Support for date and time standardized string formats.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
/**
|
||||
* @brief Manages date and time in standardized string formats.
|
||||
*/
|
||||
class DateTime {
|
||||
public:
|
||||
/// A type that represents tick spans.
|
||||
typedef uint64_t IntervalType;
|
||||
|
||||
private:
|
||||
// Number of seconds between 01-01-1970 and 01-01-1601.
|
||||
static constexpr IntervalType WindowsToPosixOffsetSeconds = 11644473600LL;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the supported date and time string formats.
|
||||
*/
|
||||
enum class DateFormat
|
||||
{
|
||||
/// RFC 1123.
|
||||
Rfc1123,
|
||||
|
||||
/// ISO 8601.
|
||||
Iso8601
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the current UTC time.
|
||||
*/
|
||||
static DateTime UtcNow();
|
||||
|
||||
/// An invalid UTC timestamp value.
|
||||
static constexpr IntervalType UtcTimestampInvalid = static_cast<IntervalType>(-1);
|
||||
|
||||
/**
|
||||
* @brief Get seconds since Unix/POSIX time epoch at `01-01-1970 00:00:00`.
|
||||
* If time is before epoch, @UtcTimestampInvalid is returned.
|
||||
*/
|
||||
static IntervalType UtcTimestamp()
|
||||
{
|
||||
auto const seconds = UtcNow().ToInterval() / WindowsToPosixOffsetSeconds;
|
||||
return (seconds >= WindowsToPosixOffsetSeconds) ? (seconds - WindowsToPosixOffsetSeconds)
|
||||
: UtcTimestampInvalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct an uninitialized (!@IsInitialized()) instance of @DateTime.
|
||||
*/
|
||||
DateTime() : m_interval(0) {}
|
||||
|
||||
/**
|
||||
* @brief Create @DateTime from a string representing time in UTC in the specified format.
|
||||
*
|
||||
* @param timeString A string with the date and time.
|
||||
* @param format A format to which /p timeString adheres to.
|
||||
*
|
||||
* @return @DateTime that was constructed from the \p timeString; Uninitialized
|
||||
* (!@IsInitialized()) @DateTime if parsing \p timeString was not successful.
|
||||
*
|
||||
* @throw DateTimeException If \p format is not recognized.
|
||||
*/
|
||||
static DateTime FromString(
|
||||
std::string const& timeString,
|
||||
DateFormat format = DateFormat::Rfc1123);
|
||||
|
||||
/**
|
||||
* @brief Get a string representation of the @DateTime.
|
||||
*
|
||||
* @param format The representation format to use.
|
||||
*
|
||||
* @throw DateTimeException If year exceeds 9999, or if \p format is not recognized.
|
||||
*/
|
||||
std::string ToString(DateFormat format = DateFormat::Rfc1123) const;
|
||||
|
||||
/// Get the integral time value.
|
||||
IntervalType ToInterval() const { return m_interval; }
|
||||
|
||||
/// Subtract an interval from @DateTime.
|
||||
DateTime operator-(IntervalType value) const { return DateTime(m_interval - value); }
|
||||
|
||||
/// Add an interval to @DateTime.
|
||||
DateTime operator+(IntervalType value) const { return DateTime(m_interval + value); }
|
||||
|
||||
/// Compare two instances of @DateTime for equality.
|
||||
bool operator==(DateTime dt) const { return m_interval == dt.m_interval; }
|
||||
|
||||
/// Compare two instances of @DateTime for inequality.
|
||||
bool operator!=(const DateTime& dt) const { return !(*this == dt); }
|
||||
|
||||
/// Compare the chronological order of two @DateTime instances.
|
||||
bool operator>(const DateTime& dt) const { return this->m_interval > dt.m_interval; }
|
||||
|
||||
/// Compare the chronological order of two @DateTime instances.
|
||||
bool operator<(const DateTime& dt) const { return this->m_interval < dt.m_interval; }
|
||||
|
||||
/// Compare the chronological order of two @DateTime instances.
|
||||
bool operator>=(const DateTime& dt) const { return this->m_interval >= dt.m_interval; }
|
||||
|
||||
/// Compare the chronological order of two @DateTime instances.
|
||||
bool operator<=(const DateTime& dt) const { return this->m_interval <= dt.m_interval; }
|
||||
|
||||
/// Create an interval from milliseconds.
|
||||
static IntervalType FromMilliseconds(unsigned int milliseconds)
|
||||
{
|
||||
return milliseconds * TicksPerMillisecond;
|
||||
}
|
||||
|
||||
/// Create an interval from seconds.
|
||||
static IntervalType FromSeconds(unsigned int seconds) { return seconds * TicksPerSecond; }
|
||||
|
||||
/// Create an interval from minutes.
|
||||
static IntervalType FromMinutes(unsigned int minutes) { return minutes * TicksPerMinute; }
|
||||
|
||||
/// Create an interval from hours.
|
||||
static IntervalType FromHours(unsigned int hours) { return hours * TicksPerHour; }
|
||||
|
||||
/// Create an interval from days.
|
||||
static IntervalType FromDays(unsigned int days) { return days * TicksPerDay; }
|
||||
|
||||
/// Checks whether this instance of @DateTime is initialized.
|
||||
bool IsInitialized() const { return m_interval != 0; }
|
||||
|
||||
private:
|
||||
friend IntervalType operator-(DateTime t1, DateTime t2);
|
||||
|
||||
static constexpr IntervalType TicksPerMillisecond = static_cast<IntervalType>(10000);
|
||||
static constexpr IntervalType TicksPerSecond = 1000 * TicksPerMillisecond;
|
||||
static constexpr IntervalType TicksPerMinute = 60 * TicksPerSecond;
|
||||
static constexpr IntervalType TicksPerHour = 60 * TicksPerMinute;
|
||||
static constexpr IntervalType TicksPerDay = 24 * TicksPerHour;
|
||||
|
||||
// Private constructor. Use static methods to create an instance.
|
||||
DateTime(IntervalType interval) : m_interval(interval) {}
|
||||
|
||||
// Storing as hundreds of nanoseconds 10e-7, i.e. 1 here equals 100ns.
|
||||
IntervalType m_interval;
|
||||
};
|
||||
|
||||
inline DateTime::IntervalType operator-(DateTime t1, DateTime t2)
|
||||
{
|
||||
auto diff = (t1.m_interval - t2.m_interval);
|
||||
|
||||
// Round it down to seconds
|
||||
diff /= DateTime::TicksPerSecond;
|
||||
|
||||
return static_cast<DateTime::IntervalType>(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief An exception that gets thrown when @DateTime error occurs.
|
||||
*/
|
||||
class DateTimeException : public std::runtime_error {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct with message string.
|
||||
*
|
||||
* @param msg Message string.
|
||||
*/
|
||||
explicit DateTimeException(std::string const& msg) : std::runtime_error(msg) {}
|
||||
};
|
||||
}} // namespace Azure::Core
|
||||
792
sdk/core/azure-core/src/datetime.cpp
Normal file
792
sdk/core/azure-core/src/datetime.cpp
Normal file
@ -0,0 +1,792 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "azure/core/datetime.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
using namespace Azure::Core;
|
||||
|
||||
DateTime DateTime::UtcNow()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FILETIME fileTime = {};
|
||||
GetSystemTimeAsFileTime(&fileTime);
|
||||
|
||||
ULARGE_INTEGER largeInt = {};
|
||||
largeInt.LowPart = fileTime.dwLowDateTime;
|
||||
largeInt.HighPart = fileTime.dwHighDateTime;
|
||||
|
||||
return DateTime(largeInt.QuadPart);
|
||||
#else
|
||||
struct timeval time = {};
|
||||
if (gettimeofday(&time, nullptr) != 0)
|
||||
{
|
||||
return DateTime();
|
||||
}
|
||||
|
||||
IntervalType result = WindowsToPosixOffsetSeconds + time.tv_sec;
|
||||
result *= TicksPerSecond; // convert to 10e-7
|
||||
result += time.tv_usec * 10; // convert and add microseconds, 10e-6 to 10e-7
|
||||
|
||||
return DateTime(static_cast<IntervalType>(result));
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct ComputeYearResult
|
||||
{
|
||||
int Year;
|
||||
int SecondsLeftThisYear;
|
||||
};
|
||||
|
||||
constexpr int SecondsInMinute = 60;
|
||||
constexpr int SecondsInHour = SecondsInMinute * 60;
|
||||
constexpr int SecondsInDay = 24 * SecondsInHour;
|
||||
|
||||
constexpr int DaysInYear = 365;
|
||||
|
||||
constexpr ComputeYearResult ComputeYear(int64_t secondsSince1900)
|
||||
{
|
||||
constexpr int64_t SecondsFrom1601To1900 = 9435484800LL;
|
||||
|
||||
constexpr int DaysIn4Years = DaysInYear * 4 + 1;
|
||||
|
||||
constexpr int DaysIn100Years = DaysIn4Years * 25 - 1;
|
||||
constexpr int DaysIn400Years = DaysIn100Years * 4 + 1;
|
||||
|
||||
constexpr int SecondsInYear = SecondsInDay * DaysInYear;
|
||||
constexpr int SecondsIn4Years = SecondsInDay * DaysIn4Years;
|
||||
|
||||
constexpr int64_t SecondsIn100Years = static_cast<int64_t>(SecondsInDay) * DaysIn100Years;
|
||||
constexpr int64_t SecondsIn400Years = static_cast<int64_t>(SecondsInDay) * DaysIn400Years;
|
||||
|
||||
int64_t secondsLeft
|
||||
= secondsSince1900 + SecondsFrom1601To1900; // shift to start of this 400 year cycle
|
||||
|
||||
int year400 = static_cast<int>(secondsLeft / SecondsIn400Years);
|
||||
secondsLeft -= year400 * SecondsIn400Years;
|
||||
|
||||
int year100 = static_cast<int>(secondsLeft / SecondsIn100Years);
|
||||
secondsLeft -= year100 * SecondsIn100Years;
|
||||
|
||||
int year4 = static_cast<int>(secondsLeft / SecondsIn4Years);
|
||||
int secondsInt = static_cast<int>(secondsLeft - static_cast<int64_t>(year4) * SecondsIn4Years);
|
||||
|
||||
int year1 = secondsInt / SecondsInYear;
|
||||
secondsInt -= year1 * SecondsInYear;
|
||||
|
||||
// shift back to 1900 base from 1601:
|
||||
return {year400 * 400 + year100 * 100 + year4 * 4 + year1 - 299, secondsInt};
|
||||
}
|
||||
|
||||
constexpr bool IsLeapYear(int year)
|
||||
{
|
||||
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
||||
}
|
||||
|
||||
// The following table assumes no leap year; leap year is added separately
|
||||
constexpr unsigned short const CumulativeDaysToMonth[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
|
||||
};
|
||||
|
||||
constexpr unsigned short const CumulativeDaysToMonthLeap[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
|
||||
};
|
||||
|
||||
constexpr char const dayNames[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
|
||||
constexpr char const monthNames[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
|
||||
|
||||
constexpr int64_t TicksFromWindowsEpochTo1900 = 0x014F373BFDE04000LL;
|
||||
} // namespace
|
||||
|
||||
std::string DateTime::ToString(DateFormat format) const
|
||||
{
|
||||
if (m_interval > 2650467743990000000LL)
|
||||
{
|
||||
throw DateTimeException("The requested year exceeds the year 9999.");
|
||||
}
|
||||
|
||||
int64_t const epochAdjusted = static_cast<int64_t>(m_interval) - TicksFromWindowsEpochTo1900;
|
||||
int64_t const secondsSince1900 = epochAdjusted / TicksPerSecond; // convert to seconds
|
||||
int const fracSec = static_cast<int>(epochAdjusted % TicksPerSecond);
|
||||
|
||||
auto const yearData = ComputeYear(secondsSince1900);
|
||||
int const year = yearData.Year;
|
||||
int const yearDay = yearData.SecondsLeftThisYear / SecondsInDay;
|
||||
|
||||
int leftover = yearData.SecondsLeftThisYear % SecondsInDay;
|
||||
int const hour = leftover / SecondsInHour;
|
||||
|
||||
leftover = leftover % SecondsInHour;
|
||||
int const minute = leftover / SecondsInMinute;
|
||||
|
||||
leftover = leftover % SecondsInMinute;
|
||||
|
||||
auto const& monthTable = IsLeapYear(year) ? CumulativeDaysToMonthLeap : CumulativeDaysToMonth;
|
||||
int month = 0;
|
||||
while (month < 11 && monthTable[month + 1] <= yearDay)
|
||||
{
|
||||
++month;
|
||||
}
|
||||
|
||||
auto const monthDay = yearDay - monthTable[month] + 1;
|
||||
auto const weekday = static_cast<int>((secondsSince1900 / SecondsInDay + 1) % 7);
|
||||
|
||||
char outBuffer[38]{}; // Thu, 01 Jan 1970 00:00:00 GMT\0
|
||||
// 1970-01-01T00:00:00.1234567Z\0
|
||||
|
||||
char* outCursor = outBuffer;
|
||||
switch (format)
|
||||
{
|
||||
case DateFormat::Rfc1123:
|
||||
#ifdef _MSC_VER
|
||||
sprintf_s(
|
||||
#else
|
||||
std::sprintf(
|
||||
#endif
|
||||
outCursor,
|
||||
#ifdef _MSC_VER
|
||||
26,
|
||||
#endif
|
||||
"%s, %02d %s %04d %02d:%02d:%02d",
|
||||
dayNames + 4ULL * static_cast<uint64_t>(weekday),
|
||||
monthDay,
|
||||
monthNames + 4ULL * static_cast<uint64_t>(month),
|
||||
year + 1900,
|
||||
hour,
|
||||
minute,
|
||||
leftover);
|
||||
|
||||
outCursor += 25;
|
||||
memcpy(outCursor, " GMT", 4);
|
||||
outCursor += 4;
|
||||
return std::string(outBuffer, outCursor);
|
||||
|
||||
case DateFormat::Iso8601:
|
||||
#ifdef _MSC_VER
|
||||
sprintf_s(
|
||||
#else
|
||||
std::sprintf(
|
||||
#endif
|
||||
outCursor,
|
||||
#ifdef _MSC_VER
|
||||
20,
|
||||
#endif
|
||||
"%04d-%02d-%02dT%02d:%02d:%02d",
|
||||
year + 1900,
|
||||
month + 1,
|
||||
monthDay,
|
||||
hour,
|
||||
minute,
|
||||
leftover);
|
||||
|
||||
outCursor += 19;
|
||||
if (fracSec != 0)
|
||||
{
|
||||
// Append fractional second, which is a 7-digit value with no trailing zeros
|
||||
// This way, '1200' becomes '00012'
|
||||
size_t appended =
|
||||
#ifdef _MSC_VER
|
||||
sprintf_s(
|
||||
#else
|
||||
std::sprintf(
|
||||
#endif
|
||||
outCursor,
|
||||
#ifdef _MSC_VER
|
||||
9,
|
||||
#endif
|
||||
".%07d",
|
||||
fracSec);
|
||||
|
||||
while (outCursor[appended - 1] == '0')
|
||||
{
|
||||
--appended; // trim trailing zeros
|
||||
}
|
||||
|
||||
outCursor += appended;
|
||||
}
|
||||
|
||||
*outCursor = 'Z';
|
||||
++outCursor;
|
||||
return std::string(outBuffer, outCursor);
|
||||
|
||||
default:
|
||||
throw DateTimeException("Unrecognized date format.");
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr bool StringStartsWith(char const* haystack, char const* needle)
|
||||
{
|
||||
while (*needle)
|
||||
{
|
||||
if (*haystack != *needle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
++haystack;
|
||||
++needle;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <int N = 9> constexpr bool IsDigit(char c)
|
||||
{
|
||||
return ((unsigned char)((unsigned char)(c) - '0') <= N);
|
||||
}
|
||||
|
||||
constexpr int StringToDoubleDigitInt(char const* str)
|
||||
{
|
||||
return (static_cast<unsigned char>(str[0]) - '0') * 10
|
||||
+ (static_cast<unsigned char>(str[1]) - '0');
|
||||
}
|
||||
|
||||
constexpr bool ValidateDay(int day, int month, int year)
|
||||
{
|
||||
if (day < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Month is 0-based
|
||||
switch (month)
|
||||
{
|
||||
case 1: // Feb
|
||||
return day <= (28 + IsLeapYear(year));
|
||||
break;
|
||||
case 3: // Apr
|
||||
case 5: // Jun
|
||||
case 8: // Sep
|
||||
case 10: // Nov
|
||||
return day <= 30;
|
||||
break;
|
||||
default:
|
||||
return day <= 31;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int GetYearDay(int month, int monthDay, int year)
|
||||
{
|
||||
return CumulativeDaysToMonth[month] + monthDay + (IsLeapYear(year) && month > 1) - 1;
|
||||
}
|
||||
|
||||
constexpr int CountLeapYears(int yearsSince1900)
|
||||
{
|
||||
int tmpYears = yearsSince1900 + 299; // shift into 1601, the first 400 year cycle including 1900
|
||||
|
||||
int const year400 = tmpYears / 400;
|
||||
tmpYears -= year400 * 400;
|
||||
|
||||
int result = year400 * 97;
|
||||
|
||||
int const year100 = tmpYears / 100;
|
||||
tmpYears -= year100 * 100;
|
||||
result += year100 * 24;
|
||||
|
||||
result += tmpYears / 4;
|
||||
|
||||
// subtract off leap years from 1601
|
||||
result -= 72;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr int64_t AdjustTimezone(
|
||||
int64_t result,
|
||||
unsigned char chSign,
|
||||
int adjustHours,
|
||||
int adjustMinutes)
|
||||
{
|
||||
if (adjustHours > 23)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// adjustMinutes > 59 is impossible due to digit 5 check
|
||||
int const tzAdjust = adjustMinutes * 60 + adjustHours * 60 * 60;
|
||||
if (chSign == '-')
|
||||
{
|
||||
if (std::numeric_limits<int64_t>::max() - result < tzAdjust)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
result += tzAdjust;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tzAdjust > result)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
result -= tzAdjust;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/*
|
||||
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 DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
{
|
||||
DateTime result = {};
|
||||
|
||||
int64_t secondsSince1900 = 0;
|
||||
uint64_t fracSec = 0;
|
||||
|
||||
auto str = dateString.c_str();
|
||||
if (format == DateFormat::Rfc1123)
|
||||
{
|
||||
int parsedWeekday = 0;
|
||||
for (; parsedWeekday < 7; ++parsedWeekday)
|
||||
{
|
||||
if (StringStartsWith(str, dayNames + static_cast<uint64_t>(parsedWeekday) * 4ULL)
|
||||
&& str[3] == ',' && str[4] == ' ')
|
||||
{
|
||||
str += 5; // parsed day of week
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int monthDay;
|
||||
if (IsDigit<3>(str[0]) && IsDigit(str[1]) && str[2] == ' ')
|
||||
{
|
||||
monthDay = StringToDoubleDigitInt(str); // validity checked later
|
||||
str += 3; // parsed day
|
||||
}
|
||||
else if (IsDigit(str[0]) && str[1] == ' ')
|
||||
{
|
||||
monthDay = str[0] - '0';
|
||||
str += 2; // parsed day
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (monthDay == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int month = 0;
|
||||
for (;;)
|
||||
{
|
||||
if (StringStartsWith(str, monthNames + static_cast<uint64_t>(month) * 4ULL))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
++month;
|
||||
if (month == 12)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (str[3] != ' ')
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
str += 4; // parsed month
|
||||
|
||||
if (!IsDigit(str[0]) || !IsDigit(str[1]) || !IsDigit(str[2]) || !IsDigit(str[3])
|
||||
|| str[4] != ' ')
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int year = (str[0] - '0') * 1000 + (str[1] - '0') * 100 + (str[2] - '0') * 10 + (str[3] - '0');
|
||||
if (year < 1900)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// days in month validity check
|
||||
if (!ValidateDay(monthDay, month, year))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
str += 5; // parsed year
|
||||
int const yearDay = GetYearDay(month, monthDay, year);
|
||||
|
||||
if (!IsDigit<2>(str[0]) || !IsDigit(str[1]) || str[2] != ':' || !IsDigit<5>(str[3])
|
||||
|| !IsDigit(str[4]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int const hour = StringToDoubleDigitInt(str);
|
||||
if (hour > 23)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
str += 3; // parsed hour
|
||||
|
||||
int const minute = StringToDoubleDigitInt(str);
|
||||
str += 2; // parsed mins
|
||||
|
||||
int sec = 0;
|
||||
if (str[0] == ':')
|
||||
{
|
||||
if (!IsDigit<6>(str[1]) || !IsDigit(str[2]) || str[3] != ' ')
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
sec = StringToDoubleDigitInt(str + 1);
|
||||
str += 4; // parsed seconds
|
||||
}
|
||||
else if (str[0] == ' ')
|
||||
{
|
||||
str += 1; // parsed seconds
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (sec > 60)
|
||||
{ // 60 to allow leap seconds
|
||||
return result;
|
||||
}
|
||||
|
||||
year -= 1900;
|
||||
int const daysSince1900 = year * DaysInYear + CountLeapYears(year) + yearDay;
|
||||
|
||||
if (parsedWeekday != 7)
|
||||
{
|
||||
int const actualWeekday = (daysSince1900 + 1) % 7;
|
||||
if (parsedWeekday != actualWeekday)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
secondsSince1900 = static_cast<IntervalType>(daysSince1900) * SecondsInDay
|
||||
+ static_cast<IntervalType>(hour) * SecondsInHour
|
||||
+ static_cast<IntervalType>(minute) * SecondsInMinute + sec;
|
||||
|
||||
fracSec = 0;
|
||||
if (!StringStartsWith(str, "GMT") && !StringStartsWith(str, "UT"))
|
||||
{
|
||||
// some timezone adjustment necessary
|
||||
auto tzCh = '-';
|
||||
int tzHours;
|
||||
int tzMinutes = 0;
|
||||
if (StringStartsWith(str, "EDT"))
|
||||
{
|
||||
tzHours = 4;
|
||||
}
|
||||
else if (StringStartsWith(str, "EST") || StringStartsWith(str, "CDT"))
|
||||
{
|
||||
tzHours = 5;
|
||||
}
|
||||
else if (StringStartsWith(str, "CST") || StringStartsWith(str, "MDT"))
|
||||
{
|
||||
tzHours = 6;
|
||||
}
|
||||
else if (StringStartsWith(str, "MST") || StringStartsWith(str, "PDT"))
|
||||
{
|
||||
tzHours = 7;
|
||||
}
|
||||
else if (StringStartsWith(str, "PST"))
|
||||
{
|
||||
tzHours = 8;
|
||||
}
|
||||
else if (
|
||||
(str[0] == '+' || str[0] == '-') && IsDigit<2>(str[1]) && IsDigit(str[2])
|
||||
&& IsDigit<5>(str[3]) && IsDigit(str[4]))
|
||||
{
|
||||
tzCh = str[0];
|
||||
tzHours = StringToDoubleDigitInt(str + 1);
|
||||
tzMinutes = StringToDoubleDigitInt(str + 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
secondsSince1900
|
||||
= AdjustTimezone(secondsSince1900, static_cast<unsigned char>(tzCh), tzHours, tzMinutes);
|
||||
|
||||
if (secondsSince1900 < 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (format == DateFormat::Iso8601)
|
||||
{
|
||||
// parse year
|
||||
if (!IsDigit(str[0]) || !IsDigit(str[1]) || !IsDigit(str[2]) || !IsDigit(str[3]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int year = (str[0] - '0') * 1000 + (str[1] - '0') * 100 + (str[2] - '0') * 10 + (str[3] - '0');
|
||||
if (year < 1900)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
str += 4;
|
||||
if (*str == '-')
|
||||
{
|
||||
++str;
|
||||
}
|
||||
|
||||
// parse month
|
||||
if (!IsDigit<1>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int month = StringToDoubleDigitInt(str);
|
||||
if (month < 1 || month > 12)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
month -= 1;
|
||||
str += 2;
|
||||
|
||||
if (*str == '-')
|
||||
{
|
||||
++str;
|
||||
}
|
||||
|
||||
// parse day
|
||||
if (!IsDigit<3>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int monthDay = StringToDoubleDigitInt(str);
|
||||
if (!ValidateDay(monthDay, month, year))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int const yearDay = GetYearDay(month, monthDay, year);
|
||||
|
||||
str += 2;
|
||||
year -= 1900;
|
||||
int daysSince1900 = year * DaysInYear + CountLeapYears(year) + yearDay;
|
||||
|
||||
if (str[0] != 'T' && str[0] != 't')
|
||||
{
|
||||
// No time
|
||||
secondsSince1900 = static_cast<int64_t>(daysSince1900) * SecondsInDay;
|
||||
|
||||
result.m_interval = static_cast<IntervalType>(
|
||||
secondsSince1900 * TicksPerSecond + fracSec + TicksFromWindowsEpochTo1900);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
++str; // skip 'T'
|
||||
|
||||
// parse hour
|
||||
if (!IsDigit<2>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int const hour = StringToDoubleDigitInt(str);
|
||||
str += 2;
|
||||
if (hour > 23)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (*str == ':')
|
||||
{
|
||||
++str;
|
||||
}
|
||||
|
||||
// parse minute
|
||||
if (!IsDigit<5>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int const minute = StringToDoubleDigitInt(str);
|
||||
// minute > 59 is impossible because we checked that the first digit is <= 5 in the basic format
|
||||
// check above
|
||||
|
||||
str += 2;
|
||||
|
||||
if (*str == ':')
|
||||
{
|
||||
++str;
|
||||
}
|
||||
|
||||
// parse seconds
|
||||
if (!IsDigit<6>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
int const sec = StringToDoubleDigitInt(str);
|
||||
// We allow 60 to account for leap seconds
|
||||
if (sec > 60)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
str += 2;
|
||||
if (str[0] == '.' && IsDigit(str[1]))
|
||||
{
|
||||
++str;
|
||||
int digits = 7;
|
||||
for (;;)
|
||||
{
|
||||
fracSec *= 10;
|
||||
fracSec += static_cast<uint64_t>(*str) - static_cast<uint64_t>('0');
|
||||
|
||||
--digits;
|
||||
++str;
|
||||
|
||||
if (digits == 0)
|
||||
{
|
||||
while (IsDigit(*str))
|
||||
{
|
||||
// consume remaining fractional second digits we can't use
|
||||
++str;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!IsDigit(*str))
|
||||
{
|
||||
// no more digits in the input, do the remaining multiplies we need
|
||||
for (; digits != 0; --digits)
|
||||
{
|
||||
fracSec *= 10;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
secondsSince1900 = static_cast<int64_t>(daysSince1900) * SecondsInDay
|
||||
+ static_cast<int64_t>(hour) * SecondsInHour
|
||||
+ static_cast<int64_t>(minute) * SecondsInMinute + sec;
|
||||
|
||||
if (str[0] == 'Z' || str[0] == 'z')
|
||||
{
|
||||
// no adjustment needed for zulu time
|
||||
}
|
||||
else if (str[0] == '+' || str[0] == '-')
|
||||
{
|
||||
unsigned char const offsetDirection = static_cast<unsigned char>(str[0]);
|
||||
if (!IsDigit<2>(str[1]) || !IsDigit(str[2]) || str[3] != ':' || !IsDigit<5>(str[4])
|
||||
|| !IsDigit(str[5]))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
secondsSince1900 = AdjustTimezone(
|
||||
secondsSince1900,
|
||||
offsetDirection,
|
||||
StringToDoubleDigitInt(str + 1),
|
||||
StringToDoubleDigitInt(str + 4));
|
||||
if (secondsSince1900 < 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the timezone is malformed, but cpprestsdk currently accepts this as no timezone
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw DateTimeException("Unrecognized date format.");
|
||||
}
|
||||
|
||||
result.m_interval = static_cast<IntervalType>(
|
||||
secondsSince1900 * TicksPerSecond + fracSec + TicksFromWindowsEpochTo1900);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -22,18 +22,18 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
include(GoogleTest)
|
||||
add_executable (
|
||||
${TARGET_NAME}
|
||||
context.cpp
|
||||
datetime.cpp
|
||||
http.cpp
|
||||
http.hpp
|
||||
logging.cpp
|
||||
main.cpp
|
||||
nullable.cpp
|
||||
string.cpp
|
||||
telemetry_policy.cpp
|
||||
transport_adapter.cpp
|
||||
transport_adapter.hpp
|
||||
transport_adapter_file_upload.cpp
|
||||
uuid.cpp
|
||||
context.cpp)
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME} PRIVATE azure-core gtest)
|
||||
|
||||
|
||||
459
sdk/core/azure-core/test/ut/datetime.cpp
Normal file
459
sdk/core/azure-core/test/ut/datetime.cpp
Normal file
@ -0,0 +1,459 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <azure/core/datetime.hpp>
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace Azure::Core;
|
||||
|
||||
TEST(DateTime, ParseDateAndTimeBasic)
|
||||
{
|
||||
auto dt1 = DateTime::FromString("20130517T00:00:00Z", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt1.ToInterval());
|
||||
|
||||
auto dt2 = DateTime::FromString("Fri, 17 May 2013 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_NE(0u, dt2.ToInterval());
|
||||
|
||||
EXPECT_EQ(dt1.ToInterval(), dt2.ToInterval());
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseDateAndTimeExtended)
|
||||
{
|
||||
auto dt1 = DateTime::FromString("2013-05-17T00:00:00Z", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt1.ToInterval());
|
||||
|
||||
auto dt2 = DateTime::FromString("Fri, 17 May 2013 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_NE(0u, dt2.ToInterval());
|
||||
|
||||
EXPECT_EQ(dt1.ToInterval(), dt2.ToInterval());
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseDateBasic)
|
||||
{
|
||||
{
|
||||
auto dt = DateTime::FromString("20130517", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt.ToInterval());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseDateExtended)
|
||||
{
|
||||
{
|
||||
auto dt = DateTime::FromString("2013-05-17", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt.ToInterval());
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void TestDateTimeRoundtrip(std::string const& str, std::string const& strExpected)
|
||||
{
|
||||
auto dt = DateTime::FromString(str, DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToString(DateTime::DateFormat::Iso8601);
|
||||
EXPECT_EQ(str2, strExpected);
|
||||
}
|
||||
|
||||
void TestDateTimeRoundtrip(std::string const& str) { TestDateTimeRoundtrip(str, str); }
|
||||
} // namespace
|
||||
|
||||
TEST(DateTime, ParseTimeRoundrip1)
|
||||
{
|
||||
// Preserve all 7 digits after the comma:
|
||||
TestDateTimeRoundtrip("2013-11-19T14:30:59.1234567Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRoundrip2)
|
||||
{
|
||||
// lose the last '000'
|
||||
TestDateTimeRoundtrip("2013-11-19T14:30:59.1234567000Z", "2013-11-19T14:30:59.1234567Z");
|
||||
|
||||
// lose the last '999' without rounding up
|
||||
TestDateTimeRoundtrip("2013-11-19T14:30:59.1234567999Z", "2013-11-19T14:30:59.1234567Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRoundrip3)
|
||||
{
|
||||
// leading 0-s after the comma, tricky to parse correctly
|
||||
TestDateTimeRoundtrip("2013-11-19T14:30:59.00123Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRoundrip4)
|
||||
{
|
||||
// another leading 0 test
|
||||
TestDateTimeRoundtrip("2013-11-19T14:30:59.0000001Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRoundrip5)
|
||||
{
|
||||
// this is going to be truncated
|
||||
TestDateTimeRoundtrip("2013-11-19T14:30:59.00000001Z", "2013-11-19T14:30:59Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRoundrip6)
|
||||
{
|
||||
// Only one digit after the dot
|
||||
TestDateTimeRoundtrip("2013-11-19T14:30:59.5Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRoundripYear1900) { TestDateTimeRoundtrip("1900-01-01T00:00:00Z"); }
|
||||
|
||||
TEST(DateTime, ParseTimeRoundripYear9999) { TestDateTimeRoundtrip("9999-12-31T23:59:59Z"); }
|
||||
|
||||
TEST(DateTime, EmittingTimeCorrectDay)
|
||||
{
|
||||
auto const test = DateTime() + 132004507640000000LL; // 2019-04-22T23:52:44 is a Monday
|
||||
auto const actual = test.ToString(DateTime::DateFormat::Rfc1123);
|
||||
std::string const expected("Mon");
|
||||
EXPECT_EQ(actual.substr(0, 3), expected);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void TestRfc1123IsTimeT(char const* str, DateTime::IntervalType t)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Rfc1123);
|
||||
auto interval = dt.ToInterval();
|
||||
EXPECT_EQ(0, interval % 10000000);
|
||||
interval /= 10000000;
|
||||
interval -= 11644473600; // NT epoch adjustment
|
||||
EXPECT_EQ(t, interval);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(DateTime, ParseTimeRfc1123AcceptsEachDay)
|
||||
{
|
||||
TestRfc1123IsTimeT("01 Jan 1970 00:00:00 GMT", 0);
|
||||
TestRfc1123IsTimeT("Fri, 02 Jan 1970 00:00:00 GMT", 86400 * 1);
|
||||
TestRfc1123IsTimeT("Sat, 03 Jan 1970 00:00:00 GMT", 86400 * 2);
|
||||
TestRfc1123IsTimeT("Sun, 04 Jan 1970 00:00:00 GMT", 86400 * 3);
|
||||
TestRfc1123IsTimeT("Mon, 05 Jan 1970 00:00:00 GMT", 86400 * 4);
|
||||
TestRfc1123IsTimeT("Tue, 06 Jan 1970 00:00:00 GMT", 86400 * 5);
|
||||
TestRfc1123IsTimeT("Wed, 07 Jan 1970 00:00:00 GMT", 86400 * 6);
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRfc1123BoundaryCases)
|
||||
{
|
||||
TestRfc1123IsTimeT("01 Jan 1970 00:00:00 GMT", 0);
|
||||
TestRfc1123IsTimeT("19 Jan 2038 03:14:06 GMT", std::numeric_limits<int>::max() - 1);
|
||||
TestRfc1123IsTimeT("19 Jan 2038 03:13:07 -0001", std::numeric_limits<int>::max());
|
||||
TestRfc1123IsTimeT("19 Jan 2038 03:14:07 -0000", std::numeric_limits<int>::max());
|
||||
TestRfc1123IsTimeT("14 Jan 2019 23:16:21 +0000", 1547507781);
|
||||
TestRfc1123IsTimeT("14 Jan 2019 23:16:21 -0001", 1547507841);
|
||||
TestRfc1123IsTimeT("14 Jan 2019 23:16:21 +0001", 1547507721);
|
||||
TestRfc1123IsTimeT("14 Jan 2019 23:16:21 -0100", 1547511381);
|
||||
TestRfc1123IsTimeT("14 Jan 2019 23:16:21 +0100", 1547504181);
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRfc1123UseEachField)
|
||||
{
|
||||
TestRfc1123IsTimeT("02 Jan 1970 00:00:00 GMT", 86400);
|
||||
TestRfc1123IsTimeT("12 Jan 1970 00:00:00 GMT", 950400);
|
||||
TestRfc1123IsTimeT("01 Feb 1970 00:00:00 GMT", 2678400);
|
||||
TestRfc1123IsTimeT("01 Jan 2000 00:00:00 GMT", 946684800);
|
||||
TestRfc1123IsTimeT("01 Jan 2100 00:00:00 GMT", 4102444800);
|
||||
TestRfc1123IsTimeT("01 Jan 1990 00:00:00 GMT", 631152000);
|
||||
TestRfc1123IsTimeT("01 Jan 1971 00:00:00 GMT", 31536000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 10:00:00 GMT", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 01:00:00 GMT", 3600);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 00:10:00 GMT", 600);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 00:01:00 GMT", 60);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 00:00:10 GMT", 10);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 00:00:01 GMT", 1);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 10:00:00 GMT", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 02:00:00 PST", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 03:00:00 PDT", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 03:00:00 MST", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 04:00:00 MDT", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 04:00:00 CST", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 05:00:00 CDT", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 05:00:00 EST", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 06:00:00 EDT", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 06:00:00 -0400", 36000);
|
||||
TestRfc1123IsTimeT("01 Jan 1970 05:59:00 -0401", 36000);
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRfc1123MaxDays)
|
||||
{
|
||||
TestRfc1123IsTimeT("31 Jan 1970 00:00:00 GMT", 2592000);
|
||||
TestRfc1123IsTimeT("28 Feb 2019 00:00:00 GMT", 1551312000); // non leap year allows feb 28
|
||||
TestRfc1123IsTimeT("29 Feb 2020 00:00:00 GMT", 1582934400); // leap year allows feb 29
|
||||
TestRfc1123IsTimeT("31 Mar 1970 00:00:00 GMT", 7689600);
|
||||
TestRfc1123IsTimeT("30 Apr 1970 00:00:00 GMT", 10281600);
|
||||
TestRfc1123IsTimeT("31 May 1970 00:00:00 GMT", 12960000);
|
||||
TestRfc1123IsTimeT("30 Jun 1970 00:00:00 GMT", 15552000);
|
||||
TestRfc1123IsTimeT("31 Jul 1970 00:00:00 GMT", 18230400);
|
||||
TestRfc1123IsTimeT("31 Aug 1970 00:00:00 GMT", 20908800);
|
||||
TestRfc1123IsTimeT("30 Sep 1970 00:00:00 GMT", 23500800);
|
||||
TestRfc1123IsTimeT("31 Oct 1970 00:00:00 GMT", 26179200);
|
||||
TestRfc1123IsTimeT("30 Nov 1970 00:00:00 GMT", 28771200);
|
||||
TestRfc1123IsTimeT("31 Dec 1970 00:00:00 GMT", 31449600);
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRfc1123InvalidCases)
|
||||
{
|
||||
std::string const badStrings[] = {
|
||||
"Ahu, 01 Jan 1970 00:00:00 GMT", // bad letters in each place
|
||||
"TAu, 01 Jan 1970 00:00:00 GMT",
|
||||
"ThA, 01 Jan 1970 00:00:00 GMT",
|
||||
"ThuA 01 Jan 1970 00:00:00 GMT",
|
||||
"Thu,A01 Jan 1970 00:00:00 GMT",
|
||||
"Thu, A1 Jan 1970 00:00:00 GMT",
|
||||
"Thu, 0A Jan 1970 00:00:00 GMT",
|
||||
"Thu, 01AJan 1970 00:00:00 GMT",
|
||||
"Thu, 01 Aan 1970 00:00:00 GMT",
|
||||
"Thu, 01 JAn 1970 00:00:00 GMT",
|
||||
"Thu, 01 JaA 1970 00:00:00 GMT",
|
||||
"Thu, 01 JanA1970 00:00:00 GMT",
|
||||
"Thu, 01 Jan A970 00:00:00 GMT",
|
||||
"Thu, 01 Jan 1A70 00:00:00 GMT",
|
||||
"Thu, 01 Jan 19A0 00:00:00 GMT",
|
||||
"Thu, 01 Jan 197A 00:00:00 GMT",
|
||||
"Thu, 01 Jan 1970A00:00:00 GMT",
|
||||
"Thu, 01 Jan 1970 A0:00:00 GMT",
|
||||
"Thu, 01 Jan 1970 0A:00:00 GMT",
|
||||
"Thu, 01 Jan 1970 00A00:00 GMT",
|
||||
"Thu, 01 Jan 1970 00:A0:00 GMT",
|
||||
"Thu, 01 Jan 1970 00:0A:00 GMT",
|
||||
"Thu, 01 Jan 1970 00:00A00 GMT",
|
||||
"Thu, 01 Jan 1970 00:00:A0 GMT",
|
||||
"Thu, 01 Jan 1970 00:00:0A GMT",
|
||||
"Thu, 01 Jan 1970 00:00:00AGMT",
|
||||
"Thu, 01 Jan 1970 00:00:00 AMT",
|
||||
"Thu, 01 Jan 1970 00:00:00 GAT",
|
||||
"Thu, 01 Jan 1970 00:00:00 GMA",
|
||||
"", // truncation
|
||||
"T",
|
||||
"Th",
|
||||
"Thu",
|
||||
"Thu,",
|
||||
"Thu, ",
|
||||
"Thu, 0",
|
||||
"Thu, 01",
|
||||
"Thu, 01 ",
|
||||
"Thu, 01 J",
|
||||
"Thu, 01 Ja",
|
||||
"Thu, 01 Jan",
|
||||
"Thu, 01 Jan ",
|
||||
"Thu, 01 Jan 1",
|
||||
"Thu, 01 Jan 19",
|
||||
"Thu, 01 Jan 197",
|
||||
"Thu, 01 Jan 1970",
|
||||
"Thu, 01 Jan 1970 ",
|
||||
"Thu, 01 Jan 1970 0",
|
||||
"Thu, 01 Jan 1970 00",
|
||||
"Thu, 01 Jan 1970 00:",
|
||||
"Thu, 01 Jan 1970 00:0",
|
||||
"Thu, 01 Jan 1970 00:00",
|
||||
"Thu, 01 Jan 1970 00:00:",
|
||||
"Thu, 01 Jan 1970 00:00:0",
|
||||
"Thu, 01 Jan 1970 00:00:00",
|
||||
"Thu, 01 Jan 1970 00:00:00 ",
|
||||
"Thu, 01 Jan 1970 00:00:00 G",
|
||||
"Thu, 01 Jan 1970 00:00:00 GM",
|
||||
"Fri, 01 Jan 1970 00:00:00 GMT", // wrong day
|
||||
"01 Jan 1899 00:00:00 GMT", // year too small
|
||||
"01 Xxx 1971 00:00:00 GMT", // month bad
|
||||
"00 Jan 1971 00:00:00 GMT", // day too small
|
||||
"32 Jan 1971 00:00:00 GMT", // day too big
|
||||
"30 Feb 1971 00:00:00 GMT", // day too big for feb
|
||||
"30 Feb 1971 00:00:00 GMT", // day too big for feb (non-leap year)
|
||||
"32 Mar 1971 00:00:00 GMT", // other months
|
||||
"31 Apr 1971 00:00:00 GMT",
|
||||
"32 May 1971 00:00:00 GMT",
|
||||
"31 Jun 1971 00:00:00 GMT",
|
||||
"32 Jul 1971 00:00:00 GMT",
|
||||
"32 Aug 1971 00:00:00 GMT",
|
||||
"31 Sep 1971 00:00:00 GMT",
|
||||
"32 Oct 1971 00:00:00 GMT",
|
||||
"31 Nov 1971 00:00:00 GMT",
|
||||
"32 Dec 1971 00:00:00 GMT",
|
||||
"01 Jan 1971 70:00:00 GMT", // hour too big
|
||||
"01 Jan 1971 24:00:00 GMT",
|
||||
"01 Jan 1971 00:60:00 GMT", // minute too big
|
||||
"01 Jan 1971 00:00:70 GMT", // second too big
|
||||
"01 Jan 1971 00:00:61 GMT",
|
||||
"01 Jan 1899 00:00:00 GMT", // underflow
|
||||
"01 Jan 1969 00:00:00 CEST", // bad tz
|
||||
"14 Jan 2019 23:16:21 G0100", // bad tzoffsets
|
||||
"01 Jan 1970 00:00:00 +2400",
|
||||
"01 Jan 1970 00:00:00 -3000",
|
||||
"01 Jan 1970 00:00:00 +2160",
|
||||
"01 Jan 1970 00:00:00 -2400",
|
||||
"01 Jan 1970 00:00:00 -2160",
|
||||
"00 Jan 1971 00:00:00 GMT", // zero month day
|
||||
};
|
||||
|
||||
for (auto const& str : badStrings)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_EQ(0, dt.ToInterval());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601BoundaryCases)
|
||||
{
|
||||
// boundary cases:
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:00Z"); // epoch
|
||||
TestDateTimeRoundtrip("2038-01-19T03:14:06+00:00", "2038-01-19T03:14:06Z"); // INT_MAX - 1
|
||||
TestDateTimeRoundtrip(
|
||||
"2038-01-19T03:13:07-00:01",
|
||||
"2038-01-19T03:14:07Z"); // INT_MAX after subtacting 1
|
||||
TestDateTimeRoundtrip("2038-01-19T03:14:07-00:00", "2038-01-19T03:14:07Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601UsesEachTimezoneDigit)
|
||||
{
|
||||
TestDateTimeRoundtrip("2019-01-14T23:16:21+00:00", "2019-01-14T23:16:21Z");
|
||||
TestDateTimeRoundtrip("2019-01-14T23:16:21-00:01", "2019-01-14T23:17:21Z");
|
||||
TestDateTimeRoundtrip("2019-01-14T23:16:21+00:01", "2019-01-14T23:15:21Z");
|
||||
TestDateTimeRoundtrip("2019-01-14T23:16:21-01:00", "2019-01-15T00:16:21Z");
|
||||
TestDateTimeRoundtrip("2019-01-14T23:16:21+01:00", "2019-01-14T22:16:21Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601UsesEachDigit)
|
||||
{
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:01Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T00:01:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T01:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-02T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-02-01T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1971-01-01T00:00:00Z");
|
||||
|
||||
TestDateTimeRoundtrip("1999-01-01T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-12-01T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-09-01T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-30T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-31T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T23:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T19:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T00:59:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:59Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:60Z", "1970-01-01T00:01:00Z"); // leap seconds
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601AcceptsMonthMaxDays)
|
||||
{
|
||||
TestDateTimeRoundtrip("1970-01-31T00:00:00Z"); // jan
|
||||
TestDateTimeRoundtrip("2019-02-28T00:00:00Z"); // non leap year allows feb 28
|
||||
TestDateTimeRoundtrip("2020-02-29T00:00:00Z"); // leap year allows feb 29
|
||||
TestDateTimeRoundtrip("1970-03-31T00:00:00Z"); // mar
|
||||
TestDateTimeRoundtrip("1970-04-30T00:00:00Z"); // apr
|
||||
TestDateTimeRoundtrip("1970-05-31T00:00:00Z"); // may
|
||||
TestDateTimeRoundtrip("1970-06-30T00:00:00Z"); // jun
|
||||
TestDateTimeRoundtrip("1970-07-31T00:00:00Z"); // jul
|
||||
TestDateTimeRoundtrip("1970-08-31T00:00:00Z"); // aug
|
||||
TestDateTimeRoundtrip("1970-09-30T00:00:00Z"); // sep
|
||||
TestDateTimeRoundtrip("1970-10-31T00:00:00Z"); // oct
|
||||
TestDateTimeRoundtrip("1970-11-30T00:00:00Z"); // nov
|
||||
TestDateTimeRoundtrip("1970-12-31T00:00:00Z"); // dec
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601AcceptsLowercaseTZ)
|
||||
{
|
||||
TestDateTimeRoundtrip("1970-01-01t00:00:00Z", "1970-01-01T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:00z", "1970-01-01T00:00:00Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeRoundtripAcceptsInvalidNoTrailingTimezone)
|
||||
{
|
||||
// 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
|
||||
std::string const badStrings[] = {"2013-11-19T14:30:59.Z", "2013-11-19T14:30:59.a12Z"};
|
||||
std::string const strCorrected = "2013-11-19T14:30:59Z";
|
||||
|
||||
for (auto const& str : badStrings)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToString(DateTime::DateFormat::Iso8601);
|
||||
EXPECT_EQ(str2, strCorrected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeInvalid2)
|
||||
{
|
||||
// Various unsupported cases. In all cases, we have produce an empty date time
|
||||
std::string const badStrings[] = {
|
||||
"", // empty
|
||||
".Z", // too short
|
||||
".Zx", // no trailing Z
|
||||
"3.14Z" // not a valid date
|
||||
"a971-01-01T00:00:00Z", // any non digits or valid separators
|
||||
"1a71-01-01T00:00:00Z",
|
||||
"19a1-01-01T00:00:00Z",
|
||||
"197a-01-01T00:00:00Z",
|
||||
"1971a01-01T00:00:00Z",
|
||||
"1971-a1-01T00:00:00Z",
|
||||
"1971-0a-01T00:00:00Z",
|
||||
"1971-01a01T00:00:00Z",
|
||||
"1971-01-a1T00:00:00Z",
|
||||
"1971-01-0aT00:00:00Z",
|
||||
// "1971-01-01a00:00:00Z", parsed as complete date
|
||||
"1971-01-01Ta0:00:00Z",
|
||||
"1971-01-01T0a:00:00Z",
|
||||
"1971-01-01T00a00:00Z",
|
||||
"1971-01-01T00:a0:00Z",
|
||||
"1971-01-01T00:0a:00Z",
|
||||
"1971-01-01T00:00a00Z",
|
||||
"1971-01-01T00:00:a0Z",
|
||||
"1971-01-01T00:00:0aZ",
|
||||
// "1971-01-01T00:00:00a", accepted as per invalid_no_trailing_timezone above
|
||||
"1", // truncation
|
||||
"19",
|
||||
"197",
|
||||
"1970",
|
||||
"1970-",
|
||||
"1970-0",
|
||||
"1970-01",
|
||||
"1970-01-",
|
||||
"1970-01-0",
|
||||
// "1970-01-01", complete date
|
||||
"1970-01-01T",
|
||||
"1970-01-01T0",
|
||||
"1970-01-01T00",
|
||||
"1970-01-01T00:",
|
||||
"1970-01-01T00:0",
|
||||
"1970-01-01T00:00",
|
||||
"1970-01-01T00:00:",
|
||||
"1970-01-01T00:00:0",
|
||||
// "1970-01-01T00:00:00", // accepted as invalid timezone above
|
||||
"1899-01-01T00:00:00Z", // year too small
|
||||
"1971-00-01T00:00:00Z", // month too small
|
||||
"1971-20-01T00:00:00Z", // month too big
|
||||
"1971-13-01T00:00:00Z",
|
||||
"1971-01-00T00:00:00Z", // day too small
|
||||
"1971-01-32T00:00:00Z", // day too big
|
||||
"1971-02-30T00:00:00Z", // day too big for feb
|
||||
"1971-02-30T00:00:00Z", // day too big for feb (non-leap year)
|
||||
"1971-03-32T00:00:00Z", // other months
|
||||
"1971-04-31T00:00:00Z",
|
||||
"1971-05-32T00:00:00Z",
|
||||
"1971-06-31T00:00:00Z",
|
||||
"1971-07-32T00:00:00Z",
|
||||
"1971-08-32T00:00:00Z",
|
||||
"1971-09-31T00:00:00Z",
|
||||
"1971-10-32T00:00:00Z",
|
||||
"1971-11-31T00:00:00Z",
|
||||
"1971-12-32T00:00:00Z",
|
||||
"1971-01-01T70:00:00Z", // hour too big
|
||||
"1971-01-01T24:00:00Z",
|
||||
"1971-01-01T00:60:00Z", // minute too big
|
||||
"1971-01-01T00:00:70Z", // second too big
|
||||
"1971-01-01T00:00:61Z",
|
||||
"1899-01-01T00:00:00Z", // underflow
|
||||
"1900-01-01T00:00:00+00:01", // time zone underflow
|
||||
// "1970-01-01T00:00:00.Z", // accepted as invalid timezone above
|
||||
"1970-01-01T00:00:00+24:00", // bad tzoffsets
|
||||
"1970-01-01T00:00:00-30:00",
|
||||
"1970-01-01T00:00:00+21:60",
|
||||
"1970-01-01T00:00:00-24:00",
|
||||
"1970-01-01T00:00:00-21:60",
|
||||
"1971-01-00", // zero month day
|
||||
};
|
||||
|
||||
for (auto const& str : badStrings)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Iso8601);
|
||||
EXPECT_EQ(dt.ToInterval(), 0);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user