2015-09-06 20:19:09 +02:00
|
|
|
#include "./datetime.h"
|
2015-04-22 18:36:40 +02:00
|
|
|
|
2017-01-27 18:51:54 +01:00
|
|
|
#include "../conversion/stringbuilder.h"
|
2017-05-01 03:13:11 +02:00
|
|
|
#include "../conversion/stringconversion.h"
|
2015-04-22 18:36:40 +02:00
|
|
|
|
|
|
|
#include <iomanip>
|
2017-05-01 03:13:11 +02:00
|
|
|
#include <sstream>
|
2015-04-22 18:36:40 +02:00
|
|
|
#include <stdexcept>
|
|
|
|
|
|
|
|
using namespace std;
|
2019-06-10 21:56:46 +02:00
|
|
|
|
|
|
|
namespace CppUtilities {
|
2015-04-22 18:36:40 +02:00
|
|
|
|
|
|
|
const int DateTime::m_daysPerYear = 365;
|
|
|
|
const int DateTime::m_daysPer4Years = 1461;
|
|
|
|
const int DateTime::m_daysPer100Years = 36524;
|
|
|
|
const int DateTime::m_daysPer400Years = 146097;
|
|
|
|
const int DateTime::m_daysTo1601 = 584388;
|
|
|
|
const int DateTime::m_daysTo1899 = 693593;
|
|
|
|
const int DateTime::m_daysTo10000 = 3652059;
|
2017-05-01 03:13:11 +02:00
|
|
|
const int DateTime::m_daysToMonth365[13] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
|
|
|
|
const int DateTime::m_daysToMonth366[13] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
|
|
|
|
const int DateTime::m_daysInMonth365[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
|
|
const int DateTime::m_daysInMonth366[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
2015-04-22 18:36:40 +02:00
|
|
|
|
2017-11-29 19:08:22 +01:00
|
|
|
template <typename num1, typename num2, typename num3> constexpr bool inRangeInclMax(num1 val, num2 min, num3 max)
|
2015-04-22 18:36:40 +02:00
|
|
|
{
|
|
|
|
return (val) >= (min) && (val) <= (max);
|
|
|
|
}
|
|
|
|
|
2017-11-29 19:08:22 +01:00
|
|
|
template <typename num1, typename num2, typename num3> constexpr bool inRangeExclMax(num1 val, num2 min, num3 max)
|
2015-04-22 18:36:40 +02:00
|
|
|
{
|
2017-05-01 03:13:11 +02:00
|
|
|
return (val) >= (min) && (val) < (max);
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2019-06-10 21:56:46 +02:00
|
|
|
* \class DateTime
|
2015-04-22 18:36:40 +02:00
|
|
|
* \brief Represents an instant in time, typically expressed as a date and time of day.
|
2016-02-15 20:29:52 +01:00
|
|
|
* \remarks
|
|
|
|
* - Time values are measured in 100-nanosecond units called ticks,
|
|
|
|
* and a particular date is the number of ticks since 12:00 midnight, January 1,
|
2019-11-17 22:30:51 +01:00
|
|
|
* 0001 A.D. (C.E.) in the Gregorian Calendar (excluding ticks that would be added by leap seconds).
|
|
|
|
* - There is no time zone information associated. You need to keep track of the used time zone separately. That can
|
|
|
|
* be done by keeping an additional TimeSpan around which represents the delta to GMT or by simply using GMT everywhere
|
|
|
|
* in the program.
|
|
|
|
* - When constructing an instance via DateTime::fromTimeStamp(), DateTime::fromChronoTimePoint() or DateTime::fromIsoStringLocal()
|
|
|
|
* the time zone deltas are "baked into" the DateTime instance. For instance, the expression (DateTime::now() - DateTime::gmtNow())
|
|
|
|
* returns one hour in Germany during winter time (and *not* zero although both instances represent the current time).
|
2017-12-03 01:45:11 +01:00
|
|
|
* \todo
|
2021-07-03 19:07:49 +02:00
|
|
|
* - Allow to determine the date part for each component at once to prevent multiple
|
2017-12-03 01:45:11 +01:00
|
|
|
* invocations of getDatePart().
|
2015-04-22 18:36:40 +02:00
|
|
|
*/
|
|
|
|
|
2022-08-13 14:07:57 +02:00
|
|
|
/*!
|
|
|
|
* \struct DateTimeExpression
|
|
|
|
* \brief The DateTimeExpression struct holds information about a time expression (e.g. an ISO-8601 timestamp).
|
|
|
|
*
|
|
|
|
* This struct holds the date and time specification (DateTimeExpression::value), the time-zone delta
|
|
|
|
* (DateTimeExpression::delta) and what parts are actually specified (DateTimeExpression::parts).
|
|
|
|
*
|
|
|
|
* The idea is to be able to preserve a time-zone delta and present parts when deserializing and then
|
|
|
|
* serializing again. Note that it still does not allow to preserve the format of the original timestamp
|
|
|
|
* exactly.
|
|
|
|
*/
|
|
|
|
|
2015-06-21 21:41:56 +02:00
|
|
|
/*!
|
2017-04-19 21:48:23 +02:00
|
|
|
* \brief Constructs a new DateTime object with the local time from the specified UNIX \a timeStamp.
|
2015-06-21 21:41:56 +02:00
|
|
|
*/
|
|
|
|
DateTime DateTime::fromTimeStamp(time_t timeStamp)
|
|
|
|
{
|
2017-05-01 03:13:11 +02:00
|
|
|
if (timeStamp) {
|
2019-08-19 19:00:50 +02:00
|
|
|
struct tm *const timeinfo = localtime(&timeStamp);
|
2017-05-01 03:13:11 +02:00
|
|
|
return DateTime::fromDateAndTime(timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min,
|
|
|
|
timeinfo->tm_sec < 60 ? timeinfo->tm_sec : 59, 0);
|
2015-09-06 15:30:16 +02:00
|
|
|
} else {
|
|
|
|
return DateTime();
|
|
|
|
}
|
2015-06-21 21:41:56 +02:00
|
|
|
}
|
|
|
|
|
2015-04-22 18:36:40 +02:00
|
|
|
/*!
|
2016-08-30 19:59:04 +02:00
|
|
|
* \brief Parses the given C-style string as DateTime.
|
2017-12-03 01:45:11 +01:00
|
|
|
* \throws Throws a ConversionException if the specified \a str does not match the expected time format.
|
|
|
|
*
|
|
|
|
* The expected format is something like "2012-02-29 15:34:20.033" or "2012/02/29 15:34:20.033". The
|
|
|
|
* delimiters '-', ':' and '/' are exchangeable.
|
|
|
|
*
|
|
|
|
* \sa DateTime::fromIsoString()
|
2015-04-22 18:36:40 +02:00
|
|
|
*/
|
2016-08-30 19:59:04 +02:00
|
|
|
DateTime DateTime::fromString(const char *str)
|
2015-04-22 18:36:40 +02:00
|
|
|
{
|
2022-08-12 20:40:17 +02:00
|
|
|
return DateTimeExpression::fromString(str).value;
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-09-01 15:42:25 +02:00
|
|
|
* \brief Parses the specified ISO date time denotation provided as C-style string.
|
|
|
|
* \returns Returns a pair where the first value is the parsed date time and the second value
|
2021-05-24 21:27:18 +02:00
|
|
|
* the time zone designator (a time span which can be subtracted from the first value to get the UTC time).
|
2022-08-12 20:40:17 +02:00
|
|
|
* \throws Throws a ConversionException if the specified \a str does not match the expected time format.
|
2021-05-24 21:27:18 +02:00
|
|
|
* \remarks
|
|
|
|
* - Parsing durations and time intervals is *not* supported.
|
|
|
|
* - Truncated representations are *not* supported.
|
|
|
|
* - Standardised extensions (ISO 8601-2:2019) are *not* supported.
|
2018-01-29 16:24:39 +01:00
|
|
|
* \sa https://en.wikipedia.org/wiki/ISO_8601
|
2016-08-30 19:59:04 +02:00
|
|
|
*/
|
|
|
|
std::pair<DateTime, TimeSpan> DateTime::fromIsoString(const char *str)
|
|
|
|
{
|
2022-08-12 20:40:17 +02:00
|
|
|
const auto expr = DateTimeExpression::fromIsoString(str);
|
|
|
|
return std::make_pair(expr.value, expr.delta);
|
2016-08-30 19:59:04 +02:00
|
|
|
}
|
|
|
|
|
2015-04-22 18:36:40 +02:00
|
|
|
/*!
|
2016-08-30 19:59:04 +02:00
|
|
|
* \brief Returns the string representation of the current instance using the specified \a format.
|
|
|
|
* \remarks If \a noMilliseconds is true the date will be rounded to full seconds.
|
|
|
|
* \sa toIsoString() for ISO format
|
2015-04-22 18:36:40 +02:00
|
|
|
*/
|
|
|
|
void DateTime::toString(string &result, DateTimeOutputFormat format, bool noMilliseconds) const
|
|
|
|
{
|
2020-04-26 21:38:02 +02:00
|
|
|
if (format == DateTimeOutputFormat::Iso) {
|
|
|
|
result = toIsoString();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-22 18:36:40 +02:00
|
|
|
stringstream s(stringstream::in | stringstream::out);
|
|
|
|
s << setfill('0');
|
2020-04-26 21:38:02 +02:00
|
|
|
|
|
|
|
if (format == DateTimeOutputFormat::IsoOmittingDefaultComponents) {
|
|
|
|
constexpr auto dateDelimiter = '-', timeDelimiter = ':';
|
|
|
|
const int components[] = { year(), month(), day(), hour(), minute(), second(), millisecond(), microsecond(), nanosecond() };
|
|
|
|
const int *const firstTimeComponent = components + 3;
|
|
|
|
const int *const firstFractionalComponent = components + 6;
|
|
|
|
const int *const lastComponent = components + 8;
|
|
|
|
const int *componentsEnd = noMilliseconds ? firstFractionalComponent : lastComponent + 1;
|
|
|
|
for (const int *i = componentsEnd - 1; i > components; --i) {
|
|
|
|
if (i >= firstTimeComponent && *i == 0) {
|
|
|
|
componentsEnd = i;
|
|
|
|
} else if (i < firstTimeComponent && *i == 1) {
|
|
|
|
componentsEnd = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const int *i = components; i != componentsEnd; ++i) {
|
|
|
|
if (i == firstTimeComponent) {
|
|
|
|
s << 'T';
|
|
|
|
} else if (i == firstFractionalComponent) {
|
|
|
|
s << '.';
|
|
|
|
}
|
|
|
|
if (i == components) {
|
|
|
|
s << setw(4) << *i;
|
|
|
|
} else if (i < firstFractionalComponent) {
|
|
|
|
if (i < firstTimeComponent) {
|
|
|
|
s << dateDelimiter;
|
|
|
|
} else if (i > firstTimeComponent) {
|
|
|
|
s << timeDelimiter;
|
|
|
|
}
|
|
|
|
s << setw(2) << *i;
|
|
|
|
} else if (i < lastComponent) {
|
|
|
|
s << setw(3) << *i;
|
|
|
|
} else {
|
|
|
|
s << *i / TimeSpan::nanosecondsPerTick;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = s.str();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-01 03:13:11 +02:00
|
|
|
if (format == DateTimeOutputFormat::DateTimeAndWeekday || format == DateTimeOutputFormat::DateTimeAndShortWeekday)
|
2016-08-30 19:59:04 +02:00
|
|
|
s << printDayOfWeek(dayOfWeek(), format == DateTimeOutputFormat::DateTimeAndShortWeekday) << ' ';
|
2017-05-01 03:13:11 +02:00
|
|
|
if (format == DateTimeOutputFormat::DateOnly || format == DateTimeOutputFormat::DateAndTime || format == DateTimeOutputFormat::DateTimeAndWeekday
|
|
|
|
|| format == DateTimeOutputFormat::DateTimeAndShortWeekday)
|
2016-08-30 19:59:04 +02:00
|
|
|
s << setw(4) << year() << '-' << setw(2) << month() << '-' << setw(2) << day();
|
2017-05-01 03:13:11 +02:00
|
|
|
if (format == DateTimeOutputFormat::DateAndTime || format == DateTimeOutputFormat::DateTimeAndWeekday
|
|
|
|
|| format == DateTimeOutputFormat::DateTimeAndShortWeekday)
|
2015-04-22 18:36:40 +02:00
|
|
|
s << " ";
|
2017-05-01 03:13:11 +02:00
|
|
|
if (format == DateTimeOutputFormat::TimeOnly || format == DateTimeOutputFormat::DateAndTime || format == DateTimeOutputFormat::DateTimeAndWeekday
|
|
|
|
|| format == DateTimeOutputFormat::DateTimeAndShortWeekday) {
|
2016-08-30 19:59:04 +02:00
|
|
|
s << setw(2) << hour() << ':' << setw(2) << minute() << ':' << setw(2) << second();
|
2015-04-22 18:36:40 +02:00
|
|
|
int ms = millisecond();
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!noMilliseconds && ms > 0) {
|
2016-08-30 19:59:04 +02:00
|
|
|
s << '.' << setw(3) << ms;
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
result = s.str();
|
|
|
|
}
|
|
|
|
|
2016-08-30 19:59:04 +02:00
|
|
|
/*!
|
2020-04-11 21:51:52 +02:00
|
|
|
* \brief Returns the string representation of the current instance in the ISO format with custom delimiters,
|
|
|
|
* eg. 2016/08/29T21-32-31.588539814+02:00 with '/' as \a dateDelimiter and '-' as \a timeDelimiter.
|
2016-08-30 19:59:04 +02:00
|
|
|
*/
|
2020-04-11 21:51:52 +02:00
|
|
|
string DateTime::toIsoStringWithCustomDelimiters(TimeSpan timeZoneDelta, char dateDelimiter, char timeDelimiter, char timeZoneDelimiter) const
|
2016-08-30 19:59:04 +02:00
|
|
|
{
|
|
|
|
stringstream s(stringstream::in | stringstream::out);
|
|
|
|
s << setfill('0');
|
2020-04-13 01:10:33 +02:00
|
|
|
s << setw(4) << year() << dateDelimiter << setw(2) << month() << dateDelimiter << setw(2) << day() << 'T' << setw(2) << hour() << timeDelimiter
|
|
|
|
<< setw(2) << minute() << timeDelimiter << setw(2) << second();
|
2017-08-23 23:04:22 +02:00
|
|
|
const int milli(millisecond());
|
|
|
|
const int micro(microsecond());
|
|
|
|
const int nano(nanosecond());
|
|
|
|
if (milli || micro || nano) {
|
|
|
|
s << '.' << setw(3) << milli;
|
|
|
|
if (micro || nano) {
|
|
|
|
s << setw(3) << micro;
|
|
|
|
if (nano) {
|
|
|
|
s << nano / TimeSpan::nanosecondsPerTick;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!timeZoneDelta.isNull()) {
|
2018-01-29 16:24:39 +01:00
|
|
|
if (timeZoneDelta.isNegative()) {
|
|
|
|
s << '-';
|
|
|
|
timeZoneDelta = TimeSpan(-timeZoneDelta.totalTicks());
|
|
|
|
} else {
|
|
|
|
s << '+';
|
|
|
|
}
|
2020-04-11 21:51:52 +02:00
|
|
|
s << setw(2) << timeZoneDelta.hours() << timeZoneDelimiter << setw(2) << timeZoneDelta.minutes();
|
2016-08-30 19:59:04 +02:00
|
|
|
}
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
2020-04-11 21:51:52 +02:00
|
|
|
/*!
|
|
|
|
* \brief Returns the string representation of the current instance in the ISO format,
|
|
|
|
* eg. 2016-08-29T21:32:31.588539814+02:00.
|
|
|
|
*/
|
|
|
|
string DateTime::toIsoString(TimeSpan timeZoneDelta) const
|
|
|
|
{
|
|
|
|
return toIsoStringWithCustomDelimiters(timeZoneDelta);
|
|
|
|
}
|
|
|
|
|
2015-04-22 18:36:40 +02:00
|
|
|
/*!
|
2016-01-18 23:41:30 +01:00
|
|
|
* \brief Returns the string representation as C-style string for the given day of week.
|
2015-04-22 18:36:40 +02:00
|
|
|
*
|
|
|
|
* If \a abbreviation is true, only the first three letters of the string will
|
|
|
|
* be returned.
|
|
|
|
* \sa DayOfWeek
|
|
|
|
*/
|
|
|
|
const char *DateTime::printDayOfWeek(DayOfWeek dayOfWeek, bool abbreviation)
|
|
|
|
{
|
2017-05-01 03:13:11 +02:00
|
|
|
if (abbreviation) {
|
|
|
|
switch (dayOfWeek) {
|
2015-04-22 18:36:40 +02:00
|
|
|
case DayOfWeek::Monday:
|
|
|
|
return "Mon";
|
|
|
|
case DayOfWeek::Tuesday:
|
|
|
|
return "Tue";
|
|
|
|
case DayOfWeek::Wednesday:
|
|
|
|
return "Wed";
|
|
|
|
case DayOfWeek::Thursday:
|
|
|
|
return "Thu";
|
|
|
|
case DayOfWeek::Friday:
|
|
|
|
return "Fri";
|
|
|
|
case DayOfWeek::Saturday:
|
|
|
|
return "Sat";
|
|
|
|
case DayOfWeek::Sunday:
|
|
|
|
return "Sun";
|
|
|
|
}
|
|
|
|
} else {
|
2017-05-01 03:13:11 +02:00
|
|
|
switch (dayOfWeek) {
|
2015-04-22 18:36:40 +02:00
|
|
|
case DayOfWeek::Monday:
|
|
|
|
return "Monday";
|
|
|
|
case DayOfWeek::Tuesday:
|
|
|
|
return "Tuesday";
|
|
|
|
case DayOfWeek::Wednesday:
|
|
|
|
return "Wednesday";
|
|
|
|
case DayOfWeek::Thursday:
|
|
|
|
return "Thursday";
|
|
|
|
case DayOfWeek::Friday:
|
|
|
|
return "Friday";
|
|
|
|
case DayOfWeek::Saturday:
|
|
|
|
return "Saturday";
|
|
|
|
case DayOfWeek::Sunday:
|
|
|
|
return "Sunday";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2017-07-29 00:32:19 +02:00
|
|
|
#if defined(PLATFORM_UNIX) && !defined(PLATFORM_MAC)
|
2017-04-19 21:48:23 +02:00
|
|
|
/*!
|
|
|
|
* \brief Returns a DateTime object that is set to the current date and time on this computer, expressed as the GMT time.
|
2017-11-29 19:11:01 +01:00
|
|
|
* \remarks Only available under UNIX-like systems supporting clock_gettime().
|
2017-04-19 21:48:23 +02:00
|
|
|
*/
|
|
|
|
DateTime DateTime::exactGmtNow()
|
|
|
|
{
|
|
|
|
struct timespec t;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &t);
|
2019-03-13 19:00:37 +01:00
|
|
|
return DateTime(DateTime::unixEpochStart().totalTicks() + static_cast<std::uint64_t>(t.tv_sec) * TimeSpan::ticksPerSecond
|
|
|
|
+ static_cast<std::uint64_t>(t.tv_nsec) / 100);
|
2017-04-19 21:48:23 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-04-22 18:36:40 +02:00
|
|
|
/*!
|
2016-01-18 23:41:30 +01:00
|
|
|
* \brief Converts the given date expressed in \a year, \a month and \a day to ticks.
|
2015-04-22 18:36:40 +02:00
|
|
|
*/
|
2022-02-15 23:00:18 +01:00
|
|
|
DateTime::TickType DateTime::dateToTicks(int year, int month, int day)
|
2015-04-22 18:36:40 +02:00
|
|
|
{
|
2017-06-25 15:09:16 +02:00
|
|
|
if (!inRangeInclMax(year, 1, 9999)) {
|
2015-04-22 18:36:40 +02:00
|
|
|
throw ConversionException("year is out of range");
|
|
|
|
}
|
2017-06-25 15:09:16 +02:00
|
|
|
if (!inRangeInclMax(month, 1, 12)) {
|
|
|
|
throw ConversionException("month is out of range");
|
|
|
|
}
|
2017-11-29 19:08:22 +01:00
|
|
|
const auto *const daysToMonth = reinterpret_cast<const int *>(isLeapYear(year) ? m_daysToMonth366 : m_daysToMonth365);
|
|
|
|
const int passedMonth = month - 1;
|
2017-06-25 15:09:16 +02:00
|
|
|
if (!inRangeInclMax(day, 1, daysToMonth[month] - daysToMonth[passedMonth])) {
|
|
|
|
throw ConversionException("day is out of range");
|
|
|
|
}
|
2017-08-23 23:04:22 +02:00
|
|
|
const auto passedYears = static_cast<unsigned int>(year - 1);
|
|
|
|
const auto passedDays = static_cast<unsigned int>(day - 1);
|
2017-09-22 00:23:02 +02:00
|
|
|
return (passedYears * m_daysPerYear + passedYears / 4 - passedYears / 100 + passedYears / 400
|
|
|
|
+ static_cast<unsigned int>(daysToMonth[passedMonth]) + passedDays)
|
2017-08-23 23:14:18 +02:00
|
|
|
* TimeSpan::ticksPerDay;
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-01-18 23:41:30 +01:00
|
|
|
* \brief Converts the given time expressed in \a hour, \a minute, \a second and \a millisecond to ticks.
|
2015-04-22 18:36:40 +02:00
|
|
|
*/
|
2022-02-15 23:00:18 +01:00
|
|
|
DateTime::TickType DateTime::timeToTicks(int hour, int minute, int second, double millisecond)
|
2015-04-22 18:36:40 +02:00
|
|
|
{
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!inRangeExclMax(hour, 0, 24)) {
|
2015-04-22 18:36:40 +02:00
|
|
|
throw ConversionException("hour is out of range");
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!inRangeExclMax(minute, 0, 60)) {
|
2015-04-22 18:36:40 +02:00
|
|
|
throw ConversionException("minute is out of range");
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!inRangeExclMax(second, 0, 60)) {
|
2015-04-22 18:36:40 +02:00
|
|
|
throw ConversionException("second is out of range");
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!inRangeExclMax(millisecond, 0.0, 1000.0)) {
|
2015-04-22 18:36:40 +02:00
|
|
|
throw ConversionException("millisecond is out of range");
|
|
|
|
}
|
2019-03-13 19:00:37 +01:00
|
|
|
return static_cast<std::uint64_t>(hour * TimeSpan::ticksPerHour) + static_cast<std::uint64_t>(minute * TimeSpan::ticksPerMinute)
|
|
|
|
+ static_cast<std::uint64_t>(second * TimeSpan::ticksPerSecond) + static_cast<std::uint64_t>(millisecond * TimeSpan::ticksPerMillisecond);
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-01-18 23:41:30 +01:00
|
|
|
* \brief Returns the specified date part.
|
2015-04-22 18:36:40 +02:00
|
|
|
* \sa DatePart
|
|
|
|
*/
|
|
|
|
int DateTime::getDatePart(DatePart part) const
|
|
|
|
{
|
2021-03-19 23:09:01 +01:00
|
|
|
const auto fullDays = static_cast<int>(m_ticks / TimeSpan::ticksPerDay);
|
|
|
|
const auto full400YearBlocks = fullDays / m_daysPer400Years;
|
|
|
|
const auto daysMinusFull400YearBlocks = fullDays - full400YearBlocks * m_daysPer400Years;
|
|
|
|
auto full100YearBlocks = daysMinusFull400YearBlocks / m_daysPer100Years;
|
2017-05-01 03:13:11 +02:00
|
|
|
if (full100YearBlocks == 4) {
|
2015-04-22 18:36:40 +02:00
|
|
|
full100YearBlocks = 3;
|
|
|
|
}
|
2021-03-19 23:09:01 +01:00
|
|
|
const auto daysMinusFull100YearBlocks = daysMinusFull400YearBlocks - full100YearBlocks * m_daysPer100Years;
|
|
|
|
const auto full4YearBlocks = daysMinusFull100YearBlocks / m_daysPer4Years;
|
|
|
|
const auto daysMinusFull4YearBlocks = daysMinusFull100YearBlocks - full4YearBlocks * m_daysPer4Years;
|
|
|
|
auto full1YearBlocks = daysMinusFull4YearBlocks / m_daysPerYear;
|
2017-05-01 03:13:11 +02:00
|
|
|
if (full1YearBlocks == 4) {
|
2015-04-22 18:36:40 +02:00
|
|
|
full1YearBlocks = 3;
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
if (part == DatePart::Year) {
|
2015-04-22 18:36:40 +02:00
|
|
|
return full400YearBlocks * 400 + full100YearBlocks * 100 + full4YearBlocks * 4 + full1YearBlocks + 1;
|
|
|
|
}
|
2021-03-19 23:09:01 +01:00
|
|
|
const auto restDays = daysMinusFull4YearBlocks - full1YearBlocks * m_daysPerYear;
|
2017-05-01 03:13:11 +02:00
|
|
|
if (part == DatePart::DayOfYear) { // day
|
2015-04-22 18:36:40 +02:00
|
|
|
return restDays + 1;
|
|
|
|
}
|
2021-03-19 23:09:01 +01:00
|
|
|
const auto *const daysToMonth = (full1YearBlocks == 3 && (full4YearBlocks != 24 || full100YearBlocks == 3)) ? m_daysToMonth366 : m_daysToMonth365;
|
|
|
|
auto month = 1;
|
2017-05-01 03:13:11 +02:00
|
|
|
while (restDays >= daysToMonth[month]) {
|
2015-04-22 18:36:40 +02:00
|
|
|
++month;
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
if (part == DatePart::Month) {
|
2015-04-22 18:36:40 +02:00
|
|
|
return month;
|
2017-05-01 03:13:11 +02:00
|
|
|
} else if (part == DatePart::Day) {
|
2015-04-22 18:36:40 +02:00
|
|
|
return restDays - daysToMonth[month - 1] + 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2019-06-10 21:56:46 +02:00
|
|
|
|
2022-08-12 20:40:17 +02:00
|
|
|
/// \cond
|
|
|
|
static DateTimeParts dateTimePartsFromParsingDistance(const int *valueIndex, const int *values)
|
|
|
|
{
|
|
|
|
return static_cast<DateTimeParts>((1 << (valueIndex - values + 1)) - 1);
|
|
|
|
}
|
|
|
|
/// \endcond
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses the specified ISO date time denotation provided as C-style string.
|
|
|
|
* \throws Throws a ConversionException if the specified \a str does not match the expected time format.
|
|
|
|
* \remarks
|
|
|
|
* - Parsing durations and time intervals is *not* supported.
|
|
|
|
* - Truncated representations are *not* supported.
|
|
|
|
* - Standardised extensions (ISO 8601-2:2019) are *not* supported.
|
|
|
|
* \sa https://en.wikipedia.org/wiki/ISO_8601
|
|
|
|
*/
|
|
|
|
DateTimeExpression DateTimeExpression::fromIsoString(const char *str)
|
|
|
|
{
|
|
|
|
auto res = DateTimeExpression();
|
|
|
|
int values[9] = { 0 };
|
|
|
|
int *const yearIndex = values + 0;
|
|
|
|
int *const monthIndex = values + 1;
|
|
|
|
int *const dayIndex = values + 2;
|
|
|
|
int *const hourIndex = values + 3;
|
|
|
|
int *const secondsIndex = values + 5;
|
|
|
|
int *const miliSecondsIndex = values + 6;
|
|
|
|
int *const deltaHourIndex = values + 7;
|
|
|
|
int *const valuesEnd = values + 9;
|
|
|
|
int *valueIndex = values;
|
|
|
|
unsigned int remainingDigits = 4;
|
|
|
|
bool deltaNegative = false;
|
|
|
|
double millisecondsFact = 100.0, milliseconds = 0.0;
|
|
|
|
for (const char *strIndex = str;; ++strIndex) {
|
|
|
|
const char c = *strIndex;
|
|
|
|
if (c <= '9' && c >= '0') {
|
|
|
|
if (valueIndex == miliSecondsIndex) {
|
|
|
|
milliseconds += (c - '0') * millisecondsFact;
|
|
|
|
millisecondsFact /= 10;
|
|
|
|
} else {
|
|
|
|
if (!remainingDigits) {
|
|
|
|
if (++valueIndex == miliSecondsIndex || valueIndex >= valuesEnd) {
|
|
|
|
throw ConversionException("Max. number of digits exceeded");
|
|
|
|
}
|
|
|
|
remainingDigits = 2;
|
|
|
|
}
|
|
|
|
*valueIndex *= 10;
|
|
|
|
*valueIndex += c - '0';
|
|
|
|
remainingDigits -= 1;
|
|
|
|
}
|
|
|
|
} else if (c == 'T') {
|
|
|
|
if (++valueIndex != hourIndex) {
|
|
|
|
throw ConversionException("\"T\" expected before hour");
|
|
|
|
}
|
|
|
|
remainingDigits = 2;
|
|
|
|
} else if (c == '-') {
|
|
|
|
if (valueIndex < dayIndex) {
|
|
|
|
++valueIndex;
|
|
|
|
} else if (++valueIndex >= secondsIndex) {
|
|
|
|
valueIndex = deltaHourIndex;
|
|
|
|
deltaNegative = true;
|
|
|
|
} else {
|
|
|
|
throw ConversionException("Unexpected \"-\" after day");
|
|
|
|
}
|
|
|
|
remainingDigits = 2;
|
|
|
|
} else if (c == '.') {
|
|
|
|
if (valueIndex != secondsIndex) {
|
|
|
|
throw ConversionException("Unexpected \".\"");
|
|
|
|
} else {
|
|
|
|
++valueIndex;
|
|
|
|
}
|
|
|
|
} else if (c == ':') {
|
|
|
|
if (valueIndex < hourIndex) {
|
|
|
|
throw ConversionException("Unexpected \":\" before hour");
|
|
|
|
} else if (valueIndex == secondsIndex) {
|
|
|
|
throw ConversionException("Unexpected \":\" after second");
|
|
|
|
} else {
|
|
|
|
++valueIndex;
|
|
|
|
}
|
|
|
|
remainingDigits = 2;
|
|
|
|
} else if ((c == '+') && (++valueIndex >= secondsIndex)) {
|
|
|
|
valueIndex = deltaHourIndex;
|
|
|
|
deltaNegative = false;
|
|
|
|
remainingDigits = 2;
|
|
|
|
} else if ((c == 'Z') && (++valueIndex >= secondsIndex)) {
|
|
|
|
valueIndex = deltaHourIndex + 2;
|
|
|
|
remainingDigits = 2;
|
|
|
|
} else if (c == '\0') {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
throw ConversionException(argsToString("Unexpected \"", c, '\"'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res.delta = TimeSpan::fromMinutes(*deltaHourIndex * 60 + values[8]);
|
|
|
|
if (deltaNegative) {
|
|
|
|
res.delta = TimeSpan(-res.delta.totalTicks());
|
|
|
|
}
|
|
|
|
if (valueIndex < monthIndex && !*monthIndex) {
|
|
|
|
*monthIndex = 1;
|
|
|
|
}
|
|
|
|
if (valueIndex < dayIndex && !*dayIndex) {
|
|
|
|
*dayIndex = 1;
|
|
|
|
}
|
|
|
|
res.value = DateTime::fromDateAndTime(*yearIndex, *monthIndex, *dayIndex, *hourIndex, values[4], *secondsIndex, milliseconds);
|
|
|
|
res.parts = dateTimePartsFromParsingDistance(valueIndex, values);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses the given C-style string.
|
|
|
|
* \throws Throws a ConversionException if the specified \a str does not match the expected time format.
|
|
|
|
*
|
|
|
|
* The expected format is something like "2012-02-29 15:34:20.033" or "2012/02/29 15:34:20.033". The
|
|
|
|
* delimiters '-', ':' and '/' are exchangeable.
|
|
|
|
*
|
|
|
|
* \sa DateTimeExpression::fromIsoString()
|
|
|
|
*/
|
|
|
|
DateTimeExpression DateTimeExpression::fromString(const char *str)
|
|
|
|
{
|
|
|
|
auto res = DateTimeExpression();
|
|
|
|
int values[7] = { 0 };
|
|
|
|
int *const monthIndex = values + 1;
|
|
|
|
int *const dayIndex = values + 2;
|
|
|
|
int *const secondsIndex = values + 5;
|
|
|
|
int *valueIndex = values;
|
|
|
|
int *const valuesEnd = values + 7;
|
|
|
|
double millisecondsFact = 100.0, milliseconds = 0.0;
|
|
|
|
for (const char *strIndex = str;; ++strIndex) {
|
|
|
|
const char c = *strIndex;
|
|
|
|
if (c <= '9' && c >= '0') {
|
|
|
|
if (valueIndex > secondsIndex) {
|
|
|
|
milliseconds += (c - '0') * millisecondsFact;
|
|
|
|
millisecondsFact /= 10;
|
|
|
|
} else {
|
|
|
|
Detail::raiseAndAdd(*valueIndex, 10, c);
|
|
|
|
}
|
|
|
|
} else if ((c == '-' || c == ':' || c == '/') || (c == '.' && (valueIndex == secondsIndex))
|
|
|
|
|| ((c == ' ' || c == 'T') && (valueIndex == dayIndex))) {
|
|
|
|
if (++valueIndex == valuesEnd) {
|
|
|
|
break; // just ignore further values for now
|
|
|
|
}
|
|
|
|
} else if (c == '\0') {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
throw ConversionException(argsToString("Unexpected character \"", c, '\"'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (valueIndex < monthIndex && !*monthIndex) {
|
|
|
|
*monthIndex = 1;
|
|
|
|
}
|
|
|
|
if (valueIndex < dayIndex && !*dayIndex) {
|
|
|
|
*dayIndex = 1;
|
|
|
|
}
|
|
|
|
res.value = DateTime::fromDateAndTime(values[0], values[1], *dayIndex, values[3], values[4], *secondsIndex, milliseconds);
|
|
|
|
res.parts = dateTimePartsFromParsingDistance(valueIndex, values);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2022-08-13 14:10:40 +02:00
|
|
|
/*!
|
|
|
|
* \brief Returns the string representation of the current instance in the ISO format.
|
|
|
|
* \remarks Only present parts will be included.
|
|
|
|
*/
|
|
|
|
std::string DateTimeExpression::toIsoString(char dateDelimiter, char timeDelimiter, char timeZoneDelimiter) const
|
|
|
|
{
|
|
|
|
auto s = std::stringstream(std::stringstream::in | std::stringstream::out);
|
|
|
|
s << setfill('0');
|
|
|
|
if (parts & DateTimeParts::Year) {
|
|
|
|
s << setw(4) << value.year();
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::Month) {
|
|
|
|
if (s.tellp()) {
|
|
|
|
s << dateDelimiter;
|
|
|
|
}
|
|
|
|
s << setw(2) << value.month();
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::Day) {
|
|
|
|
if (s.tellp()) {
|
|
|
|
s << dateDelimiter;
|
|
|
|
}
|
|
|
|
s << setw(2) << value.day();
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::Hour) {
|
|
|
|
if (s.tellp()) {
|
|
|
|
s << 'T';
|
|
|
|
}
|
|
|
|
s << setw(2) << value.hour();
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::Minute) {
|
|
|
|
if (s.tellp()) {
|
|
|
|
s << timeDelimiter;
|
|
|
|
}
|
|
|
|
s << setw(2) << value.minute();
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::Second) {
|
|
|
|
if (s.tellp()) {
|
|
|
|
s << timeDelimiter;
|
|
|
|
}
|
|
|
|
s << setw(2) << value.second();
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::SubSecond) {
|
|
|
|
const auto milli = value.millisecond();
|
|
|
|
const auto micro = value.microsecond();
|
|
|
|
const auto nano = value.nanosecond();
|
|
|
|
s << '.' << setw(3) << milli;
|
|
|
|
if (micro || nano) {
|
|
|
|
s << setw(3) << micro;
|
|
|
|
if (nano) {
|
|
|
|
s << nano / TimeSpan::nanosecondsPerTick;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::TimeZoneDelta) {
|
|
|
|
auto d = delta;
|
|
|
|
if (d.isNegative()) {
|
|
|
|
s << '-';
|
|
|
|
d = TimeSpan(-d.totalTicks());
|
|
|
|
} else {
|
|
|
|
s << '+';
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::DeltaHour) {
|
|
|
|
s << setw(2) << d.hours();
|
|
|
|
}
|
|
|
|
if (parts & DateTimeParts::DeltaMinute) {
|
|
|
|
if (parts & DateTimeParts::DeltaHour) {
|
|
|
|
s << timeZoneDelimiter;
|
|
|
|
}
|
|
|
|
s << setw(2) << d.minutes();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
2019-06-10 21:56:46 +02:00
|
|
|
} // namespace CppUtilities
|