diff --git a/chrono/datetime.cpp b/chrono/datetime.cpp index f396e1e..979260f 100644 --- a/chrono/datetime.cpp +++ b/chrono/datetime.cpp @@ -528,4 +528,78 @@ DateTimeExpression DateTimeExpression::fromString(const char *str) return res; } +/*! + * \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(); +} + } // namespace CppUtilities diff --git a/chrono/datetime.h b/chrono/datetime.h index 863ebd8..33beefb 100644 --- a/chrono/datetime.h +++ b/chrono/datetime.h @@ -166,6 +166,8 @@ struct CPP_UTILITIES_EXPORT DateTimeExpression { DateTimeParts parts = DateTimeParts::None; /**< The parts present in the expression as flag enum. */ constexpr DateTime gmt() const; + constexpr bool operator==(const DateTimeExpression &other) const; + std::string toIsoString(char dateDelimiter = '-', char timeDelimiter = ':', char timeZoneDelimiter = ':') const; static DateTimeExpression fromIsoString(const char *str); static DateTimeExpression fromString(const char *str); }; @@ -178,6 +180,14 @@ constexpr DateTime DateTimeExpression::gmt() const return value - delta; } +/*! + * \brief Returns whether the expressions are equivalent. + */ +constexpr bool DateTimeExpression::operator==(const DateTimeExpression &other) const +{ + return value == other.value && delta == other.delta && parts == other.parts; +} + /*! * \brief Constructs a DateTime. */ diff --git a/tests/chronotests.cpp b/tests/chronotests.cpp index 9294ce4..38c6e12 100644 --- a/tests/chronotests.cpp +++ b/tests/chronotests.cpp @@ -230,44 +230,53 @@ void ChronoTests::testDateTime() */ void ChronoTests::testDateTimeExpression() { - // check adding ISO timestamp parts one-by-one + // check adding ISO timestamp parts one-by-one and serialization back to string auto expr = DateTimeExpression::fromIsoString("1"); auto parts = DateTimeParts::Year; CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::Month, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1-1"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::Day, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01-01"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1-1T0"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::Hour, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01-01T00"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1-1T0:0"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::Minute, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01-01T00:00"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1-1T0:0:0"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::Second, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01-01T00:00:00"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1-1T0:0:0.0"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::SubSecond, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01-01T00:00:00.000"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1-1T0:0:0.0+0"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::DeltaHour, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01-01T00:00:00.000+00"s, expr.toIsoString()); expr = DateTimeExpression::fromIsoString("1-1-1T0:0:0.0-0:0"); CPPUNIT_ASSERT_EQUAL(DateTime(), expr.value); CPPUNIT_ASSERT_EQUAL(TimeSpan(), expr.delta); CPPUNIT_ASSERT_EQUAL(parts |= DateTimeParts::DeltaMinute, expr.parts); + CPPUNIT_ASSERT_EQUAL("0001-01-01T00:00:00.000+00:00"s, expr.toIsoString()); // check that omitting parts in the middle is not possible anyways CPPUNIT_ASSERT_THROW(DateTimeExpression::fromIsoString("1-1T0"), ConversionException);