Hace unas semanas, en nuestra infraestructura, descubr铆 un peque帽o error de configuraci贸n en la variable de entorno TZ
. La correcci贸n de este error alter贸 el fr谩gil equilibrio de errores en el universo y los gr谩ficos RPS para uno de los proyectos en nuestro grafito literalmente se volvieron locos. Te dir茅 c贸mo persegu铆 unas horas durante varios d铆as.
C贸mo todo empez贸
El script, que se lanz贸 a mano y funcion贸 perfectamente, arroj贸 un error al comenzar desde cron.d. Un estudio superficial de los registros en texto sin formato indic贸 lo que estaba mal.
$ 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 con nosotros o en el programa?
驴Es correcto el comportamiento del programa o es un error?
La documentaci贸n de GNU indica que hay 3 formatos posibles para una variable de entorno TZ
:
- Formato sin DST (horario de verano)
std offset
. Ejemplos EST+5
,GMT+0
- Formatear con el horario de verano:
std offset dst [offset],start[/time],end[/time]
. Ejemplo EST+5EDT,M3.2.0/2,M11.1.0/2
. - El nombre del archivo de descripci贸n de zona horaria. Puede comenzar con un colon
:
. Si el primer car谩cter (o despu茅s de los dos puntos) es una barra oblicua /
, esta debe ser la ruta absoluta al archivo. Si no, entonces /usr/share/zoneinfo/${TZ}
.
驴Por qu茅 un cliente ClickHouse necesita conocimiento de la hora local?
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++
, .
El maravilloso Tikki Schellen aconseja razonablemente : "No te tomes el tiempo con tus propias manos". Para m铆, en el ranking de peligro, cay贸 en una l铆nea con DNS. Intenta evitarlo en general siempre que sea posible.
Y en este momento interesante deseo mantenerme saludable. Intenta quedarte en casa y si de repente est谩s muy aburrido y no tienes nada que hacer, puedes jugar juegos de InnoGames . Por cierto, tenemos vacantes abiertas