7

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.)

Community
  • 1
  • 1
Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • It is not at all obvious that the global lock in `tzset` is "terrible for performance" — did you actually benchmark it? Unless you benchamrked it, it appears to be a case of the proverbial premature optimization. As far as I can tell, `tzset` has a fast path that basically does nothing if the time zone hasn't actually changed. In a real-life application I would expect the lock to never be contended. – user4815162342 Oct 11 '13 at 18:05
  • The lock is contended any time two threads call `__tz_convert` at once; check the code. And yes, the only reason we know this lock exists is that we saw it being a bottleneck in perf tests (specific tests that served a lot of localtime requests). – Quuxplusone Oct 11 '13 at 18:32
  • 1
    Have you looked at [Boost](http://www.boost.org/doc/libs/1_54_0/doc/html/date_time.html) or [ICU](http://userguide.icu-project.org/datetime)? – Matt Johnson-Pint Oct 11 '13 at 20:54
  • It looks like [Boost.Date_Time](http://www.boost.org/doc/libs/1_54_0/doc/html/date_time.html) can do what we want, but it looks pretty heavyweight in terms of dependencies. (We're not currently a "Boost shop".) However, if there's no easier solution... Boost is definitely a possibility. – Quuxplusone Oct 11 '13 at 21:31
  • I'm not much of a C++ guru, but I know that both Boost and ICU have IANA TZDB implementations. I also know that ICU is being used by [the v8-i18n project](https://code.google.com/p/v8-i18n/) and several others, so it *might* be lighter weight than Boost, but I'm not really sure. Perhaps someone else more familiar can offer a better answer. – Matt Johnson-Pint Oct 12 '13 at 01:05

2 Answers2

3

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

  • 2
    Generally, links to a tool or library [should be accompanied by usage notes, a specific explanation of how the linked resource is applicable to the problem, or some sample code](http://meta.stackoverflow.com/a/251605), or if possible all of the above. – IKavanagh Nov 24 '15 at 15:26
  • Thank you Greg. That library looks great. There is a global lock in your library too, see https://github.com/google/cctz/blob/master/src/time_zone_impl.cc#L64 . Found by Cloudera as descirbed in comments at https://issues.cloudera.org/browse/IMPALA-3316 . Submitted https://github.com/google/cctz/issues/23 – Tagar Apr 12 '16 at 01:55
1

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577