How to Parse a Date String into a C++11 Std::Chrono Time_Point or Similar

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

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

Create std::chrono::time_point from string

Just to be clear, there is no namespace date in C++20. So the code in the question should look like:

#include <chrono>
#include <iostream>

int
main()
{
std::cout << std::chrono::system_clock::now() << '\n';
}

The inverse of this is std::chrono::parse which operates on streams. You can also use std::chrono::from_stream if desired. parse is a stream manipulator that makes the syntax a little nicer.

istringstream in{"2017-09-15 13:11:34.356648"};
system_clock::time_point tp;
in >> parse("%F %T", tp);

(I've dropped the namespaces just to keep the verbosity down)

The locale used is the global locale in effect at the time the istringstream is constructed. If you prefer another locale use the imbue member function to set the desired locale. The locale will only impact the decimal point character in this example.

The %T will read up to whatever precision the input time_point has (which varies with platform from microseconds to nanoseconds). If you want to be sure you can parse nanoseconds even if system_clock::time_point is coarser than that, then you can parse into a sys_time<nanoseconds> which is a type alias for time_point<system_clock, nanoseconds>.

sys_time<nanoseconds> tp;
in >> parse("%F %T", tp);

If the input stream has precision less than the input time_point, there is no problem. What the stream has will be read, and no more. If the input stream has precision finer than the input time_point, then the parse stops at the precision of the time_point, and the remaining digits are left unparsed in the stream.

Other strptime-like parsing flags are supported.

How to convert std::chrono::time_point to string

Howard Hinnant's free, open source, header-only, portable date/time library is a modern way to do this that doesn't traffic through the old C API, and doesn't require that you discard all of your sub-second information. This library is also being proposed for standardization.

There is a lot of flexibility in formatting. The easiest way is to just stream out:

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

int
main()
{
using namespace date;
std::cout << std::chrono::system_clock::now() << '\n';
}

This just output for me:

2017-09-15 13:11:34.356648

The using namespace date; is required in order to find the streaming operator for the system_clock::time_point (it isn't legal for my lib to insert it into namespace std::chrono). No information is lost in this format: the full precision of your system_clock::time_point will be output (microseconds where I ran this on macOS).

The full suite of strftime-like formatting flags is available for other formats, with minor extensions to handle things like fractional seconds. Here is another example that outputs with millisecond precision:

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

int
main()
{
using namespace date;
using namespace std::chrono;
std::cout << format("%D %T %Z\n", floor<milliseconds>(system_clock::now()));
}

which just output for me:

09/15/17 13:17:40.466 UTC

Creating a `std::chrono::time_point` from a calendar date known at compile time

Yes, you can do the entire computation at compile time, creating a constexpr system_clock::time_point using Howard Hinnant's date/time library.

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

int
main()
{
using namespace date;
using namespace std::chrono;
constexpr system_clock::time_point tp = sys_days{January/9/2014} + 12h + 35min + 34s;
static_assert(tp == system_clock::time_point{1389270934s}, "");
}

This is assuming that the date/time is UTC. If it isn't, you will have to manually add/subtract the UTC offset to make it so. As time zone rules are changed at the whim of politicians all the time, there is little hope in making them constexpr. Even historical time zone rules are updated when misunderstandings come to light.

Also this program will port to C++20 by dropping #include "date/date.h" and using namespace date;. Also using Howard Hinnant's date/time library requires C++14 constexpr muscle. C++11 constexpr is not sufficient (but you can do it at run-time, dropping the constexpr and static_assert).

chrono parse including timezone

Unfortunately there is no way to reliably and uniquely identify a time zone given just a time zone abbreviation. Some abbreviations are used by multiple time zones, sometimes even with different UTC offsets.

So in short, the time zone abbreviation is parsed, but does not identify a UTC offset which can be used to alter the parsed timestamp.

See Convert a time zone abbreviation into a time zone for code that attempts to at least narrow down which timezones are using a specific time zone abbreviation at one time.

Alternatively, if you a parse a UTC offset ("%z" or "%Ez"), then that offset will be applied to the timestamp to convert it to a sys_time.


Fwiw, I ran each of these three examples through the find_by_abbrev overload taking local_time described here. The results are interesting in that they likely confirm the fragility of parsing time zone abbreviations:

"Fri, 25 Sep 2020 13:44:43 EST"

Could be any of these time zones:

2020-09-25 13:44:43 EST 2020-09-25 18:44:43 UTC America/Atikokan 
2020-09-25 13:44:43 EST 2020-09-25 18:44:43 UTC America/Cancun
2020-09-25 13:44:43 EST 2020-09-25 18:44:43 UTC America/Jamaica
2020-09-25 13:44:43 EST 2020-09-25 18:44:43 UTC America/Panama
2020-09-25 13:44:43 EST 2020-09-25 18:44:43 UTC EST

All of these have a UTC offset of -5h. So in that sense, the UTC equivalent is unique (2020-09-25 18:44:43 UTC as shown above). However one has to wonder if America/Montreal was actually intended, which has a UTC offset of -4h and an abbreviation of EDT on this date.

"Fri, 25 Sep 2020 13:44:43 PST"

Has only one match!

2020-09-25 13:44:43 PST 2020-09-25 05:44:43 UTC Asia/Manila

This has a UTC offset of 8h. But I have to wonder if America/Vancouver was intended, which has a UTC offset of -7h and an abbreviation of PDT on this date.

If one knows the matching UTC offset for the abbreviations one will be parsing, one could parse into a local_time, parse the abbreviation, look up the UTC offset, and apply it to transform the local_time into a sys_time. This library makes it easy to parse the abbreviation along with the timestamp:

local_seconds tpEst;
std::string abbrev;
inEst >> date::parse("%a, %d %b %Y %T %Z", tpEst, abbrev);
sys_seconds tpUTC{tpEst - local_seconds{} - get_offset(abbrev)};

where get_offset(abbrev) is a custom map you've written to return offsets given a time zone abbreviation. Note that this wouldn't help if (for example) EDT (-4h) was intended but EST (-5h) was parsed.

Another possible strategy is to write a map of abbreviations to time zone names (instead of to offsets). For example: Both "EST" and "EDT" could map to "America/Toronto", and then you could do:

local_seconds tpEst;
std::string abbrev;
inEst >> date::parse("%a, %d %b %Y %T %Z", tpEst, abbrev);
zoned_seconds zt{get_tz_name(abbrev), tpEst};
sys_seconds tpUTC = zt.get_sys_time();

Chrono, c++, comparing dates

First of all, pc and pl types are types char* and long*. If you want to print the full type name using typeid pipe your output to c++filt, something like ./prog | c++filt --types.
To compare these two dates, you should convert std::string to time_t. For that use tm structure. To convert string to time use strptime() function from time.h header. After that create time_point value using from_time_t() and mktime(). And at the end convert time_point_t type to time_t with to_time_t() function.

Your code should be something like this:

  auto end = chrono::system_clock::now();
string date_to_do_something = "Mon Jul 27 17:13:17 2021";
time_t end_time = chrono::system_clock::to_time_t(end);
// gives some weird types:pc, pl
cout << typeid(ctime(&end_time)).name() << endl;
cout << typeid(&end_time).name() << endl;
// Now how to compare?
tm t = tm{};
strptime(date_to_do_something.c_str(), "%a %b %d %H:%M:%S %Y", &t);
chrono::system_clock::time_point tp =
chrono::system_clock::from_time_t(mktime(&t));
time_t time = chrono::system_clock::to_time_t(tp);
if (time == end_time) {
// do something
} else {
// do something else
}

Convert a string with micro second precision to a chrono time_point

With just a time, you've got a couple of options. But you should definitely not put "just a time" into a system_clock::time_point. The reason for this is that system_clock::time_point has a specific meaning: This measures the time since (or before) 1970-01-01 00:00:00 UTC, excluding leap seconds.

To be fair, this meaning is absent in C++11/14/17, and is only specified in C++20. However all implementations followed this convention in C++11/14/17.

You could store "time of day" in a duration such as microseconds. Then in your code you simply interpret this duration as time elapsed since midnight, where you get to define "midnight" (local midnight? UTC? etc.).

std::chrono::microseconds
makeTime(std::string s)
{
using namespace std;
using namespace std::chrono;
istringstream in{move(s)};
microseconds tp;
in >> date::parse("%T", tp);
return tp;
}

The above solution is simple, and that is good. But it is possible that this "time of day" could be accidentally mixed up with some other duration that doesn't mean "time of day" in a way that would cause logic errors in your program.

To make the above more type-safe, you could create a "time of day" clock:

struct time_of_day
{
using duration = std::chrono::microseconds;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<time_of_day>;
static constexpr bool is_steady = false;

static
time_point
now()
{
using namespace date;
using namespace std::chrono;
auto t_sys = floor<microseconds>(system_clock::now());
return time_point{t_sys - floor<days>(t_sys)};
};
};

time_of_day::time_point
makeTime(std::string s)
{
using namespace std;
using namespace std::chrono;
istringstream in{move(s)};
microseconds d;
in >> date::parse("%T", d);
return time_of_day::time_point{d};
}

Above I've defined midnight as UTC just to simplify the demonstration.

The time_of_day solution isn't necessarily better. The programmer should decide if the extra complexity cost justifies the type safety benefit.

Weekday from date string in C++11

You can use std:get_time() to translate such a string to a std::tm.

The following program is a modified version of the program published at http://en.cppreference.com/w/cpp/locale/time_get.

The valid format specifiers for std::get_time() can be seen at
http://en.cppreference.com/w/cpp/io/manip/get_time.

#include <iostream>
#include <sstream>
#include <string>
#include <locale>
#include <ctime>
#include <iomanip>

int main()
{
std::string input = "2015-11-27";
std::tm t = {};
std::istringstream ss(input);
ss >> std::get_time(&t, "%Y-%m-%d");
std::mktime(&t);
std::cout << std::asctime(&t);
}

See it working at http://ideone.com/xAEjsr.



Related Topics



Leave a reply



Submit