Il y a quelques semaines, dans notre infrastructure, j'ai découvert une petite erreur de configuration dans la variable d'environnement TZ. La correction de cette erreur a bouleversé l'équilibre fragile des bogues dans l'univers et les graphismes RPS pour l'un des projets de notre graphite sont littéralement devenus fous. Je vais vous dire comment j'ai couru quelques heures pendant plusieurs jours.
Comment tout a commencé
Le script, qui a été lancé à la main et fonctionnait parfaitement, a généré une erreur lors du démarrage de cron.d. Une étude superficielle des journaux en texte clair a indiqué ce qui n'allait pas.
$ 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
Erreur avec nous ou dans le programme?
Le comportement du programme est-il correct ou s'agit-il d'un bogue?
La documentation GNU indique qu'il existe 3 formats possibles pour une variable d'environnement TZ:
- Format sans DST (heure d'été) std offset. ExemplesEST+5,GMT+0
- Format avec la DST: std offset dst [offset],start[/time],end[/time]. ExempleEST+5EDT,M3.2.0/2,M11.1.0/2.
- Nom du fichier de description du fuseau horaire. Peut commencer par deux points :. Si le premier caractère (ou après les deux-points) est une barre oblique/, il doit s'agir du chemin absolu vers le fichier. Sinon, alors/usr/share/zoneinfo/${TZ}.
Pourquoi un client ClickHouse a-t-il besoin de connaître l'heure locale?
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
Script 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)
Script de démarrage Python avec différents 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 .
timestampValeur de fuseau horaire cassée, valeur 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++
, .
 Le merveilleux Tikki Schellen conseille raisonnablement : "Ne prenez pas le temps à mains nues." Pour moi, dans le classement du danger, c'est tombé sur une ligne avec DNS. Essayez généralement de l'éviter autant que possible.
Et en ce moment intéressant, je souhaite rester en bonne santé. Essayez de rester à la maison et si vous êtes soudain très ennuyé et que vous n'avez rien à faire, vous pouvez jouer à des jeux d' InnoGames . Soit dit en passant, nous avons des postes vacants