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.)
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With