4

I need to be able to parse and store various dates, times, or both according to a subset of the ISO-8601 standard.

The dates are in the formats:

  • YYYY
  • YYYY-mm
  • YYYY-mm-dd

The times are in the formats:

  • HH:MM:SS
  • HH:MM:SS.ffffff

If a date and time are both defined, then a timezone must also be defined, like so:

  • YYYY-mm-ddTHH:MM:SS.ffffff+ZZ:ZZ
    • For example: 2012-03-04T05:06:07.123456+07:00

I tried to use Howard Hinnant's date library, the same one in the C++20 standard. It seems I need to use specific types to parse different formats which is slightly annoying. I would rather be able to parse and store any format within the same type.

To illustrate the problem:

sys_time<microseconds> sys_us;
microseconds us;
year_month ym;
year y;

std::istringstream iss;

iss.str("2012-03-04T05:06:07.123456+07:00");
iss >> date::parse("%FT%T%Ez", sys_us); // Only works with this type. (The others can't parse this much info.)
assert(!iss.fail());

iss.str("2012-03-04");
iss >> date::parse("%F", sys_us); // If the date has the full year, month, and day, this works.
assert(!iss.fail());

iss.str("2012-03");
// iss >> date::parse("%Y-%m", sys_us); // This fails; day is missing.
iss >> date::parse("%Y-%m", ym); // Works.
assert(!iss.fail());

iss.str("2012");
// iss >> date::parse("%Y", sys_us); // This fails.
// iss >> date::parse("%Y", ym); // Also fails; month is missing.
iss >> date::parse("%Y", y); // Works.
assert(!iss.fail());

iss.str("05:06:07.123456");
// iss >> date::parse("%T", sys_us); // Also fails; unhappy with missing date.
iss >> date::parse("%T", us); // Must use duration type for time instead.
assert(!iss.fail());

It would be much nicer if I could date::parse(format, obj) where obj didn't need to change types. Is that possible?

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • 1
    And what type would they all use? These seem to be different concepts. Consider `2012 + 1 ` vs `12:30 + 1`. Very different expectations. You could parse into a struct with each element optional, but that isn't useful as operations don't make sense. You could consider a variant, but again, the problem that seems to be more pressing is, "And then what do you do with it?" – Glenn Teitelbaum Nov 08 '21 at 19:30
  • I was thinking they could all use the sys_time type. Maybe it could just assume that a time without a date is that time past the epoch. And a year without a month or day would just assume it was the first month or first day. – user17360045 Nov 08 '21 at 19:34
  • @user17360045: "*I would rather be able to parse and store any format within the same type.*" If there is one over-arching design principle of Chrono, it's that there is no one right type that is appropriate for all things with regard to time. And forcing the user to be aware of that is a really good idea. Forcing the user to know that a year is not the same thing as a full point in time is good. – Nicol Bolas Nov 08 '21 at 19:38

1 Answers1

6

The only way to store them all in the same type is to pick the one with the most information (sys_time<microseconds>), then do the parse in the partial types as you've shown and add defaults for those values not parsed.

For example:

iss.str("05:06:07.123456");
iss >> date::parse("%T", us); // Must use duration type for time     
sys_us = sys_days{year{0}/1/1} + us;  // Add defaulted date
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • But why doesn't this work? `sys_time yt; iss >> date::parse("%Y", yt); ` Why can't dates without months and days be stored as time_point? – stilgar Apr 06 '22 at 10:52
  • 1
    I made a design decision that `parse` would be a "checking" interface, intended to catch common logic errors. Most notably, if you try to parse a `time_point`, and the input stream doesn't contain sufficient information to create a `time_point`, then a runtime error is flagged on the stream. There exists no universally accepted default year, default month, or default day of the month which could be supplied if said information was absent in the stream. That being said, there also exist workarounds, such as parsing durations and supplying your own defaults. – Howard Hinnant Apr 06 '22 at 12:45
  • 1
    `sys_time` is a `time_point` with a precision of the average year length (365.2425 days). There exists no commonly accepted way to precisely map a year number/name (e.g. 2022) into such a `time_point`. However you can parse into the partial calendrical type `year` with `%Y`, and subsequently combine that `year` with any default `month` and `day` you desire. – Howard Hinnant Apr 06 '22 at 12:45
  • 1
    I guess that makes sense. I just incorrectly assumed that sys_time would be equivalent of std::chrono::year, but it obviously isn't. – stilgar Apr 07 '22 at 14:18
  • I struggled for a long time with the names `year` and `years`, and ditto for `months`/`month` and `days`/`day`. I knew it would cause confusion, but failed to come up with better names. However there is a pattern to it that can be easily learned. I leave that lesson to this post: https://stackoverflow.com/q/62249520/576911 – Howard Hinnant Apr 07 '22 at 16:53