The other day I ran into this timezone issue in Ruby, and it exposed me to the concept of tz database that I didn’t know before. When I was googling around for an intro article about it there were almost none, so decided to write one on my own.
We’ll be using alpine:3.12
Docker image to test things out.
What is it?
tz database is a collection of binary files used by most systems in the world to convert time between various timezones.
Here are the official documentaion and the source code on GitHub. NEWS
file is used as a replacement for more common CHANGELOG
in other projects.
tzdata package
tz database and its related cli tools are distributed as tzdata
package. Let’s download it within our container:
$> docker run -it alpine:3.12
/ # apk add tzdata
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2020c-r0)
Executing busybox-1.31.1-r19.trigger
OK: 9 MiB in 15 packages
In order to check which version your tzdata
package is on, use the package manager’s version
command:
/ # apk version tzdata
Installed: Available:
tzdata-2020c-r0 = 2020c-r0
By default, it saves timezone files to the /usr/share/zonefino
directory:
/ # ls /usr/share/zoneinfo
Africa/ Asia/ CET Cuba Egypt ...
CLI tools
When you install tzdata
, several related CLI tools are included in the package.
zic (Zone Information Compiler )
zic
compiles UTF-8 source text files into Time Zone Information Format (TZif) binary files. We’ll look at how to use this later in the article.
zdump
zdump
is used to parse TZif files. You’ll see that these files can’t be read by typical commands like cat
:
/ # zdump /usr/share/zoneinfo/America/Los_Angeles
/usr/share/zoneinfo/America/Los_Angeles Sun Nov 8 17:05:59 2020 PST
/ # cat /usr/share/zoneinfo/America/Los_Angeles
< Prints an unreadable string >
/etc/localtime
is used by libc
and many other systems to determine the system’s timezone. Under the hood, it should be a symbolic link to one of the tzif files in /usr/share/zoneinfo
as documented here. zdump
can also be used examine this file (it defaults to GMT in alpine)
/ # zdump /etc/localtime
/etc/localtime Mon Nov 9 01:25:48 2020 GMT
Timezone source files
Although timezone rules have become relatively stable within the last decade, it wasn’t the case in the 20th century. Here’s an example:
/ # TZ=America/Los_Angeles date --date="1950-04-01"
Sat Apr 1 00:00:00 PST 1950
/ # TZ=America/Los_Angeles date --date="2007-04-01"
Sun Apr 1 00:00:00 PDT 2007
As captured in the comment, California voted to start the Daylight Savings day from the last Sunday in April from 1950. However, the federal government passed the bill to extend the Daylight Savings Time to start from the second Sunday of March in 2007. Therefore you see that April 1st was not in Daylight Savings Time back in 1950 above.
Policy changes like this happened throughout the world, and tz database documents these events in the source file. tz database uses three main components: rules, zones, and links. Here is an example configuration from zic
man page (no need to understand what each line means! This is meant to give an idea of what it may look like):
# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
Rule Swiss 1941 1942 - May Mon>=1 1:00 1:00 S
Rule Swiss 1941 1942 - Oct Mon>=1 2:00 0 -
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 -
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Europe/Zurich 0:34:08 - LMT 1853 Jul 16
0:29:45.50 - BMT 1894 Jun
1:00 Swiss CE%sT 1981
1:00 EU CE%sT
# Link TARGET LINK-NAME
Link Europe/Zurich Europe/Vaduz
I recommend reading the zic
man page for what each column specifically means, but simplified meanings of these lines are:
- Rule defines special rules that to be applied within a time frame
- Zone defines a difference from UTC to be used as the default and specifies which rule to apply
- Link creates an alias (
Europe/Vaduz
will be an alias forEurope/Zurich
zone)
More examples are explored in tz-how-to page
Creating our custom timezone
After getting a grasp on the basic syntax, we can define a new timezone ourselves! We’ll use the fictional Konoha city in Hi No Kuni from Naturo as our example timezone name. Run the following command to create a source file in the container:
echo "
# Hi No Kuni
# Rule NAME FROM TO - IN ON AT SAVE LETTER/S
Rule HI 2020 only - Mar Sun>=8 0:00 1:00 D
Rule HI 2020 only - Nov 1 0:00 0 S
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Hi_No_Kuni/Konoha -8:00 HI K%sT
" > /konoha.zi
The above config defines the following rules:
- Define
Hi_No_Kuni/Konoha
zone that will be 8 hours behind UTC (same as PST). It will useHI
rules.%s
substring inK%sT
will be replaced with theLETTER
field from a current rule in use. - We define two rules:
- From the 2nd Sunday in Mar 2020, subtract 1 hour from the local time. Use
KDT
to refer to this timezone - From Nov 1st 2020, use the exact the local time. Use
KST
to refer to this timezone
- From the 2nd Sunday in Mar 2020, subtract 1 hour from the local time. Use
Let’s verify that it works:
/ # zic /konoha.zi
/ # zdump /usr/share/zoneinfo/Hi_No_Kuni/Konoha
/usr/share/zoneinfo/Hi_No_Kuni/Konoha Sun Nov 8 19:07:59 2020 KST
/ # ln -sf /usr/share/zoneinfo/Hi_No_Kuni/Konoha /etc/localtime
/ # date --date="2020-03-14"
Sat Mar 14 00:00:00 KDT 2020
/ # date --date="2020-11-01"
Sun Nov 1 00:00:00 KST 2020
zic
compiler saves the output to relative /usr/share/zoneinfo
location based on zone name. At the point of writing this post, it is 11/08/20 so as expected zdump
prints the time in KST
(possibly known as Konoha Standard Time
in Naruto’s world). We’re setting the new Hi_No_Kuni/Konoha
as the default time in this container by making a symbolic link to /etc/localtime
. As a result, date
command prints the correct timezone given a specific date!
Conclusion
I learned a lot in the process of writing this post, but there is so much more to tz database. As praised in this blog post, documentation in the source code is a wealth of knowledge about everything timezone related. I highly recommend checking out what kind of rules are applied in your home country.
Hope this helped you understand tz database a little better!