63

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.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • 3
    POSIX systems (like Linux or OSX) have a [`strptime`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/strptime.html) that parses a string into a `tm` structure. Unfortunately it doesn't exist for Windows, but [there are alternatives](http://stackoverflow.com/a/321877/440558). – Some programmer dude Jan 09 '14 at 13:34
  • @JoachimPileborg Does it support the `+0000` at the end, though? –  Jan 09 '14 at 13:34
  • @remyabel, actually I was mistaken. That suffix does not exist. I've updated the question. – Drew Noakes Jan 09 '14 at 13:38
  • That's good, because the timezone suffix doesn't seem to be supported. :) – Some programmer dude Jan 09 '14 at 13:40
  • Note that chrono was not designed with calendar functionality in mind, so associating a `time_point` with an actual date is not at the core of its functionality. Boost tried to address this in its [Date Time](http://www.boost.org/doc/libs/1_55_0/doc/html/date_time.html) library which predates Chrono. Unfortunately, those two libraries [don't go together](http://stackoverflow.com/questions/4910373/interoperability-between-boostdate-time-and-stdchrono) as smoothly as one might wish. – ComicSansMS Jan 09 '14 at 13:49

4 Answers4

96
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));
Velkan
  • 7,067
  • 6
  • 43
  • 87
Simple
  • 13,992
  • 2
  • 47
  • 47
  • But *get_time()* is not implemented yet on gcc? – SChepurin Jan 09 '14 at 13:56
  • @SChepurin it might not be, but it's in the C++ spec. – Simple Jan 09 '14 at 13:59
  • 1
    According to the [bug 54354](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54354) at gcc this is solved in gcc 5. Little bit late for a C++11 feature. – jpyllman Feb 20 '15 at 09:21
  • Just in case someone run into the same problem I just had. I needed to `bzero(&tm, sizeof(std::tm))`, otherwise unitialized were used, causing trouble. I'm kinda suprise I had to do this, however. – Xaqq Feb 25 '15 at 13:19
  • 3
    @Xaqq std::tm t{}; // should do – nurettin Oct 15 '15 at 19:33
  • Is it still the case that GCC doesn't implement `std::get_time()`? – einpoklum Jan 10 '17 at 15:58
  • 1
    @einpoklum, should work since gcc 5.0. I've tested on gcc 5.4 (that is in current stable Ubuntu 16.04). – Velkan Jan 12 '17 at 08:17
  • Perfect answer, just to remind there is a bug in Visual Studio that doesn't set fail bit for some invalid inputs to `std::get_time`. See [this question](https://stackoverflow.com/questions/43235953/stdget-time-on-visual-2015-does-not-fail-on-incorrect-date). – vasek Oct 05 '17 at 11:17
  • This might be able to do entirely in chrono starting with C++20: https://en.cppreference.com/w/cpp/chrono/parse – Zoe Jun 22 '20 at 11:54
41

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>.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • The example at milliseconds precision is exactly what I was looking for. Unfortunately, neither gcc 10.2 nor clang 11.0.0 find std::chrono::parse when I precise the --std=c++20 option. Any idea when this would be available? – user1448926 Dec 24 '20 at 11:19
  • No idea. But in the meantime, [date/date.h](https://howardhinnant.github.io/date/date.html) is free, open-source and header-only. – Howard Hinnant Dec 24 '20 at 14:41
  • 1
    @HowardHinnant I like that you point out a nasty limitation with strptime. But it turns out this entire response is a shameless cloaked promotion of this new magical open source library which when you click through surprise happens to be authored by you. Of course user-contributed code is the heart of stack overflow, but cloaking your authorship here put me off. I'd rather have a disclosure, in response to these nasty bugs I wrote some open source that you can find "here". – Bill Gale Sep 24 '21 at 22:23
  • 5
    Sorry @BillGale. I did not mean to **cloak** my authorship. Indeed unlike many others (not yourself) I use my full name both for my Stack Overflow ID, and my github ID. If I were trying to cloak my authorship, one would think I would be a little more clever than that. Additionally, one could interpret adding "And I wrote it!" as bragging. I wish to neither brag nor cloak. And I haven't earned a single buck off of this software, nor am I seeking to. My main goal was seeking field experience for a C++ standards proposal. Mission accomplished. Library standardized in C++20. – Howard Hinnant Sep 25 '21 at 00:19
1

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;
}
1

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;
  }

} // ()
cibercitizen1
  • 20,944
  • 16
  • 72
  • 95