1

In Python or Java you can get the UTC offset (at present time) given the IANA name of the timezone ("America/Los_Angeles" for instance). See Get UTC offset from time zone name in python for example.

How can you do the same using C/C++ on Ubuntu 14.04?

EDIT: Preferably in a thread-safe way (no environment variables).

Community
  • 1
  • 1
  • Thread-safe! You don't ask for much, do you? :-) Unfortunately the standard C/Unix functions are dreadfully thread-unsafe, because the defined way to ask for a different timezone is to, yes, set the `TZ` environment variable. There are BSD extensions `tzalloc` and `localtime_tz` which fix this, but for some reason they've never gained much traction. See my updated answer for some info. – Steve Summit Nov 06 '16 at 09:27
  • For the life of me I cannot understand why are those functions (or similar) not in a C++ standard. Both Python and Java have them as one-liners. So does boost. We live in a global world and global-local time calculation is a bread and butter. Is that really too much to ask for? – Andrey Savov Nov 06 '16 at 17:07

3 Answers3

2

You alluded to this fact, but it's important to note that the offset between UTC and the time in a time zone is not necessarily constant. If the time zone performs daylight saving (summer) time adjustments, the offset will vary depending on the time of year.

One way to find the offset is to take the time you're interested in, hand it to the localtime() function, then look at the tm_gmtoff field. Do this with the TZ environment variable set to the zone name you're interested in. Here's an example that does so for the current time:

#include <time.h>
#include <stdio.h>

int main()
{
    setenv("TZ", "America/Los_Angeles", 1);
    time_t t = time(NULL);
    struct tm *tmp = localtime(&t);
    printf("%ld\n", tmp->tm_gmtoff);
}

At the moment this prints -25200, indicating that Los Angeles is 25200 seconds, or 420 minutes, or 7 hours west of Greenwich. But next week (actually tomorrow) the U.S goes off of DST, at which point this code will start printing -28800.

This isn't guaranteed to work, since the tm_gmtoff field is not portable. But I believe all versions of Linux will have it. (You might have to compile with -D_BSD_SOURCE or something, or refer to the field as __tm_gmtoff. But in my experience it tends to work by default, as plain tm_gmtoff.)

The other way is to go back and forth with gmtime and mktime, as described in Sam Varshavchik's answer.


Addendum: You asked about not using environment variables. There is a way, but unfortunately it's even less standard. There are BSD functions tzalloc and localtime_rz which do the job, but they do not exist on Linux. Here's how the code looks:

    timezone_t tz = tzalloc("America/Los_Angeles");
    if(tz == NULL) return 1;
    time_t t = time(NULL);
    struct tm tm, *tmp = localtime_rz(tz, &t, &tm);
    printf("%ld\n", tmp->tm_gmtoff);

For me this prints -28800 (because PDT fell back to PST just a few minutes ago).

If you had it, you could also use localtime_rz along with mktime in Sam Varshavchik's answer. And of course Howard Hinnant's library is pretty obviously thread-safe, not requiring mucking with TZ at all.

EDIT (OP): The code for localtime_rz and tzalloc can be downloaded from https://www.iana.org/time-zones and works on Ubuntu 14.04.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • `tm_gmtoff` is defined only on BSD and System V. The question is about Linux. There is no `tm_gmtoff` in Linux. – Sam Varshavchik Nov 05 '16 at 22:17
  • @SamVarshavchik Every version of Linux I've used, including the Ubuntu system where I just compiled and tested the code I posted, has it. – Steve Summit Nov 05 '16 at 22:30
  • 1
    @SamVarshavchik Read down to the Notes section. – Steve Summit Nov 05 '16 at 22:38
  • Was just about to post the same. On the _actual_ manpage on my 16.04 box it clearly lists the two fields `tm_gmtoff` and `tm_zone` as being a glibc extension present if `_BSD_SOURCE` is set. – Dirk Eddelbuettel Nov 05 '16 at 22:40
  • They're massively useful fields. You can't do serious date/time programming without them. Every system *should* (IMHO) have them. But, alas, they're still not Standard. And if you don't have `tm_gmtoff`, or if you don't want to risk nonportability by using it, you can also use Sam's answer, which is a fine technique which I've used, too. – Steve Summit Nov 05 '16 at 22:48
  • Is there a way to not use environment variables? I should have mentioned that. – Andrey Savov Nov 06 '16 at 04:20
  • @SteveSummit: worth mentioning that the code for those functions can be downloaded from IANA itself: https://www.iana.org/time-zones which can be built for any POSIX (definitely Ubuntu). – Andrey Savov Nov 06 '16 at 16:58
