A few weeks ago, in our infrastructure, I discovered a small configuration error in the environment variable TZ
. The correction of this error upset the fragile balance of bugs in the universe and the RPS graphics for one of the projects in our graphite literally went crazy. I'll tell you how I chased a few hours for several days.
How it all began
The script, which was launched by hand and worked perfectly, threw an error when starting from cron.d. A superficial study of the logs in plain text indicated what was wrong.
$ TZ='' clickhouse-client; echo exit=$?
ClickHouse client version 20.3.2.1.
Connecting to localhost:9000 as user default.
Connected to ClickHouse server version 20.3.2 revision 54433.
Poco::Exception. Code: 1000, e.code() = 0, e.displayText() = Exception: Could not determine time zone from TZ variable value: '': filesystem error: in file_size: Is a directory [/usr/share/zoneinfo] (version 20.3.2.1)
exit=232
Error with us or in the program?
Is the program behavior correct or is it a bug?
The GNU documentation indicates that there are 3 possible formats for an environment variable TZ
:
- Format without DST (daylight saving time)
std offset
. Examples EST+5
,GMT+0
- Format with the DST:
std offset dst [offset],start[/time],end[/time]
. Example EST+5EDT,M3.2.0/2,M11.1.0/2
. - The name of the time zone description file. May begin with a colon
:
. If the first (or following the colon) character is a slash /
, then this must be the absolute path to the file. If not, then /usr/share/zoneinfo/${TZ}
.
Why does a ClickHouse client need local time knowledge?
Date
DateTime
DBMS timestamp, toDateTime('2020-02-02 20:20:20')
(, , ) UInt32
. , . TZ
, , 98% .
, ClickHouse ( Poco) , . .
, . cron.d TZ
, . . , 2020-04-15 2020-04-20 .
2020-04-22 ( ): " RPS "

, -, . .
self.now = int(datetime.datetime.utcnow().timestamp())
...
dt = datetime.datetime.strptime(time_str, time_format).utctimetuple()
timestamp = time.mktime(dt)
datetime.utcnow()
?
Return the current UTC date and time, with tzinfo
=None
.
This is like now()
, but returns the current UTC date and time, as a naive datetime
object. An aware current UTC datetime can be obtained by calling datetime.now(timezone.utc)
. See also now()
.
Warning: Because naive datetime
objects are treated by many datetime
methods as local times, it is preferred to use aware datetimes to represent times in UTC. As such, the recommended way to create an object representing the current time in UTC is by calling datetime.now(timezone.utc)
.
, UTC, tzinfo=None
datetime
. .timestamp()
UNIX time , UTC. : TZ=UTC
datetime.now().timestamp()
. , .timestamp()
, datetime.now(timezone.utc)
datetime.utctimetuple()
?
datetime.timetuple()
:
Return time.struct_time
such as returned by time.localtime()
.
datetime.utctimetuple()
:
If datetime
instance d is naive, this is the same as d.timetuple()
except that tm_isdst
is forced to 0 regardless of what d.dst()
returns. DST is never in effect for a UTC time.
If d is aware, d is normalized to UTC time, by subtracting d.utcoffset()
, and a time.struct_time
for the normalized time is returned. tm_isdst
is forced to 0.
Warning: Because naive datetime
objects are treated by many datetime
methods as local times, it is preferred to use aware datetimes to represent times in UTC; as a result, using utcfromtimetuple
may give misleading results. If you have a naive datetime
representing UTC, use datetime.replace(tzinfo=timezone.utc) to make it aware, at which point you can use datetime.timetuple().
. time.struct_time
, time.mktime()
. , python2. , . , UNIX timestamp .
Python, , .
, datetime.strptime().timestamp()
TZ
, . - , - UTC, - , . , .
, RPS :

, , . , .
, :
- ,
TZ=''
- UTC
strptime()
- UTC,
strptime()
strptime()
strptime
2020-04-24T05:31:55+02:00
python
from datetime import datetime, timezone
from time import mktime
def pprint(d: dict):
for k in d:
print("{}: {}".format(k, d[k]))
now = {'now': datetime.now(),
'utcnow': datetime.utcnow()}
now_ts = [int(now[k].timestamp()) for k in now]
now_dict = {k: [now_ts[i], now_ts[i] - now_ts[0]] for i, k in enumerate(now)}
pprint(now_dict)
print()
ts_c = 1587699115
time_format = "%Y-%m-%dT%H:%M:%S%z"
time_str = "2020-04-24T05:31:55+02:00"
timetuples = {
'timetuple': datetime.strptime(time_str, time_format).timetuple(),
'utctimetuple': datetime.strptime(time_str, time_format).utctimetuple(),
}
ts = {
'timestamp': [
int(datetime.strptime(time_str, time_format).timestamp()),
int(datetime.strptime(time_str, time_format).timestamp()) - ts_c,
],
'timetuple': [
int(mktime(timetuples['timetuple'])),
int(mktime(timetuples['timetuple'])) - ts_c,
],
'utctimetuple': [
int(mktime(timetuples['utctimetuple'])),
int(mktime(timetuples['utctimetuple'])) - ts_c,
],
}
pprint(ts)
python TZ
#!/usr/bin/env bash
for tz in '' Europe/Moscow UTC Europe/Berlin
do
date "+==TZ=${tz} %s=="
TZ=$tz python example.py
date '+++%s++' -d '2020-04-24T05:31:55+02:00'
done
TZ
. , +02:00
.
now
timestamp
. , timetuple + mktime
.
timestamp, TZ=''
==TZ='' 1587914590==
now: [1587914590, 0]
utcnow: [1587914590, 0]
timestamp: [1587699115, 0]
timetuple: [1587706315, 7200] - TZ - UTC
utctimetuple: [1587699115, 0]
++1587699115++
TZ='+03:00'
, UTC strptime()
==TZ=Europe/Moscow 1587914590==
now: [1587914590, 0]
utcnow: [1587903790, -10800] - UTC - TZ
timestamp: [1587699115, 0]
timetuple: [1587695515, -3600] - +02:00 - TZ
utctimetuple: [1587688315, -10800] - UTC - TZ
++1587699115++
TZ=UTC
, strptime()
==TZ=UTC 1587914590==
now: [1587914590, 0]
utcnow: [1587914590, 0]
timestamp: [1587699115, 0]
timetuple: [1587706315, 7200] - +02:00 - UTC
utctimetuple: [1587699115, 0]
++1587699115++
TZ='+02:00'
, strptime()
, unset TZ
==TZ=Europe/Berlin 1587914590==
now: [1587914590, 0]
utcnow: [1587907390, -7200] - UTC - TZ
timestamp: [1587699115, 0]
timetuple: [1587699115, 0]
utctimetuple: [1587695515, -3600] - UTC - TZ... DST!
++1587699115++
, .
The wonderful Tikki Schellen reasonably advises : "Do not take time with your bare hands." For me, in the ranking of danger, it fell on one line with DNS. Try to generally avoid it whenever possible.
And at this interesting time I wish to stay healthy. Try to stay at home and if you are suddenly very bored and have nothing to do, you can play games from InnoGames . By the way, we have open vacancies