How to parse a date string into a c++11 std::chrono time_point or similar?

C++C++11Date ParsingDatetime Parsing

C++ Problem Overview


Consider a historic date string of format:

Thu Jan 9 12:35:34 2014

I want to parse such a string into some kind of C++ date representation, then calculate the amount of time that has passed since then.

From the resulting duration I need access to the numbers of seconds, minutes, hours and days.

Can this be done with the new C++11 std::chrono namespace? If not, how should I go about this today?

I'm using g++-4.8.1 though presumably an answer should just target the C++11 spec.

C++ Solutions


Solution 1 - C++

std::tm tm = {};
std::stringstream ss("Jan 9 2014 12:35:34");
ss >> std::get_time(&tm, "%b %d %Y %H:%M:%S");
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));

GCC prior to version 5 doesn't implement std::get_time. You should also be able to write:

std::tm tm = {};
strptime("Thu Jan 9 2014 12:35:34", "%a %b %d %Y %H:%M:%S", &tm);
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));

Solution 2 - C++

New answer for old question. Rationale for the new answer: The question was edited from its original form because tools at the time would not handle exactly what was being asked. And the resulting accepted answer gives a subtly different behavior than what the original question asked for.

I'm not trying to put down the accepted answer. It's a good answer. It's just that the C API is so confusing that it is inevitable that mistakes like this will happen.

The original question was to parse "Thu, 9 Jan 2014 12:35:34 +0000". So clearly the intent was to parse a timestamp representing a UTC time. But strptime (which isn't standard C or C++, but is POSIX) does not parse the trailing UTC offset indicating this is a UTC timestamp (it will format it with %z, but not parse it).

The question was then edited to ask about "Thu Jan 9 12:35:34 2014". But the question was not edited to clarify if this was a UTC timestamp, or a timestamp in the computer's current local timezone. The accepted answer implicitly assumes the timestamp represents the computer's current local timezone because of the use of std::mktime.

std::mktime not only transforms the field type tm to the serial type time_t, it also performs an offset adjustment from the computer's local time zone to UTC.

But what if we want to parse a UTC timestamp as the original (unedited) question asked?

That can be done today using this newer, free open-source library.

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

int
main()
{
    using namespace std;
    using namespace date;
    istringstream in{"Thu, 9 Jan 2014 12:35:34 +0000"};
    sys_seconds tp;
    in >> parse("%a, %d %b %Y %T %z", tp);
}

This library can parse %z. And date::sys_seconds is just a typedef for:

std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>

The question also asks:

> From the resulting duration I need access to the numbers of seconds, minutes, hours and days.

That part has remained unanswered. Here's how you do it with this library.

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

int
main()
{
    using namespace std;
    using namespace date;
    istringstream in{"Thu, 9 Jan 2014 12:35:34 +0000"};
    sys_seconds tp;
    in >> parse("%a, %d %b %Y %T %z", tp);
    auto tp_days = floor<days>(tp);
    auto hms = hh_mm_ss<seconds>{tp - tp_days};
    std::cout << "Number of days    = " << tp_days.time_since_epoch() << '\n';
    std::cout << "Number of hours   = " << hms.hours() << '\n';
    std::cout << "Number of minutes = " << hms.minutes() << '\n';
    std::cout << "Number of seconds = " << hms.seconds() << '\n';
}

floor<days> truncates the seconds-precision time_point to a days-precision time_point. If you subtract the days-precision time_point from tp, you're left with a duration that represents the time since midnight (UTC).

The type hh_mm_ss<seconds> takes any duration convertible to seconds (in this case time since midnight) and creates a {hours, minutes, seconds} field type with getters for each field. If the duration has precision finer than seconds this field type will also have a getter for the subseconds. Prior to C++17, one has to specify that finer duration as the template parameter. In C++17 and later it can be deduced:

auto hms = hh_mm_ss{tp - tp_days};

Finally, one can just print out all of these durations. This example outputs:

Number of days    = 16079d
Number of hours   = 12h
Number of minutes = 35min
Number of seconds = 34s

So 2014-01-09 is 16079 days after 1970-01-01.

Here is the full example but at milliseconds precision:

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

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

    istringstream in{"Thu, 9 Jan 2014 12:35:34.123 +0000"};
    sys_time<milliseconds> tp;
    in >> parse("%a, %d %b %Y %T %z", tp);
    auto tp_days = floor<days>(tp);
    hh_mm_ss hms{tp - tp_days};
    std::cout << tp << '\n';
    std::cout << "Number of days         = " << tp_days.time_since_epoch() << '\n';
    std::cout << "Number of hours        = " << hms.hours() << '\n';
    std::cout << "Number of minutes      = " << hms.minutes() << '\n';
    std::cout << "Number of seconds      = " << hms.seconds() << '\n';
    std::cout << "Number of milliseconds = " << hms.subseconds() << '\n';
}

