HowardHinnant / date

A date and time library based on the C++11/14/17 <chrono> header
Other
3.07k stars 669 forks source link

Date Parse: one pattern for ISO8601 DATE or DATE TIME #803

Open shawncao opened 9 months ago

shawncao commented 9 months ago

I'm wondering if there is a flexible way to write the pattern so that it can parse either DATE or DATE-TIME (ISO8601)

for example, I have mixed values from a set:

I hope we can do

value >> date::parse("%FT%T", timepoint)

Obviously "%FT%T" will translate "2021-11-02" into value 0

Any ideas?

shawncao commented 9 months ago

this is my hack to work around this issue, not ideal:

  static int64_t time(const std::string_view datetime, const std::string& pattern) {
    // introduce a special tag to handle iso8601 flexible values: DATE or DATETIME
    // for example 2021-11-02, 2021-11-02T12:00:00Z, 2021-11-02T12:00:00.123Z
    auto p = pattern;
    if (pattern == "iso8601") {
      if (datetime.size() == 10) {
        p = "%F";
      } else {
        p = "%FT%TZ";
      }
    }

    date::sys_time<std::chrono::microseconds> tp;
    std::istringstream value(datetime.data());
    value >> date::parse(p, tp);
    if (value.fail()) {
      LOG(ERROR) << "Failed to parse time: " << datetime;
      return 0;
    }

    return seconds(tp);
  }
HowardHinnant commented 9 months ago

What do you think about this?

// parse "%F" or "%FT%TZ"
int64_t
time(const std::string_view datetime)
{
    std::istringstream in{datetime.data()};
    date::sys_seconds tp;
    std::chrono::seconds tod{};
    in >> date::parse("%F", tp);
    if (in.fail())
    {
        std::cerr << "Failed to parse time: " << datetime;
        return 0;
    }
    in >> date::parse("T%TZ", tod);
    tp += tod;
    return tp.time_since_epoch().count();
}

The one thing it might not do right for you is interpret "2021-11-23T20:55:52" as equivalent to "2021-11-23" because of the syntax error in the time part (missing trailing Z). If you want to interpret syntax errors in an attempted time-of-day you could peek into the stream looking for 'T' and then commit to parsing a duration or returning an error.

shawncao commented 9 months ago

This is pretty smart! Using two steps instead of one, thanks for sharing this!

shawncao commented 9 months ago

@HowardHinnant may I ask you another question?

What is the name for a date time string like this "2023-09-17 17:08:00.368787+00:00", is this another standard? What pattern format to use to parse date time string like this?

HowardHinnant commented 9 months ago

Not sure about a standard name. But this will parse it:

#include "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>

int
main()
{
    using namespace date;
    using namespace std;
    using namespace chrono;

    istringstream in{"2023-09-17 17:08:00.368787+00:00"};
    sys_time<microseconds> tp;
    in >> parse("%F %T%Ez", tp);
    cout << tp << '\n';
}
shawncao commented 9 months ago

Awesome, thank you again!