Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Parsing TimeZone From Date String

Tags:

c++

Hello I have the following date that I am consuming from an api

string sToParse = "2020-04-17T09:30:00-04:00";

which should be in human form "Friday, April 17, 2020 08:30:00" central time or epoch of 1587130200

however this code

cout << "raw: " << sToParse << endl;
static const std::string dateTimeFormat { "%Y-%m-%dT%H:%M:%S%Z" };
istringstream ss{ sToParse };
tm dt;
ss >> get_time(&dt, dateTimeFormat.c_str());
cout << mktime(&dt) << endl;

Gives me an epoch of 1587137400 which is a human format of "Friday, April 17, 2020 10:30:00 AM" which is two hours difference. How do i get the %Z to process the timezone appropriately?

Thanks in advance for any help you can give

like image 635
flashc5 Avatar asked Jun 28 '26 05:06

flashc5


1 Answers

C++20 will do this:

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

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

    string sToParse = "2020-04-17T09:30:00-04:00";
    cout << "raw: " << sToParse << endl;
    static const std::string dateTimeFormat { "%Y-%m-%dT%H:%M:%S%z" };
    istringstream ss{ sToParse };
    sys_seconds dt;
    ss >> parse(dateTimeFormat, dt);
    cout << dt << '\n';
    cout << dt.time_since_epoch() << '\n';
}

Output:

2020-04-17 13:30:00
1587130200s

This gets the "epoch time" you're looking for. The big difference is the use of %z instead of %Z. %z is the command for parsing the offset. %Z parses time zone abbreviation.

This doesn't get the "human time" you're expecting. The time printed out above is UTC. This is clearly correct on inspection: 13:30:00 is 4 hours after 09:30:00.

If you are wanting local time, it would be 2020-04-17 09:30:00, same as the input. To come up with 08:30:00 would require more information than you are providing above (e.g. output in some time zone other than that which has a UTC offset of -4h at this local time).

Also it is in general not possible to go from a UTC offset to a time zone name or abbreviation because more than one time zone will generally have the same UTC offset at any point in time.

If the C++20 <chrono> header isn't available for you (to the best of my knowledge it is not yet available anywhere), you can use a free, open-source preview of C+++20 <chrono>. For this problem, one only needs the header-only "date.h" from this preview. And everything is in namespace date instead of namespace std::chrono. It would look like this:

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

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

    string sToParse = "2020-04-17T09:30:00-04:00";
    cout << "raw: " << sToParse << endl;
    static const std::string dateTimeFormat { "%Y-%m-%dT%H:%M:%S%z" };
    istringstream ss{ sToParse };
    sys_seconds dt;
    ss >> parse(dateTimeFormat, dt);
    cout << dt << '\n';
    cout << dt.time_since_epoch() << '\n';
}

Update

With the new knowledge that the "expected human form" of the time should be US Central Time ("America/Chicago" in IANA terms), I'm updating the example code to show how that can be handled.

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

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

    string sToParse = "2020-04-17T09:30:00-04:00";
    cout << "raw: " << sToParse << endl;
    static const std::string dateTimeFormat { "%FT%T%z" };
    istringstream ss{ sToParse };

    sys_seconds utc;
    ss >> parse(dateTimeFormat, utc);
    zoned_seconds cst{"America/Chicago", utc};

    cout << utc.time_since_epoch() << '\n';
    cout << format("%F %T %Z\n", utc);
    cout << format("%F %T %Z\n", cst);
}

A few changes above:

  • A new header is required to handle the time zone issues: "tz.h" (only in the C++20 preview library, not in C++20).

  • For parsing I've substituted "%Y-%m-%dT%H:%M:%S%z" for "%FT%T%z". These are equivalent. %F is just shorthand for %Y-%m-%d and %T is shorthand for %H:%M:%S. This change is not required.

  • I've renamed dt to utc to emphasize that this variable holds a UTC time point. This change is not required.

  • The new line constructs a zoned_time (with seconds precision) with the desired destination time zone ("America/Chicago"), and the UTC time point. This creates an object that knows all about the local time at this time point and in this time zone.

  • Then everything is printed out using the date::format function and the formatting string "%F %T %Z". %Z is used to output the time zone abbreviation to make the output more readable. In C++20, this will be std::format, and the formatting string will be "{:%F %T %Z}".

The output is now:

raw: 2020-04-17T09:30:00-04:00
1587130200s
2020-04-17 13:30:00 UTC
2020-04-17 08:30:00 CDT

If your computer's current time zone setting happens to be US Central, then the line that constructs zoned_seconds can also look like:

    zoned_seconds cst{current_zone(), utc};

Or conversely, you can use this line to find the local time at utc in any time zone which your computer is set to.

Note that whether the time zone is specified with a name such as "America/Chicago", or with current_zone(), any changes of UTC offset within the specified time zone (such as daylight saving time) will be correctly taken into account.

With the preview C+++20 library, the use of the header "tz.h" is not header-only. It requires a single source file, tz.cpp. Here are instructions on how to compile it. But if you are using a fully conforming C++20 <chrono>, then the above will just work by removing #include "date/tz.h", using namespace date;, and adjusting the formatting string as noted in the 5th bullet above.

like image 74
Howard Hinnant Avatar answered Jun 29 '26 19:06

Howard Hinnant