Why does `date` ignore TZ environment variable?

On Ubuntu 18.04, I have the following behavior of date:

$ date --version | head -n1
date (GNU coreutils) 8.28
$ date
Вт окт  8 13:18:18 MSK 2019
$ TZ=UTC date
Вт окт  8 10:18:23 UTC 2019

So far so good. But now I’m trying to do the same on Raspbian 9:

$ date --version | head -n1
date (GNU coreutils) 8.26
$ date
Tue Oct  8 13:18:50 MSK 2019
$ TZ=UTC date
Tue Oct  8 13:18:51 MSK 2019

What could be the reason for Raspbian version of date to ignore the TZ environment variable?

Asked By: Ruslan

||

I can think of two possible causes:

  1. the file /usr/share/zoneinfo/UTC is not present or is corrupted on your Raspbian 9, so glibc fails to implement the TZ variable setting and falls back to system default timezone,

  2. you may have a previously-configured TZ variable that has been marked as read-only, so your attempt to change it won’t take effect.

Answered By: telcoM

The correct, standard and portable way to define via $TZ a time zone that is called UTC and is on (with 0 offset to) Universal Time all year round is with:

TZ=UTC0

That is self-contained and give the full specification for that timezone. That tells that the offset to UTC is 0 (all year round as there’s no DST part specified) and the label (as reported by date +%Z for instance) is UTC.

The timezone specification as described by TZ can get more complicated as you can also embed the DST name and offset and the rules for when to change between summer and winter time. However, those rules are limited and in particular can’t cover cases where the rules change change from one year to another (and in most countries, the rules have changed over time or use rules different from the first or last Sunday in some month).

That’s why POSIX also specifies TZ=:something but with how something is handled left implementation defined to allow implementations to come up with a better way to define a timezone for real life zones.

On most systems, that’s implemented using the ICANN’s tz database (they also publish reference code to handle those).

So you’d use TZ=:Europe/London for instance for the mainland Britain timezone which covers timezone offsets and when the change for the current year and all past years in London.

In practice, Europe/London is interpreted as the path of a file relative to some zoneinfo directory (often /usr/share/zoneinfo).

In the tz database, there is also a Etc/UTC file (with Etc/Universal and Etc/Zulu links to it). Etc/GMT is defined the same way except the label is GMT all year round (with Etc/GMT-0, Etc/GMT+0, Etc/Greenwich as links).

So, you can do TZ=:Etc/UTC to get the same effect as TZ=UTC0 except that the system needs to open that Etc/UTC file to find out about the simplest of TZ rules: 0 offset to UTC all year round.

On many (most?) systems, there’s a UTC -> Etc/UTC symlink, so you can also do TZ=:UTC.

TZ=UTC itself is not POSIX, but here in the absence of an offset being given, on most systems, it’s interpreted as the same as TZ=:UTC (looks for the timezone definition in /path/to/zoneinfo/UTC. But if the tz database is not available, that won’t work.

Also note that with date specifically, you can use the -u option to get UTC dates regardless of what $TZ contains. date -u is equivalent to TZ=UTC0 date.

Answered By: Stéphane Chazelas
Categories: Answers Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.