Output:

2014-01-09 12:35:34.123
Number of days         = 16079d
Number of hours        = 12h
Number of minutes      = 35min
Number of seconds      = 34s
Number of milliseconds = 123ms

This library is now part of C++20, but is in namespace std::chrono and found in the header <chrono>.

Solution 3 - C++

This is rather C-ish and not as elegant of a solution as Simple's answer, but I think it might work. This answer is probably wrong but I'll leave it up so someone can post corrections.

#include <iostream>
#include <ctime>

int main ()
{
  struct tm timeinfo;
  std::string buffer = "Thu, 9 Jan 2014 12:35:00";

  if (!strptime(buffer.c_str(), "%a, %d %b %Y %T", &timeinfo))
    std::cout << "Error.";
            
  time_t now;
  struct tm timeinfo2;
  time(&now);
  timeinfo2 = *gmtime(&now);
  
  time_t seconds = difftime(mktime(&timeinfo2), mktime(&timeinfo));
  time(&seconds);
  struct tm result;
  result = *gmtime ( &seconds );
  std::cout << result.tm_sec << " " << result.tm_min << " "
            << result.tm_hour << " " << result.tm_mday;
  return 0;
}

Solution 4 - C++

Cases covered (code is below):

  • since a give date until now

    long int min0 = getMinutesSince( "2005-02-19 12:35:00" );

  • since the epoch until now

    long int min1 = getMinutesSince1970( );

  • between two date+hours (since the epoch until a given date)

    long int min0 = getMinutesSince1970Until( "2019-01-18 14:23:00" );

    long int min1 = getMinutesSince1970Until( "2019-01-18 14:27:00" );

    cout << min1 - min0 << endl;

Complete code:

#include <iostream>
#include <chrono>
#include <sstream>
#include <string>
#include <iomanip>

using namespace std;

// ------------------------------------------------
// ------------------------------------------------
long int getMinutesSince1970Until( string dateAndHour ) {

  tm tm = {};
  stringstream ss( dateAndHour );
  ss >> get_time(&tm, "%Y-%m-%d  %H:%M:%S");

  chrono::system_clock::time_point tp = chrono::system_clock::from_time_t(mktime(&tm));


  return
    chrono::duration_cast<chrono::minutes>(
                                           tp.time_since_epoch()).count();

} // ()
// ------------------------------------------------
// ------------------------------------------------
long int getMinutesSince1970() {
  chrono::system_clock::time_point now = chrono::system_clock::now();
  
  return
    chrono::duration_cast<chrono::minutes>( now.time_since_epoch() ).count();
} // ()

// ------------------------------------------------
// ------------------------------------------------
long int getMinutesSince( string dateAndHour ) {

  tm tm = {};
  stringstream ss( dateAndHour );
  ss >> get_time(&tm, "%Y-%m-%d  %H:%M:%S");

  chrono::system_clock::time_point then =
	chrono::system_clock::from_time_t(mktime(&tm));
  
  chrono::system_clock::time_point now = chrono::system_clock::now();
  
  return
    chrono::duration_cast<chrono::minutes>(
										   now.time_since_epoch()-
                                           then.time_since_epoch()
										   ).count();
} // ()


// ------------------------------------------------
// ------------------------------------------------
int main () {

  long int min = getMinutesSince1970Until( "1970-01-01 01:01:00" );

  cout << min << endl;


  long int min0 = getMinutesSince1970Until( "2019-01-18 14:23:00" );
  long int min1 = getMinutesSince1970Until( "2019-01-18 14:27:00" );

  if ( (min1 - min0) != 4 ) {
    cout << " something is wrong " << endl;
  } else {
    cout << " it appears to work !" << endl;
  }

  min0 = getMinutesSince( "1970-01-01 01:00:00" );
  min1 = getMinutesSince1970( );

  if ( (min1 - min0) != 0 ) {
    cout << " something is wrong " << endl;
  } else {
    cout << " it appears to work !" << endl;
  }

} // ()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDrew NoakesView Question on Stackoverflow
Solution 1 - C++SimpleView Answer on Stackoverflow
Solution 2 - C++Howard HinnantView Answer on Stackoverflow
Solution 3 - C++user1508519View Answer on Stackoverflow
Solution 4 - C++cibercitizen1View Answer on Stackoverflow