I'm writing a method to parse date/time strings in a variety of formats.
std::chrono::system_clock::time_point toTimePoint(const std::string str) {
... a bunch of code that determines the format of the input string
std::string formatStr = string{"%Y-%m-%d"}
" " // Delimeter between date and time.
"%H:%M:%S"
"%t%Z"
;
// The %t should be 0 or 1 whitespace
// The %Z should be a timezone name
std::chrono::system_clock::time_point retVal;
std::istringstream in{str};
in >> date::parse(formatStr, retVal);
return retVal;
}
I then test it with a variety of inputs. The other formats work. I can do these:
2022-04-01 12:17:00.1234
2022-04-01 12:17:00.1234-0600
2022-04-01 12:17:00.1234-06:00
The latter two are for US Mountain Daylight Time. It does all the right things. The first one shows as 12:17:00 UST. The other two are 18:17:00 UST. Working great. I've omitted all that code for brevity. What does not work is this:
2022-04-01 12:17:00.1234 US/Central
I've tried a variety of timezone names after writing a different program to dump the ones known by Howard's library. None of them matter. I get a UST-time value with no time zone offset.
Luckily, what I need right now is the -06:00 format, so I can move forward. But I'd like to fix the code, as we have other places that use timezone names, and I'd like to get this working properly.
I'm not sure what I'm doing wrong.
CodePudding user response:
When reading an offset with %z
(e.g. -0600
), combined with a sys_time
type such as system_clock::time_point
, the parse time point is interpreted as a local time, and the offset is applied to get the sys_time
, as desired in your first two examples.
However this is not the case when reading a time zone name or abbreviation with %Z
(note the change from lower case z to upper case Z).
%Z
parses a time zone abbreviation or name, which is just a string. The common case is for this to just parse an abbreviation, e.g. CST. And in general, there is no unique mapping from an abbreviation to an offset. And so the offset can not be internally applied. Thus the parsed value should always be interpreted as a local time.
However all is not lost. You can parse the time zone name with %Z
into a string, and then look up the time_zone
with that name and use it to convert the parse local_time
into a sys_time
. This could look like:
#include "date/tz.h"
#include <chrono>
#include <iostream>
#include <sstream>
int
main()
{
using namespace date;
using namespace std;
using namespace std::chrono;
istringstream in{"2022-04-01 12:17:00.1234 US/Central"};
string tz_name;
local_time<microseconds> local_tp;
in >> parse("%F %T%t%Z", local_tp, tz_name);
system_clock::time_point tp = locate_zone(tz_name)->to_sys(local_tp);
cout << tp << '\n';
}
Just add a string
as the third argument in your parse
call, and make sure the first argument is a local_time
instead of a sys_time
. Then use locate_zone
to get a time_zone const*
and call to_sys
with that, passing in the parsed local_time
.
The above program outputs:
2022-04-01 17:17:00.123400
This is an hour off from the -6h offset because US/Central goes to daylight saving on 2022-03-13 (-5h offset).