2

You could use this free open source C++11/14 library to do it like this:

#include "chrono_io.h"
#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono;
    auto zt = make_zoned("America/Los_Angeles", system_clock::now());
    std::cout << zt.get_info().offset << '\n';
}

This currently outputs:

-25200s

Or you could format it differently like this:

    std::cout << make_time(zt.get_info().offset) << '\n';

which currently outputs:

-07:00:00

The factory function make_zoned creates a zoned_time using the IANA name "America/Los_Angeles" and the current time from std::chrono::system_clock. A zoned_time has a member getter to get the information about the timezone at that time. The information is a type called sys_info which contains all kinds of useful information, including the current UTC offset.

The UTC offset is stored as a std::chrono::seconds. The header "chrono_io.h" will format durations for streaming. Or the make_time utility can be used to format the duration into hh:mm:ss.

The program above is thread-safe. You don't have to worry about some other process changing TZ out from under you, or changing the current time zone of the computer in any other way. If you want information about the current time zone, that is available too, just use current_zone() in place of "America/Los_Angeles".

If you wanted to explore other times, that is just as easy. For example beginning at Nov/6/2016 at 2am local time:

    auto zt = make_zoned("America/Los_Angeles", local_days{nov/6/2016} + 2h);

The output changes to:

-28800s
-08:00:00

More information about this library was presented at Cppcon 2016 and can be viewed here:

https://www.youtube.com/watch?v=Vwd3pduVGKY

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Is it possible to use the _already installed_ IANA database files on Ubuntu? This seems to require curl to pull from IANA etc. What if I don't have a network? Also: ` ubuntu@ip-128-10-6-241:~/hhdate$ g++ -std=c++11 gmtoff.c date/tz.cpp -I date -L /usr/lib/x86_64-linux-gnu -lcurl ubuntu@ip-128-10-6-241:~/hhdate$ ./a.out terminate called after throwing an instance of 'std::runtime_error' what(): Timezone database version "2016i" did not install correctly to "/home/ubuntu/Downloads/tzdata" Aborted (core dumped) ` – Andrey Savov Nov 06 '16 at 05:34
  • I'm not familiar with Howard's library, but it certainly ought to be possible to use it with the already-installed tzdata files. – Steve Summit Nov 06 '16 at 09:17
  • @AndreySavov: See https://howardhinnant.github.io/date/tz.html#Installation for how to configure the installation to not require curl (`-DHAS_REMOTE_API=0`). In this case you will need to install the IANA database yourself. The link is given in the installation instructions. You want the "data only" file. You can customize where you want to install it with `INSTALL` as described in the instructions. – Howard Hinnant Nov 06 '16 at 14:32
  • @HowardHinnant: I am not seeing how to configure your code to take anything other than the IANA gzip file. Linux has the DBs installed already under `/usr/share` and I would like to use that instead. – Andrey Savov Nov 06 '16 at 16:51
  • @AndreySavov: Sorry, this library can't do that at this time. The IANA database will consume about 945Kb of disk space and will be as up-to-date as you keep it. – Howard Hinnant Nov 06 '16 at 17:19
1

Use gmtime(), first, to convert the current epoch time into UTC time, then use mktime() to recalculate the epoch time, then compare the result to the real epoch time.

gmtime() calculates the struct tm in UTC, while mktime() assumes that the struct tm represents the current local calendar time. So, by making this round-about calculation, you indirectly figure out the current timezone offset.

Note that mktime() can return an error if struct tm cannot be convert to epoch time, which will happen during certain transitions between standard time and alternate time. It's up to you to figure out what that means, in your case.

The recipe looks something like this:

time_t t = time(NULL);
struct tm *tmp = gmtime(&t);
time_t t2 = mktime(tmp);
int offset = t - t2;

See the documentation of these library functions for more information.

To use a specific time zone, either set the TZ environment variable, or you can try using localtime_rz as in Steve Summit's answer. As mentioned, beware that mktime can sometimes return -1 for unconvertible times.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148