Jonathan M Davis
Jonathan M Davis

Reputation: 38307

How do you get info for an arbitrary time zone in Linux / POSIX?

Ideally, what I'd like to be able to do is take the name of a time zone and call a function to ask for its corresponding time zone info (offset from UTC, DST offset, dates for DST switch, etc.) in Linux. However, I can't find any way to do this. The information exists in /usr/share/zoneinfo/ in the various binary files there, but I don't know how to read them or if there is a way to get the OS to give you the information in them rather than having to read them in yourself. So, I'm looking for a way to get this information. Ideally, there would be a posix function which would do it for you so that it would work on POSIX systems other than Linux, but if there isn't, I'd at least like to know the proper way (or at least the best way) to get the time zone info for an arbitrary time zone in Linux.

Upvotes: 3

Views: 2705

Answers (3)

Howard Hinnant
Howard Hinnant

Reputation: 219578

New answer for an old question.

Rationale for new answer: There is now a modern, free, open-source, C++11/14/17 library to do this.1 It does require some installation. But it is portable across Linux/macOS/Windows. And it has full documentation, and even a video introduction.

Here is an example program to get information about a specific time zone. I'm using my own timezone just as an example. This library supports the full IANA timezone database:

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

int
main()
{
    auto zone = date::locate_zone("America/New_York");
    std::cout << *zone << '\n';
    std::cout << zone->get_info(std::chrono::system_clock::now()) << '\n';
}

The first line looks up a database by its IANA name.

auto zone = date::locate_zone("America/New_York");

The return type is a date::time_zone const*. This function can never return nullptr, though it will throw if it can't locate the time zone (with an excellent what()).

The second line prints out the definition of the time zone:

std::cout << *zone << '\n';

The output of this line is not generally useful to clients of this library. It is mainly useful to myself in debugging the library:

America/New_York                   -04:56:02                  LMT        1883 Nov/18                  12:03:58         1883-11-18 17:00:00 UTC   1883-11-18 12:03:58 STD   1883-11-18 12:03:58   00:00      {nullptr, -32768}   {nullptr, 32767}
                                   -05:00:00   US             E%sT       1920 Jan/01                  00:00:00         1920-01-01 05:00:00 UTC   1920-01-01 00:00:00 STD   1920-01-01 00:00:00   00:00   S   {US             1918    1919    Mar/Sun[last]           02:00:00       01:00   D, 1918}   {US             1918    1919    Oct/Sun[last]           02:00:00       00:00   S, 1919}
                                   -05:00:00   NYC            E%sT       1942 Jan/01                  00:00:00         1942-01-01 05:00:00 UTC   1942-01-01 00:00:00 STD   1942-01-01 00:00:00   00:00   S   {NYC            1920    1920    Mar/28                  02:00:00       01:00   D, 1920}   {NYC            1921    1954    Sep/Sun[last]           02:00:00       00:00   S, 1941}
                                   -05:00:00   US             E%sT       1946 Jan/01                  00:00:00         1946-01-01 05:00:00 UTC   1946-01-01 00:00:00 STD   1946-01-01 00:00:00   00:00   S   {US             1942    1942    Feb/09                  02:00:00       01:00   W, 1942}   {US             1945    1945    Sep/30                  02:00:00       00:00   S, 1945}
                                   -05:00:00   NYC            E%sT       1967 Jan/01                  00:00:00         1967-01-01 05:00:00 UTC   1967-01-01 00:00:00 STD   1967-01-01 00:00:00   00:00   S   {NYC            1921    1954    Apr/Sun[last]           02:00:00       01:00   D, 1946}   {NYC            1955    1966    Oct/Sun[last]           02:00:00       00:00   S, 1966}
                                   -05:00:00   US             E%sT       32767 Dec/31                  00:00:00UTC      32767-12-31 00:00:00 UTC   32767-12-30 19:00:00 STD   32767-12-30 19:00:00   00:00   S   {US             1967    1973    Apr/Sun[last]           02:00:00       01:00   D, 1967}   {US             2007    32767    Nov/Sun[1]              02:00:00       00:00   S, 32767}

But one of the reasons for me showing this line is to illustrate that asking for information about a time zone, without also supplying a time point, is not likely to give you the information you're looking for. The information about a timezone is itself a function of time, including the offsets, daylight saving details, abbreviations, etc.

The final line:

std::cout << zone->get_info(std::chrono::system_clock::now()) << '\n';

is likely to be most helpful. This returns an aggregate sys_info that looks like this:

struct sys_info
{
    sys_seconds          begin;
    sys_seconds          end;
    std::chrono::seconds offset;
    std::chrono::minutes save;
    std::string          abbrev;
};

This just output for me:

2016-11-06 06:00:00
2017-03-12 07:00:00
-05:00:00
00:00
EST

Which means:

  • This information is valid from 2016-11-06 06:00:00 UTC up until (but not including) 2017-03-12 07:00:00 UTC.
  • The UTC offset for this time period is -5 hours.
  • This is not considered a daylight saving period (save == 00:00).
  • The abbreviation for this period is EST.

Of course you can access the fields of this aggregate in your program as opposed to just printing them out.

If you want to see what this result might look like 6 months from now:

std::cout << zone->get_info(std::chrono::system_clock::now() + date::months{6}) << '\n';

which currently outputs:

2017-03-12 07:00:00
2017-11-05 06:00:00
-04:00:00
01:00
EDT

This is all considered to be low-level access in this library. There exists higher-level API so that you don't have to deal with low level concepts such as the current UTC offset. The low-level stuff is there if you need it (not hidden away), but not necessary for common use cases such as getting the current time in any specific time zone:

using namespace date;
using namespace std::chrono;
std::cout << make_zoned("America/New_York", system_clock::now()) << '\n';

which just output for me:

2017-03-07 19:26:53.711662 EST

In C++17, due to template deduction guides, the above line will no longer need "make factory functions" for deduction purposes:

std::cout << zoned_time{"America/New_York", system_clock::now()} << '\n';

zoned_time is a class template, templated on duration, and deduced by the chrono::duration of the chrono::time_point (the second argument -- in my case microseconds).

This is a full-featured date/time/timezone library with both low-level access, and high-level abstractions (true to the philosophy of C++). Correctness and type-safety are highly valued in this library. It is an extension, not a replacement, of the <chrono> library introduced in C++11.


1Disclaimer: I am the principal author of this library, though there are many contributors (to which I am grateful).

Upvotes: 1

caf
caf

Reputation: 239371

The tzcode package (found along with the data at ftp://ftp.iana.org/tz/releases) contains a description of the tzfile format along with a header file tzfile.h for working directly with that data.

Upvotes: 3

Jonathan Leffler
Jonathan Leffler

Reputation: 755094

There isn't a standard way to do that. You could look at ICU. It claims:

  • Formatting: Format numbers, dates, times and currency amounts according the conventions of a chosen locale. This includes translating month and day names into the selected language, choosing appropriate abbreviations, ordering fields correctly, etc. This data also comes from the Common Locale Data Repository.

  • Time Calculations: Multiple types of calendars are provided beyond the traditional Gregorian calendar. A thorough set of timezone calculation APIs are provided (emphasis added).

Upvotes: 1

Related Questions