简体   繁体   中英

std::chrono::time_point from std::string

I am trying to convert a date (in form of a std::string ) to a std::chrono::time_point . For that, I use the Boost Date Time. Below, you can find a minimal working example that does that. However, I do not understand why certain input strings that are invalid in my opinion seem to raise no exception somehow. I cannot figure out what is going on here.

#include <iostream>
#include <chrono>
#include <sstream>
#include <boost/date_time.hpp>

using Clock = std::chrono::system_clock;
using TimePoint = std::chrono::time_point<Clock>;

TimePoint timePointFromString(const std::string& date, const std::string& format) {
  // local takes care of destructing time_input_facet
  auto loc = std::locale(std::locale::classic(), new boost::posix_time::time_input_facet(format));

  std::stringstream ss{date};
  ss.imbue(loc);

  boost::posix_time::ptime pt;
  ss >> pt;

  if (!ss.good()) {
    throw std::runtime_error("Cannot parse string");
  }

  boost::posix_time::ptime time_t_epoch{boost::gregorian::date(1970, 1, 1)};
  boost::posix_time::time_duration diff = pt - time_t_epoch;

  Clock::duration duration{diff.total_nanoseconds()};

  return TimePoint{duration};
}

int main() {
  std::string format{"%Y-%m-%d"};

  std::vector<std::string> strings {"2018", "2018-", "19700101", "19700103", "19700301"};
  for (const auto& s: strings) {
    auto tp = timePointFromString(s, format);
    std::cout << s << ": " << TimePoint::clock::to_time_t(tp) << std::endl;
  }
}

Output:

2018: 1514764800
2018-: 1514764800
19700101: 23587200
19700103: 23587200
terminate called after throwing an instance of 'std::runtime_error'
  what():  Cannot parse string

Update: I misunderstood this piece of code by thinking it would do some sort of pattern matching. This is not the case (see Öö Tiib answer and comments below his answer)! Apparently, it's best to use Howard Hinnant's date/time library.

Here's how your code would look if you used Howard Hinnant's free, open-source date/time library :

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

std::chrono::system_clock::time_point
timePointFromString(const std::string& date, const std::string& format)
{
    std::stringstream ss{date};
    std::chrono::system_clock::time_point pt;
    ss >> date::parse(format, pt);
    if (ss.fail())
        throw std::runtime_error("Cannot parse date");
    return pt;
}

int
main()
{
    std::string format{"%Y-%m-%d"};
    std::vector<std::string> strings{"2018", "2018-", "19700101", "19700103", "19700301",
                                     "1970-03-01"};
    for (const auto& s: strings)
    {
        try
        {
            auto tp = timePointFromString(s, format);
            using date::operator<<;
            std::cout << s << ": " << tp << '\n';
        }
        catch (std::exception const& e)
        {
            std::cout << s << ": " << e.what() << '\n';
        }
    }
}

And the output would be:

2018: Cannot parse date
2018-: Cannot parse date
19700101: Cannot parse date
19700103: Cannot parse date
19700301: Cannot parse date
1970-03-01: 1970-03-01 00:00:00.000000

I added a valid string/date at the end of the vector to show what it accepts using this format . And the number of trailing zeroes on the 1970-03-01 00:00:00.0... will vary depending on the precision of your platform's std::chrono::system_clock::time_point .

Also this code should easily port to C++20:

  • Drop #include "date/date.h" .
  • Drop using date::operator<<;
  • Change date::parse to std::chrono::parse .

Update

To help you interpret your results:

  • 1514764800s after 1970-01-01 00:00:00 is 2018-01-01 00:00:00
  • 23587200s after 1970-01-01 00:00:00 is 1970-10-01 00:00:00

(all neglecting leap seconds which is the norm)

You did not explain what you expected and why so I just describe what your program does.

With "19700101" and "19700103" it parses "1970" skips a character parses "10" skips a character and finds end of string so it concludes that both are first October 1970.

With "19700301" it parses "1970" skips a character parses "30" and throws up since month 30 is nonsense.

Also your output description has typo, you throw "Cannot parse date" not "Cannot parse string".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM