Freebsd – Identify current default time zone set in FreeBSD

freebsdtimezone

I know sudo tzsetup will prompt me to change time zone settings on FreeBSD.

➥ How can I see the current time zone settings, without making changes?

I know date shows me current time. For example:

Sun Dec 9 05:45:25 UTC 2018

…which I assumes mean the current default time zone is UTC.

But in this case:

Sat Dec 8 21:52:04 PST 2018

The PST is not a true time zone. Such 2-4 letter codes are neither standardized nor unique. True time zones are in the Continent/Region format such as Europe/Africa or Africa/Tunis (see Wikipedia).

How can I see the true time zone set as default?

This posting mentions using an environment variable TZ.

export TZ=America/Los_Angeles

But my FreeBSD 11.2 machine does not have such a variable set. So I suspect that is not the driving factor.

Best Answer

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

Related Question