C++: get time zone deviation

Viewed 118

So I want to creata a time stamp (as a string) with the format HH:MM:SS in C++. I use std::chrono to get a unix time stamp and then calculate the hours, minutes and seconds.

// Get unix time stamp in seconds.
const auto unix_time_stamp = std::chrono::system_clock::now();
long long seconds_since_epoch = std::chrono::duration_cast<std::chrono::seconds>(unix_time_stamp.time_since_epoch()).count();

// Calculate current time (hours, minutes, seconds).
uint8_t hours = (seconds_since_epoch % 86400) / 3600;
uint8_t minutes = (seconds_since_epoch % 3600) / 60;
uint8_t seconds = (seconds_since_epoch % 60);

// Create strings for hours, minutes, seconds.
std::string hours_string = std::to_string(hours);
std::string minutes_string = std::to_string(minutes);
std::string seconds_string = std::to_string(seconds);

// Check if the number is only one digit. If it is, add a 0 in the beginning (5:3:9 --> 05:03:09).
if(hours_string.size() == 1)
{
  hours_string = "0" + hours_string;
}
if(minutes_string.size() == 1)
{
  minutes_string = "0" + minutes_string;
}
if(seconds_string.size() == 1)
{
  seconds_string = "0" + seconds_string;
}

// Append to a final string.
std::string time_stamp = hours_string + ":" + minutes_string + ":" + seconds_string;

This is all working fine and great but there is one big problem: time zones. With this way, I'm only calculating the time stamp for GMT. Is there any easy, fast and, most importantly, portable way to get the "offset" in seconds or minutes or hours for your system's time zone? By "portable" I mean platform-independent.

Please note: I know you can do all of this more easily with std::strftime and so on, but I really want to implement this by myself.

1 Answers

Some implementations of std::tm will contain a member that has the local offset as a member. ... But it isn't portable.

One trick is to take your seconds_since_epoch, and either assign it to a std::time_t, or just make its type std::time_t in the first place instead of long long.

... Oh, wait that isn't quite portable. Some platforms still use a 32 bit time_t. But assuming a 64 bit time_t ...

Then use localtime to get a std::tm:

std::tm tm = *localtime(&seconds_since_epoch);

This isn't officially portable because system_clock and time_t aren't guaranteed to have the same epoch. But in practice they do.

Now take the {year, month, day, hour, minute, second} fields out of the tm and compute a "local epoch". The hard part of this computation is converting the {year, month, day} part into a count of days. You can use days_from_civil from here to do that computation efficiently. Be sure to take the weird offsets into account for tm_year and tm_mon when doing this.

After you get this then subtract seconds_since_epoch from it:

 auto offset = local_epoch - seconds_since_epoch;

This is your signed UTC offset in seconds. Positive is east of the prime meridian.


In C++20 this simplifies down to:

auto offset = std::chrono::current_zone()->get_info(system_clock::now()).offset;

and offset will have type std::chrono::seconds.

You can get a free, open-source preview of this here. It does require some installation.

Related