The TLDR answer is:
$ POSIXTZ=$(tail -n1 /etc/localtime)
$ echo $POSIXTZ
CET-1CEST,M3.5.0,M10.5.0/3
$ TZNAME=$(find /usr/share/zoneinfo | while read fname; do cmp -s /etc/localtime "$fname" && echo "$fname" | cut -c 21- ; done)
$ echo $TZNAME
Europe/Copenhagen
The current timezone is stored in the file /etc/localtime
. As @Kusalananda remarks this can be a symbolic link. But as @JdeBP hints that on FreeBSD this file is normally copied from /usr/share/zoneinfo
during setup.
These files originates from textual descriptions in contrib/tzdata
This information is then compiled into a binary format using zic and the format is specified in tzfile
I do not know of a built in utility which directly parses this file. But it should be easy to write in C with the documentation at hand. If we want to stick with what come out of the box we can look at it using hexdump
.
hexdump -v -C /etc/localtime
Or if we just want to look at the magic marker:
$ hexdump -v -s 0 -n 5 -e '1/5 "%s\n"' /etc/localtime
TZif2
Or the fields:
tzh_ttisgmtcnt The number of UTC/local indicators stored in the file.
tzh_ttisstdcnt The number of standard/wall indicators stored in the file.
tzh_leapcnt The number of leap seconds for which data is stored in the file.
tzh_timecnt The number of ``transition times'' for which data is stored in the file.
tzh_typecnt The number of ``local time types'' for which data is stored in the file (must not be zero).
tzh_charcnt The number of characters of ``time zone abbreviation strings'' stored in the file.
Using:
hexdump -v -s 19 -n 4 -e '"tzh_ttisgmtcnt: " 1/4 "%9u\n"' /etc/localtime
hexdump -v -s 23 -n 4 -e '"tzh_ttisstdcnt: " 1/4 "%9u\n"' /etc/localtime
hexdump -v -s 27 -n 4 -e '"tzh_leapcnt: " 1/4 "%9u\n"' /etc/localtime
hexdump -v -s 31 -n 4 -e '"tzh_timecnt: " 1/4 "%9u\n"' /etc/localtime
hexdump -v -s 35 -n 4 -e '"tzh_typecnt: " 1/4 "%9u\n"' /etc/localtime
hexdump -v -s 39 -n 4 -e '"tzh_charcnt: " 1/4 "%9u\n"' /etc/localtime
Results in my case:
tzh_ttisgmtcnt: 0
tzh_ttisstdcnt: 6
tzh_leapcnt: 6
tzh_timecnt: 0
tzh_typecnt: 133
tzh_charcnt: 6
Then we do the following math to figure out where the first ttinfo
starts:
43 + (tzh_timecnt * 4) + (tzh_timecnt * 1)
43 + (0 * 4) + (0 * 1) = 43
The wheels slowly falls of:
$ hexdump -v -s 43 -n 6 -e '"ttinfo:\n tt_gmtoff: " 1/4 "%9u\n tt_isdst: " 1/1 "%1d\n tt_abbrind: " 1/1 "%1u\n"' /etc/localtime
ttinfo:
tt_gmtoff: 2350816009
tt_isdst: 96
tt_abbrind: 155
With these numbers I am probably a little off. And this is truly a masochistic way of dealing with it. So I am stopped just short of finding the gold using tt_abbrind
But if we look at the bottom of the tzfile specification we find this little nugget:
After the
second header and data comes a newline-enclosed, POSIX-TZ-environment-
variable-style string for use in handling instants after the last transi-
tion time stored in the file (with nothing between the newlines if there
is no POSIX representation for such instants).
So it is as easy as:
$ tail -n1 /etc/localtime
CET-1CEST,M3.5.0,M10.5.0/
When you look closer you will notice that /etc/localtime
does not contain any Continent/Region
setting! But as the file is copied from /usr/share/zoneinfo
you can compare them and find the probable file. I have not dived deep enough to confirm if /usr/share/zoneinfo
might contain duplicates.
But for me - this works nicely:
$ find /usr/share/zoneinfo | while read fname; do cmp -s /etc/localtime "$fname" && echo "$fname" | cut -c 21- ; done
Europe/Copenhagen
We iterate through all files in /usr/share/zoneinfo
and compare each of them with /etc/localtime
. cmp
using the -s
parameter will not display anything and only exit using a value. If the value is zero we will print the name. When printing the name we use cut
to remove the first 21 characters to get Continent/Region
TZ
to the value that you want to use?