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.
This commit is contained in:
Martchus 2020-04-26 21:38:02 +02:00
parent c834f8923d
commit 32780ed6a6
3 changed files with 65 additions and 1 deletions

View File

@ -196,8 +196,53 @@ std::pair<DateTime, TimeSpan> 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

View File

@ -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" */
};
/*!

View File

@ -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)