Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux/crossplatform API for timezone rules? (replace locking localtime_r)

We have some code that wants to call localtime very often from multiple threads. (Relevant background: it's a server where one of the things you can ask it for is the local time as a string, and it wants to be able to serve 100Ks of requests per second.)

We have discovered that on Ubuntu Linux 12.04, the glibc function localtime_r ("reentrant localtime") calls __tz_convert, which still takes a global lock!

(Also, it looks like FreeBSD makes localtime_r call tzset on every single invocation, because they're paranoid that the program might have done a setenv("TZ") and/or the user downloaded a new version of /etc/localtime between now and the last time localtime_r was called. (This is the opposite of the situation described here; it seems that glibc calls tzset on every invocation of localtime but not localtime_r, just to be confusing.)

Obviously, this is terrible for performance. For our purposes, we would like to basically "snapshot" the rules for our current timezone when the server starts running, and then use that snapshot forever afterward. So we'd continue to respect Daylight Savings Time rules (because the rules for when to switch to DST would be part of the snapshot), but we would never go back to the disk, take mutexes, or do anything else that would cause threads to block. (We are fine with not respecting downloaded updates to tzinfo and not respecting changes to /etc/localtime; we don't expect the server to physically change timezones while it's running.)

However, I can't find any information online about how to deal with timezone rules — whether there's a userspace API for working with them, or whether we'll be forced to reimplement a few hundred lines of glibc code to read the timezone data ourselves.

Do we have to reimplement everything downstream of __tz_convert — including tzfile_read, since it doesn't seem to be exposed to users? Or is there some POSIX interface and/or third-party library that we could use for working with timezone rules?

(I've seen http://www.iana.org/time-zones/repository/tz-link.html but I'm not sure it's helpful.)

like image 784
Quuxplusone Avatar asked Oct 11 '13 17:10

Quuxplusone


2 Answers

Use https://github.com/google/cctz

It's fast and should do everything you want with a very simple API.

In particular, for a cctz equivalent to localtime, use the cctz::BreakTime() function. For example, https://github.com/google/cctz/blob/master/examples/example3.cc

like image 78
Greg Miller Avatar answered Oct 01 '22 20:10

Greg Miller


Perhaps this free, open-source timezone library would fit the need.

It has a configuration flag called LAZY_INIT which is fully documented here. By default it is on, and will cause calls to std::call_once the first time each individual timezone is accessed. However you can compile with:

-DLAZY_INIT=0

and then the calls to std::call_once go away. Every single timezone is read from disk and fully initialized on first access (via a function local static). From then on, things are stable and lock-free, with no disk access. Naturally this increases initialization time up front, but decreases the "first-access" time for each timezone.

This library requires C++11/14, and so may not be suitable for that reason. It is based on (and heavily uses) the C++11 <chrono> library. Here is sample code that prints out the current local time:

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

int
main()
{
    using namespace date;
    auto local = make_zoned(current_zone(), std::chrono::system_clock::now());
    std::cout << local << '\n';
}

which just output for me:

2016-04-12 10:13:14.585945 EDT

The library is a modern, high-performance thread safe design. It is also very flexible and fully documented. Its capabilities go far beyond a simple replacement for C's localtime. Unlike the C API, you can specify any IANA timezone you need, for example:

    auto local = make_zoned("Europe/London", std::chrono::system_clock::now());

This gives the current time in London:

2016-04-12 15:19:59.035533 BST

Note that by default the precision of the timestamp is that of std::chrono::system_clock. If you prefer another precision, that is easily accomplished:

using namespace date;
using namespace std::chrono;
auto local = make_zoned("Europe/London", std::chrono::system_clock::now());
std::cout << format("%F %H:%M %Z", local) << '\n';

2016-04-12 15:22 BST

See the docs for more details.

like image 22
Howard Hinnant Avatar answered Oct 01 '22 20:10

Howard Hinnant