Time Zones, tz Database, and Linux
If you are involved in developing or testing a global product, sooner or later you’ll stumble upon time zones. I did some testing with time zones last week and then I looked a bit more into the topic, including the tz database. I found it an interesting topic, so let’s summarise some of my findings.
To become a bit more efficient, let’s define two aliases first:
alias now='date --iso-8601=seconds'
alias utc='date -u +"%Y-%m-%dT%H:%M:%S"'
Perfect, this will save a bit of time:
These formats are called ISO 8601 and there’s a whole Wiki page (not surprisingly) if you need more detail. This format usually comes up somewhere, so it might be useful to know at least a bit.
I probably don’t need to explain what time zones are (a Wiki page does it better). I want to focus a bit on other aspects.
If the product you are testing is used in different countries, the likelihood is people in these countries will also use different time zones. And it might be interesting to see how the product behaves when you change a time zone, or when two people from different time zones start sharing something inside the product, specially if that something is time limited. I dealt with a couple of similar scenarios last week, so I ended up changing my time zone quite a bit as well.
To complicate things further, some countries of higher latitude also use daylight saving time, that typically means they typically add 1 hour (in Lord Howe Island they add 30 minutes) to their time during spring and summer (approximately).
Time zones and Linux
I touched on the fact that I needed to change my time zone, how can it be done in Linux?
Linux, and it could be distro-dependent (I don’t know for sure), uses a symlink in
/etc/localtime that points to one of the time zone files in
Zoneinfo is a database that you can typically install using a package manager of your choice. In Pacman, look for
$ pacman -Qs tzdata
Sources for time zone and daylight saving time data
To change a time zone of your Linux device, you just need to change the symlink:
$ sudo ln -sf /usr/share/zoneinfo/Europe/vilnius /etc/localtime
Later I wrote a bash script that does a few of such things for me, it saves a few keystrokes here and there:
realpath $LOCALTIME_PATH | rev | cut -d'/' -f1,2 | rev
find $ZONEINFO_PATH/$region/* | rev | cut -d'/' -f1,2 | rev
find $ZONEINFO_PATH/ -maxdepth 1 -type d | rev | cut -d'/' -f1,2 | rev | sed 's/zoneinfo\///;/^[[:space:]]*$/d'
ln -sf "$ZONEINFO_PATH/$new_timezone_path" $LOCALTIME_PATH
-a <region> get all timezone in a region, e.g. Europe
-c get current timezone
-s <timezone> new timezone, e.g. Europe/Tallinn
-r get all regions
-h print this help
}while getopts "cs:ha:r" opt; do
case $opt in
a) get_timezones "$OPTARG" ;;
c) get_timezone ;;
h) print_help ;;
s) set_timezone "$OPTARG" ;;
r) get_regions ;;
*) print_help ;;
done[ -z "$1" ] && print_helpexit 0
You can get it on GitHub if you want.
You can also have a peak into tzdata files. They are binary files, but
zdump can read them:
$ zdump -v Europe/Brussels | head -10
Europe/Brussels -9223372036854775808 (gmtime failed) = -9223372036854775808 (localtime failed)
Europe/Brussels -67768040609741851 (gmtime failed) = -67768040609741851 (localtime failed)
Europe/Brussels -67768040609741850 (gmtime failed) = Thu Jan 1 00:00:00 -2147481748 LMT isdst=0 gmtoff=1050
Europe/Brussels -67768040609740801 (gmtime failed) = Thu Jan 1 00:17:29 -2147481748 LMT isdst=0 gmtoff=1050
Europe/Brussels Thu Jan 1 00:00:00 -2147481748 UT = Thu Jan 1 00:17:30 -2147481748 LMT isdst=0 gmtoff=1050
Europe/Brussels Wed Dec 31 23:42:29 1879 UT = Wed Dec 31 23:59:59 1879 LMT isdst=0 gmtoff=1050
Europe/Brussels Wed Dec 31 23:42:30 1879 UT = Thu Jan 1 00:00:00 1880 BMT isdst=0 gmtoff=1050
Europe/Brussels Sat Apr 30 23:59:59 1892 UT = Sun May 1 00:17:29 1892 BMT isdst=0 gmtoff=1050
Europe/Brussels Sun May 1 00:00:00 1892 UT = Sun May 1 00:00:00 1892 WET isdst=0 gmtoff=0
Europe/Brussels Sat Nov 7 23:59:59 1914 UT = Sat Nov 7 23:59:59 1914 WET isdst=0 gmtoff=0
$ git clone https://github.com/eggert/tz
$ cd tz/
Let’s dig up some useful information from the files. Let’s see that I want to know what time they use in Vilnius, how they change it, or even what the history is.
$ git grep -n Vilnius
NEWS:2150: Europe/Kaliningrad and Europe/Vilnius changed from +03/+04 to
europe:1918:# (Europe/Vilnius) was changed.
europe:1943:Zone Europe/Vilnius 1:41:16 - LMT 1880
tzfile.h:110:/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */
zone.tab:256:LT +5441+02519 Europe/Vilnius
zone1970.tab:213:LT +5441+02519 Europe/Vilnius
That likely means I should be interested in
europe file. Let’s see a bit more context:
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Europe/Vilnius 1:41:16 - LMT 1880
1:24:00 - WMT 1917 # Warsaw Mean Time
1:35:36 - KMT 1919 Oct 10 # Kaunas Mean Time
1:00 - CET 1920 Jul 12
2:00 - EET 1920 Oct 9
1:00 - CET 1940 Aug 3
3:00 - MSK 1941 Jun 24
1:00 C-Eur CE%sT 1944 Aug
3:00 Russia MSK/MSD 1989 Mar 26 2:00s
2:00 Russia EE%sT 1991 Sep 29 2:00s
2:00 C-Eur EE%sT 1998
2:00 - EET 1998 Mar 29 1:00u
1:00 EU CE%sT 1999 Oct 31 1:00u
2:00 - EET 2003 Jan 1
2:00 EU EE%sT
This is a zone record. How to read it is explained at length on iana’s page here.
If I want to know the current usage of time in Vilnius time zone, I need to become interested in the last line because there’s no value in
[UNTIL] column, so this line is for the present. The last line basically tells me:
- it belongs to Europe/Vilnius time zone
- they are +2 hours from UTC
- it refers to EU rule record
- the time is called EET or EE*T where the star is a character or characters from
LETTER/Scolumn from the rule record for EU
Let’s see the EU rule record then:
$ grep -P -B1 '^Rule[[:space:]]EU' europe
# Rule NAME FROM TO - IN ON AT SAVE LETTER/S
Rule EU 1977 1980 - Apr Sun>=1 1:00u 1:00 S
Rule EU 1977 only - Sep lastSun 1:00u 0 -
Rule EU 1978 only - Oct 1 1:00u 0 -
Rule EU 1979 1995 - Sep lastSun 1:00u 0 -
Rule EU 1981 max - Mar lastSun 1:00u 1:00 S
Rule EU 1996 max - Oct lastSun 1:00u 0 -
The last two rows speak about the present, I can understand that:
- the summer time rule was applied in 1981
- the change to the summer time (applying daylight saving time) happens on the last Sunday in March at 1a.m. UTC, the clock should be ahead by 1 hour at this time; in case of Vilnius it means that when the wall clock is 3a.m., the time should be ahead by 1 hour, so it makes 4a.m., that means that Vilnius is UTC+03:00 when EEST is applied
- the rule for the time change back from the summer time was changed in 1996 for the last time; the time used to change on the last Sunday in September (between 1979 and 1995) but now changes on the last Sunday in October; and again, at 1a.m. UTC, the wall clock should be 0 hour ahead, so in case of Vilnius, when the wall clock shows 4a.m., the time goes back to 3a.m.
There are many more options, characters, etc. in the rule and zone records. I chose a rather simple example. But after reading the iana’s tutorial, it should be easy to navigate both types of records.
Lastly, reading your device time zone is fairly easy. It can locate you at a certain geographical area. That can even be as specific as one country if your time zone is something like UTC+05:45 :)
So if you are privacy mindful, you use a different time zone that the one you are in.
There’s much more that can be said, I just wanted to summarise some of my findings. Knowing at least some basics like these proved useful to me last week when I needed some of this info for my testing effort.