How can I convert a given UTC timestamp into a local time? (With the correct DST timezone)

On the other systems I code for (win, mac, linux) there is a database of timezone information that I can query to figure out the timezone in effect at a particular point in history. I can use that to convert a historical date to it’s correct local time, even if the prevailing timezone is different to the current timezone (ie the historical date was during daylight savings and it’s currently not in DST).

What tools does Haiku give me to do that? Or do I need some third party library to get the DST info I need?

I’ve had a look at BTimeZone and it seems super basic. No tools for local<->UTC conversion. Or even when the DST/non-DST changes happen each year.

Fyi the code I have for other OSs is LDateTime::GetDaylightSavingsInfo.

Not sure if it is enough for what you need, but pkgman install timezone_data should install:


and the corresponding timezone data under: under /boot/system/data/zoneinfo.

(reminds me I need to tweak our Python packages to require and make use of that too).

No idea about the BeAPI side of things, sorry.

1 Like

Is what I use on Linux but I think it’s installed by default on most distros.

And in trying it on Haiku I found that there is no “/etc/localtime” to pass to zdump. Normally I would use this command:

zdump -v /etc/localtime

On Linux to get all the DST info. But without the localtime file… zdump isn’t much use to me.

This is the sort of output I’m expecting:

nuc:~$ zdump -v /etc/localtime | grep 2023
/etc/localtime  Sat Apr  1 15:59:59 2023 UT = Sun Apr  2 02:59:59 2023 AEDT isdst=1 gmtoff=39600
/etc/localtime  Sat Apr  1 16:00:00 2023 UT = Sun Apr  2 02:00:00 2023 AEST isdst=0 gmtoff=36000
/etc/localtime  Sat Sep 30 15:59:59 2023 UT = Sun Oct  1 01:59:59 2023 AEST isdst=0 gmtoff=36000
/etc/localtime  Sat Sep 30 16:00:00 2023 UT = Sun Oct  1 03:00:00 2023 AEDT isdst=1 gmtoff=39600

Haahahaha… ok I think I’ve figured it out:

nuc:~$ ls -l /etc/localtime
lrwxrwxrwx 1 root root 36 Jun  5 11:02 /etc/localtime -> /usr/share/zoneinfo/Australia/Sydney

I need to map it to something in the zoneinfo…

> tzselect
/bin/tzselect: line 174: /usr/share/zoneinfo/ No such file or directory
/bin/tzselect: time zone files are not set up correctly

It obviously doesn’t know about the non-standard Haiku zone info location.

But even if I try and do it manually:

ln -s /boot/system/data/zoneinfo/Australia/Sydney /etc/localtime

I don’t get the right output from zdump:

> zdump -v /etc/localtime
/etc/localtime: No error

No error… but no DST info either.

I’ll try to patch it while updating the timezone_data recipe (latest version at 2023c, we’re at 2022f).

My skills/knowledge is pretty limited in most things, so… hopefully you’ll get a better answer from someone that knows what he’s talking about :smiley:.

It’s possible the BTimeZone class is missing a method. @pulkomandy is the one who refactored it to use ICU all those years ago, maybe he can suggest what modifications should be done.

The timezone data is packaged inside ICU, it is sad to need a second copy of it in a separate package. Supporting Linux applications without changes is making Haiku more bloated and full of legacy things than it could be. Oh well.

I think I only implemented just what was needed to get the Locale and Time preferences working.

I don’t know what you need exactly. The way I think about time in UNIX systems is that timestamps are always a number of seconds since january 1st 1970 in UTC. So, you can use the localtime function to convert such a timestamp into a local date representation. Or for converting to a string you could use BDateTimeFormat: DateTimeFormat.h « locale « os « headers - haiku - Haiku's main repository

It looks like BDate supports creating a BDate object from a time_t timestamp and either the UTC or a local timezone, but not any other arbitrary timezone: DateTime.h « support « os « headers - haiku - Haiku's main repository and that is completely missing from BTime and BDateTime.

So your only hope is manually applying the OffsetFromGMT() from the BTimeZone object at the moment :frowning:

These APIs need to be filled in…

The tcl package also contains timezone data too (under lib/tcl8.6/tzdata). I’ve tried first to make Python use that, but TCL’s data is in plain-text (not “zic compiled” as on the timezone_data package, that’s what Python expects, as I found out later). [1]

Then I thought of just adding a “python-package” recipe for tzdata, but then noticed that timezone_data already was on depot (and supposedly of wider appeal/usefullness than just Python’s tzdata).

Seems there’s a PyICU extension what we could use to leaverage the system-wide ICU libs/data, but while useful on its own merit, doesn’t seems to be a way to make Python Standard Library use that instead of “zic-compiled tzdb/zoneinfo”. [2]

[1] Someone volunteers to investigate if it’s possible to make tcl use timezone_data instead of packaging its own files? Edit: tcl’s tzdata files are just IANA/ICANN files parsed via tools/tclZIC.tcl, so no chance of centralizing that one, it seems.

[2] If someone finds a way… or another alternative, let me know. Albeit, to note:

Python considered and discarded the idea of using the Windows provided ICU API, due to the lack of functions that provided direct access to the underlying time zone data. So, even if Haiku’s ICU integration provides such direct access, we can’t just use it with Python without some serious patching.

Last edit: For better or worse,IANA/ICANN’s tzdb/zoneinfo seems to be widely used in one way or another. To try and keep only one central copy seems like the best we can realistically hope?

So I wrote a parser this afternoon. It works well enough for my timezone. But it’s probably not well enough tested for others. Unfortunately the timezone_data files aren’t installed by default. But at least I don’t have to rely on the broken linux libs to try and read it anymore.

Still if a better solution comes along I’m all ears.

I guess it would be kinda funny if: struct tm *localtime(const time_t *timep); from time.h actually does what I want.

I think it does?

timegm and gmtime to convert time_t to/from struct tm in UTC (no timezone offset is applied)
mktime and localtime to convert time_t to/from local timezone

Just in case, I’ve open a PR for the updated timezone_data, which fixes part of the issues with tzselect/zdump (the latter seems to work OK at least).

So… yes localtime_r does what I want. It gives me the right offset from UTC for historical dates. And the extra little trick once you have that struct tm *t to get the timezone in effect is:

auto timezone = timegm(t) - mktime(t)

That’s in seconds… divide by whatever for minutes and hours.

That said having good access to when the daylight savings changes is still needed elsewhere in the code base, so it’s not entirely wasted effort. Mainly for the calendar component of Scribe. And more specifically in resolving recurring events that overlap different DST time zones.