Home > Software design >  Problem parsing date/time with timezone name using Howard Hinnant's library
Problem parsing date/time with timezone name using Howard Hinnant's library

Time:03-23

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

  • Related