DateTime: API review feedback (#938)
This commit is contained in:
parent
2ce6e28eb0
commit
56475b003a
@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
@ -16,29 +16,26 @@ 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 format applied to the fraction part from any @DateFormat
|
||||
*
|
||||
* @brief Units of measurement the difference between instances of @DateTime.
|
||||
*/
|
||||
// 1 == 100 ns (1 / 10,000,000 of a second, 7 fractional digits).
|
||||
typedef std::chrono::duration<int64_t, std::ratio<1, 10000000>> Duration;
|
||||
|
||||
/**
|
||||
* @brief Defines the format applied to the fraction part of any @DateTime.
|
||||
*/
|
||||
enum class TimeFractionFormat
|
||||
{
|
||||
/// Decimals are not included when there are no decimals in the source Datetime and any zeros
|
||||
/// from the right are also removed.
|
||||
/// Include only meaningful fractional time digits, up to and excluding trailing zeroes.
|
||||
DropTrailingZeros,
|
||||
|
||||
/// Decimals are included for any Datetime.
|
||||
/// Include all the fractional time digits up to maximum precision, even if the entire value
|
||||
/// is zero.
|
||||
AllDigits,
|
||||
|
||||
/// Decimals are removed for any Datetime.
|
||||
/// Drop all the fractional time digits.
|
||||
Truncate
|
||||
};
|
||||
|
||||
@ -50,48 +47,46 @@ namespace Azure { namespace Core {
|
||||
/// RFC 1123.
|
||||
Rfc1123,
|
||||
|
||||
/// ISO 8601.
|
||||
Iso8601,
|
||||
/// RFC 3339.
|
||||
Rfc3339,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the current UTC time.
|
||||
*/
|
||||
static DateTime UtcNow();
|
||||
|
||||
/// An invalid UTC timestamp value.
|
||||
static constexpr IntervalType UtcTimestampInvalid = static_cast<IntervalType>(-1);
|
||||
static DateTime Now();
|
||||
|
||||
/**
|
||||
* @brief Get seconds since Unix/POSIX time epoch at `01-01-1970 00:00:00`.
|
||||
* If time is before epoch, @UtcTimestampInvalid is returned.
|
||||
* @brief Construct an instance of @DateTime.
|
||||
*
|
||||
* @param year Year.
|
||||
* @param month Month.
|
||||
* @param day Day.
|
||||
* @param hour Hour.
|
||||
* @param minute Minute.
|
||||
* @param second Seconds.
|
||||
*
|
||||
* @throw std::invalid_argument If any parameter is invalid.
|
||||
*/
|
||||
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) {}
|
||||
explicit DateTime(
|
||||
int16_t year,
|
||||
int8_t month = 1,
|
||||
int8_t day = 1,
|
||||
int8_t hour = 0,
|
||||
int8_t minute = 0,
|
||||
int8_t second = 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.
|
||||
* @param dateTime A string with the date and time.
|
||||
* @param format A format to which \p dateTime string adheres to.
|
||||
*
|
||||
* @return @DateTime that was constructed from the \p timeString; Uninitialized
|
||||
* (!@IsInitialized()) @DateTime if parsing \p timeString was not successful.
|
||||
* @return @DateTime that was constructed from the \p dateTime string.
|
||||
*
|
||||
* @throw DateTimeException If \p format is not recognized.
|
||||
* @throw std::invalid_argument If \p format is not recognized, or if parsing error.
|
||||
*/
|
||||
static DateTime FromString(
|
||||
std::string const& timeString,
|
||||
DateFormat format = DateFormat::Rfc1123);
|
||||
static DateTime Parse(std::string const& dateTime, DateFormat format);
|
||||
|
||||
private:
|
||||
/**
|
||||
@ -99,11 +94,11 @@ namespace Azure { namespace Core {
|
||||
*
|
||||
* @param format The representation format to use.
|
||||
* @param fractionFormat The format for the fraction part of the Datetime. Only supported by
|
||||
* ISO8601
|
||||
* RFC 3339.
|
||||
*
|
||||
* @throw DateTimeException If year exceeds 9999, or if \p format is not recognized.
|
||||
* @throw std::length_error If year exceeds 9999, or if \p format is not recognized.
|
||||
*/
|
||||
std::string ToString(DateFormat format, TimeFractionFormat fractionFormat) const;
|
||||
std::string GetString(DateFormat format, TimeFractionFormat fractionFormat) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
@ -111,109 +106,134 @@ namespace Azure { namespace Core {
|
||||
*
|
||||
* @param format The representation format to use.
|
||||
*
|
||||
* @throw DateTimeException If year exceeds 9999, or if \p format is not recognized.
|
||||
* @throw std::length_error If year exceeds 9999, or if \p format is not recognized.
|
||||
*/
|
||||
std::string ToString(DateFormat format = DateFormat::Rfc1123) const
|
||||
std::string GetString(DateFormat format) const
|
||||
{
|
||||
return ToString(format, TimeFractionFormat::DropTrailingZeros);
|
||||
return GetString(format, TimeFractionFormat::DropTrailingZeros);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get a string representation of the @DateTime formated with ISO8601.
|
||||
* @brief Get a string representation of the @DateTime formatted with RFC 3339.
|
||||
*
|
||||
* @param fractionFormat The format that is applied to the fraction part from the ISO8601 date.
|
||||
* @param fractionFormat The format that is applied to the fraction part from the RFC 3339 date.
|
||||
*
|
||||
* @throw DateTimeException If year exceeds 9999, or if \p fractionFormat is not recognized.
|
||||
* @throw std::length_error If year exceeds 9999.
|
||||
* @throw std::invalid_argument If \p format is not recognized.
|
||||
*/
|
||||
std::string ToIso8601String(TimeFractionFormat fractionFormat) const
|
||||
std::string GetRfc3339String(TimeFractionFormat fractionFormat) const
|
||||
{
|
||||
return ToString(DateFormat::Iso8601, fractionFormat);
|
||||
return GetString(DateFormat::Rfc3339, fractionFormat);
|
||||
};
|
||||
|
||||
/// 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)
|
||||
/**
|
||||
* @brief Add \p duration to this @DateTime.
|
||||
* @param duration @Duration to add.
|
||||
* @return Reference to this @DateTime.
|
||||
*/
|
||||
DateTime& operator+=(Duration const& duration)
|
||||
{
|
||||
return milliseconds * TicksPerMillisecond;
|
||||
m_since1601 += duration;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Create an interval from seconds.
|
||||
static IntervalType FromSeconds(unsigned int seconds) { return seconds * TicksPerSecond; }
|
||||
/**
|
||||
* @brief Subtract \p duration from this @DateTime.
|
||||
* @param duration @Duration to subtract from this @DateTime.
|
||||
* @return Reference to this @DateTime.
|
||||
*/
|
||||
DateTime& operator-=(Duration const& duration)
|
||||
{
|
||||
m_since1601 -= duration;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Create an interval from minutes.
|
||||
static IntervalType FromMinutes(unsigned int minutes) { return minutes * TicksPerMinute; }
|
||||
/**
|
||||
* @brief Subtract @Duration from @DateTime.
|
||||
* @param duration @Duration to subtract from this @DateTime.
|
||||
* @return New DateTime representing subtraction result.
|
||||
*/
|
||||
DateTime operator-(Duration const& duration) const { return DateTime(m_since1601 - duration); }
|
||||
|
||||
/// Create an interval from hours.
|
||||
static IntervalType FromHours(unsigned int hours) { return hours * TicksPerHour; }
|
||||
/**
|
||||
* @brief Add @Duration to @DateTime.
|
||||
* @param duration @Duration to add to this @DateTime.
|
||||
* @return New DateTime representing addition result.
|
||||
*/
|
||||
DateTime operator+(Duration const& duration) const { return DateTime(m_since1601 + duration); }
|
||||
|
||||
/// Create an interval from days.
|
||||
static IntervalType FromDays(unsigned int days) { return days * TicksPerDay; }
|
||||
/**
|
||||
* @brief Get @Duration between two instances of @DateTime.
|
||||
* @param other @DateTime to subtract from this @DateTime.
|
||||
* @return @Duration between this @DateTime and the \p other.
|
||||
*/
|
||||
Duration operator-(DateTime const& other) const { return m_since1601 - other.m_since1601; }
|
||||
|
||||
/// Checks whether this instance of @DateTime is initialized.
|
||||
bool IsInitialized() const { return m_interval != 0; }
|
||||
/**
|
||||
* @brief Compare with \p other @DateTime for equality.
|
||||
* @param other Other @DateTime to compare with.
|
||||
* @return `true` if @DateTime instances are equal, `false` otherwise.
|
||||
*/
|
||||
constexpr bool operator==(DateTime const& other) const
|
||||
{
|
||||
return m_since1601 == other.m_since1601;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare with \p other @DateTime for inequality.
|
||||
* @param other Other @DateTime to compare with.
|
||||
* @return `true` if @DateTime instances are not equal, `false` otherwise.
|
||||
*/
|
||||
constexpr bool operator!=(const DateTime& other) const { return !(*this == other); }
|
||||
|
||||
/**
|
||||
* @brief Check if \p other @DateTime precedes this @DateTime chronologically.
|
||||
* @param other @DateTime to compare with.
|
||||
* @return `true` if the \p other @DateTime precedes this, `false` otherwise.
|
||||
*/
|
||||
constexpr bool operator>(const DateTime& other) const { return !(*this <= other); }
|
||||
|
||||
/**
|
||||
* @brief Check if \p other @DateTime is chronologically after this @DateTime.
|
||||
* @param other @DateTime to compare with.
|
||||
* @return `true` if the \p other @DateTime is chonologically after this @DateTime, `false`
|
||||
* otherwise.
|
||||
*/
|
||||
constexpr bool operator<(const DateTime& other) const
|
||||
{
|
||||
return this->m_since1601 < other.m_since1601;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if \p other @DateTime precedes this @DateTime chronologically, or is equal to
|
||||
* it.
|
||||
* @param other @DateTime to compare with.
|
||||
* @return `true` if the \p other @DateTime precedes or is equal to this @DateTime, `false`
|
||||
* otherwise.
|
||||
*/
|
||||
constexpr bool operator>=(const DateTime& other) const { return !(*this < other); }
|
||||
|
||||
/**
|
||||
* @brief Check if \p other @DateTime is chronologically after or equal to this @DateTime.
|
||||
* @param other @DateTime to compare with.
|
||||
* @return `true` if the \p other @DateTime is chonologically after or equal to this @DateTime,
|
||||
* `false` otherwise.
|
||||
*/
|
||||
constexpr bool operator<=(const DateTime& other) const
|
||||
{
|
||||
return (*this == other) || (*this < other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get this @DateTime representation as a @Duration from the start of the
|
||||
* implementation-defined epoch.
|
||||
* @return @Duration since the start of the implementation-defined epoch.
|
||||
*/
|
||||
constexpr explicit operator Duration() const { return m_since1601; }
|
||||
|
||||
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) {}
|
||||
explicit DateTime(Duration const& since1601) : m_since1601(since1601) {}
|
||||
Duration m_since1601;
|
||||
};
|
||||
}} // namespace Azure::Core
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@ -17,7 +18,7 @@
|
||||
|
||||
using namespace Azure::Core;
|
||||
|
||||
DateTime DateTime::UtcNow()
|
||||
DateTime DateTime::Now()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FILETIME fileTime = {};
|
||||
@ -27,19 +28,21 @@ DateTime DateTime::UtcNow()
|
||||
largeInt.LowPart = fileTime.dwLowDateTime;
|
||||
largeInt.HighPart = fileTime.dwHighDateTime;
|
||||
|
||||
return DateTime(largeInt.QuadPart);
|
||||
return DateTime(Duration(largeInt.QuadPart));
|
||||
#else
|
||||
static constexpr int64_t WindowsToPosixOffsetSeconds = 11644473600LL;
|
||||
|
||||
struct timeval time = {};
|
||||
if (gettimeofday(&time, nullptr) != 0)
|
||||
{
|
||||
return DateTime();
|
||||
return DateTime(Duration());
|
||||
}
|
||||
|
||||
IntervalType result = WindowsToPosixOffsetSeconds + time.tv_sec;
|
||||
result *= TicksPerSecond; // convert to 10e-7
|
||||
int64_t result = WindowsToPosixOffsetSeconds + time.tv_sec;
|
||||
result *= DateTime::Duration::period::den; // convert to 10e-7
|
||||
result += time.tv_usec * 10; // convert and add microseconds, 10e-6 to 10e-7
|
||||
|
||||
return DateTime(static_cast<IntervalType>(result));
|
||||
return DateTime(Duration(result));
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -50,6 +53,9 @@ struct ComputeYearResult
|
||||
int SecondsLeftThisYear;
|
||||
};
|
||||
|
||||
constexpr auto MinYear = 1601;
|
||||
constexpr auto MaxYear = 9999;
|
||||
|
||||
constexpr int SecondsInMinute = 60;
|
||||
constexpr int SecondsInHour = SecondsInMinute * 60;
|
||||
constexpr int SecondsInDay = 24 * SecondsInHour;
|
||||
@ -127,19 +133,20 @@ constexpr char const monthNames[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string DateTime::ToString(DateFormat format, TimeFractionFormat fractionFormat) const
|
||||
std::string DateTime::GetString(DateFormat format, TimeFractionFormat fractionFormat) const
|
||||
{
|
||||
if (m_interval > 2650467743999999999LL)
|
||||
static constexpr auto const EndOfYear9999 = 2650467743999999999LL;
|
||||
if (m_since1601.count() > EndOfYear9999)
|
||||
{
|
||||
throw DateTimeException("The requested year exceeds the year 9999.");
|
||||
throw std::invalid_argument("The requested year exceeds the year 9999.");
|
||||
}
|
||||
|
||||
int64_t const epochAdjusted = static_cast<int64_t>(m_interval);
|
||||
int64_t const secondsSince1601 = epochAdjusted / TicksPerSecond; // convert to seconds
|
||||
int const fracSec = static_cast<int>(epochAdjusted % TicksPerSecond);
|
||||
int64_t const epochAdjusted = m_since1601.count();
|
||||
int64_t const secondsSince1601 = epochAdjusted / Duration::period::den; // convert to seconds
|
||||
int const fracSec = static_cast<int>(epochAdjusted % Duration::period::den);
|
||||
|
||||
auto const yearData = ComputeYear(secondsSince1601);
|
||||
int const year = yearData.Year + 1601;
|
||||
int const year = yearData.Year + MinYear;
|
||||
int const yearDay = yearData.SecondsLeftThisYear / SecondsInDay;
|
||||
|
||||
int leftover = yearData.SecondsLeftThisYear % SecondsInDay;
|
||||
@ -190,7 +197,7 @@ std::string DateTime::ToString(DateFormat format, TimeFractionFormat fractionFor
|
||||
outCursor += 4;
|
||||
return std::string(outBuffer, outCursor);
|
||||
|
||||
case DateFormat::Iso8601:
|
||||
case DateFormat::Rfc3339:
|
||||
#ifdef _MSC_VER
|
||||
sprintf_s(
|
||||
#else
|
||||
@ -242,7 +249,7 @@ std::string DateTime::ToString(DateFormat format, TimeFractionFormat fractionFor
|
||||
return std::string(outBuffer, outCursor);
|
||||
|
||||
default:
|
||||
throw DateTimeException("Unrecognized date format.");
|
||||
throw std::invalid_argument("Unrecognized date format.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,10 +403,8 @@ zone = "UT" / "GMT" ; Universal Time
|
||||
; hours+min. (HHMM)
|
||||
*/
|
||||
|
||||
DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
DateTime DateTime::Parse(std::string const& dateString, DateFormat format)
|
||||
{
|
||||
DateTime result = {};
|
||||
|
||||
int64_t secondsSince1601 = 0;
|
||||
uint64_t fracSec = 0;
|
||||
|
||||
@ -430,12 +435,12 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Day of the month.");
|
||||
}
|
||||
|
||||
if (monthDay == 0)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Invalid month day number (0).");
|
||||
}
|
||||
|
||||
int month = 0;
|
||||
@ -449,13 +454,13 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
++month;
|
||||
if (month == 12)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Month number.");
|
||||
}
|
||||
}
|
||||
|
||||
if (str[3] != ' ')
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime.");
|
||||
}
|
||||
|
||||
str += 4; // parsed month
|
||||
@ -463,19 +468,19 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
if (!IsDigit(str[0]) || !IsDigit(str[1]) || !IsDigit(str[2]) || !IsDigit(str[3])
|
||||
|| str[4] != ' ')
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Year.");
|
||||
}
|
||||
|
||||
int year = (str[0] - '0') * 1000 + (str[1] - '0') * 100 + (str[2] - '0') * 10 + (str[3] - '0');
|
||||
if (year < 1601)
|
||||
if (year < MinYear)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Year is less than 1601.");
|
||||
}
|
||||
|
||||
// days in month validity check
|
||||
if (!ValidateDay(monthDay, month, year))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Invalid day in month.");
|
||||
}
|
||||
|
||||
str += 5; // parsed year
|
||||
@ -484,13 +489,13 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
if (!IsDigit<2>(str[0]) || !IsDigit(str[1]) || str[2] != ':' || !IsDigit<5>(str[3])
|
||||
|| !IsDigit(str[4]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Hour and minutes.");
|
||||
}
|
||||
|
||||
int const hour = StringToDoubleDigitInt(str);
|
||||
if (hour > 23)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: hour > 23.");
|
||||
}
|
||||
str += 3; // parsed hour
|
||||
|
||||
@ -502,7 +507,7 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
{
|
||||
if (!IsDigit<6>(str[1]) || !IsDigit(str[2]) || str[3] != ' ')
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime.");
|
||||
}
|
||||
|
||||
sec = StringToDoubleDigitInt(str + 1);
|
||||
@ -514,15 +519,15 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime.");
|
||||
}
|
||||
|
||||
if (sec > 60)
|
||||
{ // 60 to allow leap seconds
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Seconds > 60.");
|
||||
}
|
||||
|
||||
year -= 1601;
|
||||
year -= MinYear;
|
||||
int const daysSince1601 = year * DaysInYear + CountLeapYears(year) + yearDay;
|
||||
|
||||
if (parsedWeekday != 7)
|
||||
@ -530,13 +535,13 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
int const actualWeekday = (daysSince1601 + 1) % 7;
|
||||
if (parsedWeekday != actualWeekday)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Weekday.");
|
||||
}
|
||||
}
|
||||
|
||||
secondsSince1601 = static_cast<IntervalType>(daysSince1601) * SecondsInDay
|
||||
+ static_cast<IntervalType>(hour) * SecondsInHour
|
||||
+ static_cast<IntervalType>(minute) * SecondsInMinute + sec;
|
||||
secondsSince1601 = static_cast<int64_t>(daysSince1601) * SecondsInDay
|
||||
+ static_cast<int64_t>(hour) * SecondsInHour
|
||||
+ static_cast<int64_t>(minute) * SecondsInMinute + sec;
|
||||
|
||||
fracSec = 0;
|
||||
if (!StringStartsWith(str, "GMT") && !StringStartsWith(str, "UT"))
|
||||
@ -575,7 +580,7 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Time zone.");
|
||||
}
|
||||
|
||||
secondsSince1601
|
||||
@ -583,22 +588,23 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
|
||||
if (secondsSince1601 < 0)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument(
|
||||
"Error parsing DateTime: year is < 1601 after time zone adjustments.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (format == DateFormat::Iso8601)
|
||||
else if (format == DateFormat::Rfc3339)
|
||||
{
|
||||
// parse year
|
||||
if (!IsDigit(str[0]) || !IsDigit(str[1]) || !IsDigit(str[2]) || !IsDigit(str[3]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Year.");
|
||||
}
|
||||
|
||||
int year = (str[0] - '0') * 1000 + (str[1] - '0') * 100 + (str[2] - '0') * 10 + (str[3] - '0');
|
||||
if (year < 1601)
|
||||
if (year < MinYear)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Year < 1601.");
|
||||
}
|
||||
|
||||
str += 4;
|
||||
@ -610,13 +616,13 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
// parse month
|
||||
if (!IsDigit<1>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Month number.");
|
||||
}
|
||||
|
||||
int month = StringToDoubleDigitInt(str);
|
||||
if (month < 1 || month > 12)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Invalid month number.");
|
||||
}
|
||||
|
||||
month -= 1;
|
||||
@ -630,27 +636,26 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
// parse day
|
||||
if (!IsDigit<3>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Day.");
|
||||
}
|
||||
|
||||
int monthDay = StringToDoubleDigitInt(str);
|
||||
if (!ValidateDay(monthDay, month, year))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Day of the month.");
|
||||
}
|
||||
|
||||
int const yearDay = GetYearDay(month, monthDay, year);
|
||||
|
||||
str += 2;
|
||||
year -= 1601;
|
||||
year -= MinYear;
|
||||
int daysSince1601 = year * DaysInYear + CountLeapYears(year) + yearDay;
|
||||
|
||||
if (str[0] != 'T' && str[0] != 't')
|
||||
{
|
||||
// No time
|
||||
secondsSince1601 = static_cast<int64_t>(daysSince1601) * SecondsInDay;
|
||||
result.m_interval = static_cast<IntervalType>(secondsSince1601 * TicksPerSecond + fracSec);
|
||||
return result;
|
||||
return DateTime(std::chrono::seconds(secondsSince1601) + DateTime::Duration(fracSec));
|
||||
}
|
||||
|
||||
++str; // skip 'T'
|
||||
@ -658,14 +663,14 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
// parse hour
|
||||
if (!IsDigit<2>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Hour.");
|
||||
}
|
||||
|
||||
int const hour = StringToDoubleDigitInt(str);
|
||||
str += 2;
|
||||
if (hour > 23)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Invalid hour number.");
|
||||
}
|
||||
|
||||
if (*str == ':')
|
||||
@ -676,7 +681,7 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
// parse minute
|
||||
if (!IsDigit<5>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Minute.");
|
||||
}
|
||||
|
||||
int const minute = StringToDoubleDigitInt(str);
|
||||
@ -693,14 +698,14 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
// parse seconds
|
||||
if (!IsDigit<6>(str[0]) || !IsDigit(str[1]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Seconds.");
|
||||
}
|
||||
|
||||
int const sec = StringToDoubleDigitInt(str);
|
||||
// We allow 60 to account for leap seconds
|
||||
if (sec > 60)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime: Invalid seconds number.");
|
||||
}
|
||||
|
||||
str += 2;
|
||||
@ -754,7 +759,7 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
if (!IsDigit<2>(str[1]) || !IsDigit(str[2]) || str[3] != ':' || !IsDigit<5>(str[4])
|
||||
|| !IsDigit(str[5]))
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime.");
|
||||
}
|
||||
|
||||
secondsSince1601 = AdjustTimezone(
|
||||
@ -764,7 +769,7 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
StringToDoubleDigitInt(str + 4));
|
||||
if (secondsSince1601 < 0)
|
||||
{
|
||||
return result;
|
||||
throw std::invalid_argument("Error parsing DateTime.");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -774,9 +779,84 @@ DateTime DateTime::FromString(std::string const& dateString, DateFormat format)
|
||||
}
|
||||
else
|
||||
{
|
||||
throw DateTimeException("Unrecognized date format.");
|
||||
throw std::invalid_argument("Unrecognized date format.");
|
||||
}
|
||||
|
||||
result.m_interval = static_cast<IntervalType>(secondsSince1601 * TicksPerSecond + fracSec);
|
||||
return result;
|
||||
return DateTime(std::chrono::seconds(secondsSince1601) + DateTime::Duration(fracSec));
|
||||
}
|
||||
|
||||
DateTime::DateTime(
|
||||
int16_t year,
|
||||
int8_t month,
|
||||
int8_t day,
|
||||
int8_t hour,
|
||||
int8_t minute,
|
||||
int8_t second)
|
||||
{
|
||||
// We should combine creation/validation logic, so it is reusable from both parsing functions and
|
||||
// from this constructor
|
||||
if (year > MaxYear || year < MinYear)
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
year > MaxYear ? "The requested year exceeds the year 9999."
|
||||
: "The requested year is less than the year 1601.");
|
||||
}
|
||||
|
||||
if (month <= 0 || month > 12)
|
||||
{
|
||||
throw std::invalid_argument("Invalid month value.");
|
||||
}
|
||||
|
||||
if (day <= 0 || day > 31)
|
||||
{
|
||||
throw std::invalid_argument("Invalid day value.");
|
||||
}
|
||||
|
||||
if (hour < 0 || hour > 23)
|
||||
{
|
||||
throw std::invalid_argument("Invalid hour value.");
|
||||
}
|
||||
|
||||
if (minute < 0 || minute > 59)
|
||||
{
|
||||
throw std::invalid_argument("Invalid minute value.");
|
||||
}
|
||||
|
||||
if (second < 0 || second > 60)
|
||||
{
|
||||
throw std::invalid_argument("Invalid seconds value.");
|
||||
}
|
||||
|
||||
if (!ValidateDay(day, month - 1, year))
|
||||
{
|
||||
throw std::invalid_argument("Invalid day of the month.");
|
||||
}
|
||||
|
||||
char outBuffer[38]{}; // Thu, 01 Jan 1970 00:00:00 GMT\0
|
||||
// 1970-01-01T00:00:00.1234567Z\0
|
||||
|
||||
char* outCursor = outBuffer;
|
||||
#ifdef _MSC_VER
|
||||
sprintf_s(
|
||||
#else
|
||||
std::sprintf(
|
||||
#endif
|
||||
outCursor,
|
||||
#ifdef _MSC_VER
|
||||
20,
|
||||
#endif
|
||||
"%04d-%02d-%02dT%02d:%02d:%02d",
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second);
|
||||
|
||||
outCursor += 19;
|
||||
|
||||
*outCursor = 'Z';
|
||||
++outCursor;
|
||||
auto const dt = DateTime::Parse(std::string(outBuffer, outCursor), DateFormat::Rfc3339);
|
||||
m_since1601 = dt.m_since1601;
|
||||
}
|
||||
|
||||
@ -9,49 +9,51 @@
|
||||
|
||||
using namespace Azure::Core;
|
||||
|
||||
namespace {
|
||||
static const auto Year1601 = DateTime(1601);
|
||||
} // namespace
|
||||
|
||||
TEST(DateTime, ParseDateAndTimeBasic)
|
||||
{
|
||||
auto dt1 = DateTime::FromString("20130517T00:00:00Z", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt1.ToInterval());
|
||||
auto dt1 = DateTime::Parse("20130517T00:00:00Z", DateTime::DateFormat::Rfc3339);
|
||||
auto dt2 = DateTime::Parse("Fri, 17 May 2013 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_NE(0, static_cast<DateTime::Duration>(dt2).count());
|
||||
|
||||
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());
|
||||
EXPECT_EQ(dt1, dt2);
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseDateAndTimeExtended)
|
||||
{
|
||||
auto dt1 = DateTime::FromString("2013-05-17T00:00:00Z", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt1.ToInterval());
|
||||
auto dt1 = DateTime::Parse("2013-05-17T00:00:00Z", DateTime::DateFormat::Rfc3339);
|
||||
EXPECT_NE(0, static_cast<DateTime::Duration>(dt1).count());
|
||||
|
||||
auto dt2 = DateTime::FromString("Fri, 17 May 2013 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_NE(0u, dt2.ToInterval());
|
||||
auto dt2 = DateTime::Parse("Fri, 17 May 2013 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_NE(0, static_cast<DateTime::Duration>(dt2).count());
|
||||
|
||||
EXPECT_EQ(dt1.ToInterval(), dt2.ToInterval());
|
||||
EXPECT_EQ(dt1, dt2);
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseDateBasic)
|
||||
{
|
||||
{
|
||||
auto dt = DateTime::FromString("20130517", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt.ToInterval());
|
||||
auto dt = DateTime::Parse("20130517", DateTime::DateFormat::Rfc3339);
|
||||
EXPECT_NE(0, static_cast<DateTime::Duration>(dt).count());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseDateExtended)
|
||||
{
|
||||
{
|
||||
auto dt = DateTime::FromString("2013-05-17", DateTime::DateFormat::Iso8601);
|
||||
EXPECT_NE(0u, dt.ToInterval());
|
||||
auto dt = DateTime::Parse("2013-05-17", DateTime::DateFormat::Rfc3339);
|
||||
EXPECT_NE(0, static_cast<DateTime::Duration>(dt).count());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
auto dt = DateTime::Parse(str, DateTime::DateFormat::Rfc3339);
|
||||
auto const str2 = dt.GetString(DateTime::DateFormat::Rfc3339);
|
||||
EXPECT_EQ(str2, strExpected);
|
||||
}
|
||||
|
||||
@ -77,22 +79,22 @@ TEST(DateTime, decimals)
|
||||
{
|
||||
{
|
||||
std::string strExpected("2020-10-13T21:06:15.3300000Z");
|
||||
auto dt = DateTime::FromString("2020-10-13T21:06:15.33Z", DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToIso8601String(DateTime::TimeFractionFormat::AllDigits);
|
||||
auto dt = DateTime::Parse("2020-10-13T21:06:15.33Z", DateTime::DateFormat::Rfc3339);
|
||||
auto const str2 = dt.GetRfc3339String(DateTime::TimeFractionFormat::AllDigits);
|
||||
EXPECT_EQ(str2, strExpected);
|
||||
}
|
||||
|
||||
{
|
||||
std::string strExpected("2020-10-13T21:06:15.0000000Z");
|
||||
auto dt = DateTime::FromString("2020-10-13T21:06:15Z", DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToIso8601String(DateTime::TimeFractionFormat::AllDigits);
|
||||
auto dt = DateTime::Parse("2020-10-13T21:06:15Z", DateTime::DateFormat::Rfc3339);
|
||||
auto const str2 = dt.GetRfc3339String(DateTime::TimeFractionFormat::AllDigits);
|
||||
EXPECT_EQ(str2, strExpected);
|
||||
}
|
||||
|
||||
{
|
||||
std::string strExpected("2020-10-13T21:06:15.1234500Z");
|
||||
auto dt = DateTime::FromString("2020-10-13T21:06:15.12345Z", DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToIso8601String(DateTime::TimeFractionFormat::AllDigits);
|
||||
auto dt = DateTime::Parse("2020-10-13T21:06:15.12345Z", DateTime::DateFormat::Rfc3339);
|
||||
auto const str2 = dt.GetRfc3339String(DateTime::TimeFractionFormat::AllDigits);
|
||||
EXPECT_EQ(str2, strExpected);
|
||||
}
|
||||
}
|
||||
@ -101,27 +103,26 @@ TEST(DateTime, noDecimals)
|
||||
{
|
||||
{
|
||||
std::string strExpected("2020-10-13T21:06:15Z");
|
||||
auto dt = DateTime::FromString("2020-10-13T21:06:15Z", DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToIso8601String(DateTime::TimeFractionFormat::Truncate);
|
||||
auto dt = DateTime::Parse("2020-10-13T21:06:15Z", DateTime::DateFormat::Rfc3339);
|
||||
auto const str2 = dt.GetRfc3339String(DateTime::TimeFractionFormat::Truncate);
|
||||
EXPECT_EQ(str2, strExpected);
|
||||
}
|
||||
|
||||
{
|
||||
std::string strExpected("2020-10-13T21:06:15Z");
|
||||
auto dt = DateTime::FromString("2020-10-13T21:06:15.99999Z", DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToIso8601String(DateTime::TimeFractionFormat::Truncate);
|
||||
auto dt = DateTime::Parse("2020-10-13T21:06:15.99999Z", DateTime::DateFormat::Rfc3339);
|
||||
auto const str2 = dt.GetRfc3339String(DateTime::TimeFractionFormat::Truncate);
|
||||
EXPECT_EQ(str2, strExpected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DateTime, sameResultFromDefaultISO)
|
||||
TEST(DateTime, sameResultFromDefaultRfc3339)
|
||||
{
|
||||
{
|
||||
auto dt = DateTime::FromString("2020-10-13T21:06:15.33000000Z", DateTime::DateFormat::Iso8601);
|
||||
auto dt2
|
||||
= DateTime::FromString("2020-10-13T21:06:15.330000000Z", DateTime::DateFormat::Iso8601);
|
||||
auto const str1 = dt.ToIso8601String(DateTime::TimeFractionFormat::DropTrailingZeros);
|
||||
auto const str2 = dt2.ToString(DateTime::DateFormat::Iso8601);
|
||||
auto dt = DateTime::Parse("2020-10-13T21:06:15.33000000Z", DateTime::DateFormat::Rfc3339);
|
||||
auto dt2 = DateTime::Parse("2020-10-13T21:06:15.330000000Z", DateTime::DateFormat::Rfc3339);
|
||||
auto const str1 = dt.GetRfc3339String(DateTime::TimeFractionFormat::DropTrailingZeros);
|
||||
auto const str2 = dt2.GetString(DateTime::DateFormat::Rfc3339);
|
||||
EXPECT_EQ(str1, str2);
|
||||
}
|
||||
}
|
||||
@ -156,17 +157,18 @@ TEST(DateTime, ParseTimeRoundripYear9999) { TestDateTimeRoundtrip("9999-12-31T23
|
||||
|
||||
TEST(DateTime, EmittingTimeCorrectDay)
|
||||
{
|
||||
auto const test = DateTime() + 132004507640000000LL; // 2019-04-22T23:52:44 is a Monday
|
||||
auto const actual = test.ToString(DateTime::DateFormat::Rfc1123);
|
||||
auto const test = Year1601 + std::chrono::seconds(13200450764); // 2019-04-22T23:52:44 is a Monday
|
||||
auto const actual = test.GetString(DateTime::DateFormat::Rfc1123);
|
||||
std::string const expected("Mon");
|
||||
EXPECT_EQ(actual.substr(0, 3), expected);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void TestRfc1123IsTimeT(char const* str, DateTime::IntervalType t)
|
||||
void TestRfc1123IsTimeT(char const* str, int64_t t)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Rfc1123);
|
||||
auto interval = dt.ToInterval();
|
||||
auto const dt = DateTime::Parse(str, DateTime::DateFormat::Rfc1123);
|
||||
int64_t interval = static_cast<DateTime::Duration>(dt).count();
|
||||
|
||||
EXPECT_EQ(0, interval % 10000000);
|
||||
interval /= 10000000;
|
||||
interval -= 11644473600; // NT epoch adjustment
|
||||
@ -188,7 +190,8 @@ TEST(DateTime, ParseTimeRfc1123AcceptsEachDay)
|
||||
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:14:06 GMT", static_cast<int64_t>(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);
|
||||
@ -338,12 +341,11 @@ TEST(DateTime, ParseTimeRfc1123InvalidCases)
|
||||
|
||||
for (auto const& str : badStrings)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_EQ(0, dt.ToInterval());
|
||||
EXPECT_THROW(DateTime::Parse(str, DateTime::DateFormat::Rfc1123), std::invalid_argument);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601BoundaryCases)
|
||||
TEST(DateTime, ParseTimeRfc3339BoundaryCases)
|
||||
{
|
||||
// boundary cases:
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:00Z"); // epoch
|
||||
@ -354,7 +356,7 @@ TEST(DateTime, ParseTimeIso8601BoundaryCases)
|
||||
TestDateTimeRoundtrip("2038-01-19T03:14:07-00:00", "2038-01-19T03:14:07Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601UsesEachTimezoneDigit)
|
||||
TEST(DateTime, ParseTimeRfc3339UsesEachTimezoneDigit)
|
||||
{
|
||||
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");
|
||||
@ -363,7 +365,7 @@ TEST(DateTime, ParseTimeIso8601UsesEachTimezoneDigit)
|
||||
TestDateTimeRoundtrip("2019-01-14T23:16:21+01:00", "2019-01-14T22:16:21Z");
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601UsesEachDigit)
|
||||
TEST(DateTime, ParseTimeRfc3339UsesEachDigit)
|
||||
{
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:01Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T00:01:00Z");
|
||||
@ -384,7 +386,7 @@ TEST(DateTime, ParseTimeIso8601UsesEachDigit)
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:60Z", "1970-01-01T00:01:00Z"); // leap seconds
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601AcceptsMonthMaxDays)
|
||||
TEST(DateTime, ParseTimeRfc3339AcceptsMonthMaxDays)
|
||||
{
|
||||
TestDateTimeRoundtrip("1970-01-31T00:00:00Z"); // jan
|
||||
TestDateTimeRoundtrip("2019-02-28T00:00:00Z"); // non leap year allows feb 28
|
||||
@ -401,7 +403,7 @@ TEST(DateTime, ParseTimeIso8601AcceptsMonthMaxDays)
|
||||
TestDateTimeRoundtrip("1970-12-31T00:00:00Z"); // dec
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseTimeIso8601AcceptsLowercaseTZ)
|
||||
TEST(DateTime, ParseTimeRfc3339AcceptsLowercaseTZ)
|
||||
{
|
||||
TestDateTimeRoundtrip("1970-01-01t00:00:00Z", "1970-01-01T00:00:00Z");
|
||||
TestDateTimeRoundtrip("1970-01-01T00:00:00z", "1970-01-01T00:00:00Z");
|
||||
@ -416,8 +418,8 @@ TEST(DateTime, ParseTimeRoundtripAcceptsInvalidNoTrailingTimezone)
|
||||
|
||||
for (auto const& str : badStrings)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Iso8601);
|
||||
auto const str2 = dt.ToString(DateTime::DateFormat::Iso8601);
|
||||
auto const dt = DateTime::Parse(str, DateTime::DateFormat::Rfc3339);
|
||||
auto const str2 = dt.GetString(DateTime::DateFormat::Rfc3339);
|
||||
EXPECT_EQ(str2, strCorrected);
|
||||
}
|
||||
}
|
||||
@ -504,21 +506,75 @@ TEST(DateTime, ParseTimeInvalid2)
|
||||
|
||||
for (auto const& str : badStrings)
|
||||
{
|
||||
auto const dt = DateTime::FromString(str, DateTime::DateFormat::Iso8601);
|
||||
EXPECT_EQ(dt.ToInterval(), 0);
|
||||
EXPECT_THROW(DateTime::Parse(str, DateTime::DateFormat::Rfc3339), std::invalid_argument);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DateTime, ParseDatesBefore1900)
|
||||
{
|
||||
TestDateTimeRoundtrip("1899-01-01T00:00:00Z");
|
||||
auto dt1 = DateTime::FromString("1899-01-01T00:00:00Z", DateTime::DateFormat::Iso8601);
|
||||
auto dt2 = DateTime::FromString("Sun, 1 Jan 1899 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_EQ(dt1.ToInterval(), dt2.ToInterval());
|
||||
auto dt1 = DateTime::Parse("1899-01-01T00:00:00Z", DateTime::DateFormat::Rfc3339);
|
||||
auto dt2 = DateTime::Parse("Sun, 1 Jan 1899 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_EQ(dt1, dt2);
|
||||
|
||||
TestDateTimeRoundtrip("1601-01-01T00:00:00Z");
|
||||
auto dt3 = DateTime::FromString("1601-01-01T00:00:00Z", DateTime::DateFormat::Iso8601);
|
||||
auto dt4 = DateTime::FromString("Mon, 1 Jan 1601 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_EQ(dt3.ToInterval(), dt4.ToInterval());
|
||||
EXPECT_EQ(0u, dt3.ToInterval());
|
||||
auto dt3 = DateTime::Parse("1601-01-01T00:00:00Z", DateTime::DateFormat::Rfc3339);
|
||||
auto dt4 = DateTime::Parse("Mon, 1 Jan 1601 00:00:00 GMT", DateTime::DateFormat::Rfc1123);
|
||||
EXPECT_EQ(dt3, dt4);
|
||||
EXPECT_EQ(0, static_cast<DateTime::Duration>(dt3).count());
|
||||
}
|
||||
|
||||
TEST(DateTime, ConstructorAndDuration)
|
||||
{
|
||||
auto dt1 = DateTime::Parse("2020-11-03T15:30:45.1234567Z", DateTime::DateFormat::Rfc3339);
|
||||
auto dt2 = DateTime(2020, 11, 03, 15, 30, 45);
|
||||
dt2 += std::chrono::duration_cast<DateTime::Duration>(std::chrono::nanoseconds(123456700));
|
||||
EXPECT_EQ(dt1, dt2);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
auto duration = 8h + 29min + 14s + 876543300ns;
|
||||
|
||||
auto dt3 = dt1 + std::chrono::duration_cast<DateTime::Duration>(duration);
|
||||
|
||||
auto dt4 = DateTime::Parse("2020-11-04T00:00:00Z", DateTime::DateFormat::Rfc3339);
|
||||
EXPECT_EQ(dt3, dt4);
|
||||
}
|
||||
|
||||
TEST(DateTime, ArithmeticOperators)
|
||||
{
|
||||
auto const dt1 = DateTime(2020, 11, 03, 15, 30, 45);
|
||||
auto const dt2 = DateTime(2020, 11, 04, 15, 30, 45);
|
||||
auto dt3 = dt1;
|
||||
EXPECT_EQ(dt3, dt1);
|
||||
EXPECT_EQ(dt1, dt3);
|
||||
EXPECT_NE(dt3, dt2);
|
||||
EXPECT_NE(dt2, dt3);
|
||||
EXPECT_LT(dt1, dt2);
|
||||
EXPECT_LE(dt1, dt2);
|
||||
EXPECT_LE(dt1, dt3);
|
||||
EXPECT_LE(dt3, dt1);
|
||||
EXPECT_LE(dt3, dt2);
|
||||
EXPECT_GT(dt2, dt1);
|
||||
EXPECT_GE(dt2, dt1);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
auto const diff = dt2 - dt1;
|
||||
EXPECT_EQ(24h, diff);
|
||||
EXPECT_LE(24h, diff);
|
||||
EXPECT_GE(24h, diff);
|
||||
|
||||
dt3 += 24h;
|
||||
EXPECT_EQ(dt3, dt2);
|
||||
EXPECT_NE(dt3, dt1);
|
||||
|
||||
dt3 -= 24h;
|
||||
EXPECT_EQ(dt3, dt1);
|
||||
EXPECT_NE(dt3, dt2);
|
||||
|
||||
dt3 = dt1 + 12h;
|
||||
EXPECT_GT(dt3, dt1);
|
||||
EXPECT_LT(dt3, dt2);
|
||||
|
||||
dt3 = dt2 - 24h;
|
||||
EXPECT_EQ(dt3, dt1);
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
TEST(Logging, simplifiedHeader)
|
||||
{
|
||||
EXPECT_NO_THROW(Azure::Core::Context c);
|
||||
EXPECT_NO_THROW(Azure::Core::DateTime::DateTime::FromSeconds(10));
|
||||
EXPECT_NO_THROW(Azure::Core::DateTime(2020, 11, 03, 15, 30, 44));
|
||||
EXPECT_NO_THROW(Azure::Core::Nullable<int> n);
|
||||
EXPECT_NO_THROW(Azure::Core::Http::RawResponse r(
|
||||
1, 1, Azure::Core::Http::HttpStatusCode::Accepted, "phrase"));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user