From 32780ed6a63fa935a582443b2bd17e0e9723d65a Mon Sep 17 00:00:00 2001 From: Martchus Date: Sun, 26 Apr 2020 21:38:02 +0200 Subject: [PATCH] Support formatting ISO timestamps via DateTime::toString() with option to omit defaults Omitting components are also allowed when parsing ISO timestamps so it makes sense to have something similar in the other direction as well. Note that the idea comes from ID3v2.4.0 which stores timestamps in a subset of ISO 8601 similarily to what this library supports and it allows to omit default components as well. --- chrono/datetime.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++ chrono/datetime.h | 4 +++- tests/chronotests.cpp | 17 ++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/chrono/datetime.cpp b/chrono/datetime.cpp index 41f5c85..b75820f 100644 --- a/chrono/datetime.cpp +++ b/chrono/datetime.cpp @@ -196,8 +196,53 @@ std::pair DateTime::fromIsoString(const char *str) */ void DateTime::toString(string &result, DateTimeOutputFormat format, bool noMilliseconds) const { + if (format == DateTimeOutputFormat::Iso) { + result = toIsoString(); + return; + } + stringstream s(stringstream::in | stringstream::out); s << setfill('0'); + + 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; + } + if (format == DateTimeOutputFormat::DateTimeAndWeekday || format == DateTimeOutputFormat::DateTimeAndShortWeekday) s << printDayOfWeek(dayOfWeek(), format == DateTimeOutputFormat::DateTimeAndShortWeekday) << ' '; if (format == DateTimeOutputFormat::DateOnly || format == DateTimeOutputFormat::DateAndTime || format == DateTimeOutputFormat::DateTimeAndWeekday diff --git a/chrono/datetime.h b/chrono/datetime.h index 319f8e1..e3173f6 100644 --- a/chrono/datetime.h +++ b/chrono/datetime.h @@ -19,7 +19,9 @@ enum class DateTimeOutputFormat { DateOnly, /**< date only */ TimeOnly, /**< time only */ DateTimeAndWeekday, /**< date with weekday and time */ - DateTimeAndShortWeekday /**< date with abbreviated weekday and time */ + DateTimeAndShortWeekday, /**< date with abbreviated weekday and time */ + Iso, /**< ISO format like DateTime::toIsoString() */ + IsoOmittingDefaultComponents, /**< ISO format like DateTime::toIsoString() omitting default components, e.g. just "2017" instead of "2017-01-01T00:00:00" */ }; /*! diff --git a/tests/chronotests.cpp b/tests/chronotests.cpp index 65a9028..3b67616 100644 --- a/tests/chronotests.cpp +++ b/tests/chronotests.cpp @@ -187,6 +187,23 @@ void ChronoTests::testDateTime() CPPUNIT_ASSERT_THROW_MESSAGE("invalid .", DateTime::fromIsoString("2017-08.5-23T19:40:15.985077682+02:00"), ConversionException); CPPUNIT_ASSERT_THROW_MESSAGE("invalid :", DateTime::fromIsoString("2017:08-23T19:40:15.985077682+02:00"), ConversionException); CPPUNIT_ASSERT_THROW_MESSAGE("invalid :", DateTime::fromIsoString("2017-08-23T19:40:15:985077682+02:00"), ConversionException); + // ISO string via toString() format option + CPPUNIT_ASSERT_EQUAL("1234-05-06T07:08:09.0105005"s, DateTime::fromDateAndTime(1234, 5, 6, 7, 8, 9, 10.5005).toString(DateTimeOutputFormat::Iso)); + CPPUNIT_ASSERT_EQUAL("1234-05-06T07:08:09.0105005"s, + DateTime::fromDateAndTime(1234, 5, 6, 7, 8, 9, 10.5005).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL("1234-05-06T07:08:09.010500"s, + DateTime::fromDateAndTime(1234, 5, 6, 7, 8, 9, 10.500).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL( + "1234-05-06T07:08:09.010"s, DateTime::fromDateAndTime(1234, 5, 6, 7, 8, 9, 10).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL( + "1234-05-06T07:08:09"s, DateTime::fromDateAndTime(1234, 5, 6, 7, 8, 9).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL( + "1234-05-06T07:08"s, DateTime::fromDateAndTime(1234, 5, 6, 7, 8).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL("1234-05-06T07"s, DateTime::fromDateAndTime(1234, 5, 6, 7).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL("1234-05-06"s, DateTime::fromDateAndTime(1234, 5, 6).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL("1234-05"s, DateTime::fromDateAndTime(1234, 5).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL("1234"s, DateTime::fromDateAndTime(1234).toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); + CPPUNIT_ASSERT_EQUAL("0001"s, DateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents)); // test now() and exactNow() (or at least whether both behave the same) #if defined(PLATFORM_UNIX)