Timezones segons dades⚓
Context⚓
Una mica de la festa de la timezone després de barallar-nos-hi un xic.
mantras: Timestamp
is a picture of a clock. You don't want a picture of a clock.
Sobre timestamptz, contrariament al què hom pensaria quan llegeix "timestamp with time zone", aquest datatype de Postgres no guarda un timezone. És un "flag binari" de visualizació, un helper d'inserció i de visualització que converteix de la teva configuració o la de servidor a unix timestamp.
Sobre què fem a dades, coincideix bastant amb el què diu aquí:
Glossari⚓
Si posem [citation needed]
vol dir que encara ho estem analitzant.
show time zone
és Europe/Madrid
. Per tant les visualitzacions de timestamptz s'ensenyen en Europe/Madrid
Resumet⚓
Opcions:
- use timestamptz
- use timestamp (without time zone)
- use timestamp (without time zone) to store UTC times
Triem la opció 1. tot datetime a la db en timestamptz i quan insertes has de fer-ho amb el timezone especificat. En general no confiem amb show time zone;
del servidor o client [citation needed perquè potser podríem acceptar agregacions naïf si hi confiéssim].
Quan estem fent servir timescaledb
fem agregacions amb la funció time_bucket
per que el query planner sàpiga com accedir els chunks correctament. En altre cas, fem servir agregacions amb date_trunc('day', some_timestamptz, 'Europe/Madrid')
Les dates poden ser naïf (no existeix el concepte de date aware a postgres), però seran en local. Si es pot i té sentit, mantenir el timestamptz de mitjanit [citation needed].
Aprofundim⚓
types d'entrada⚓
si rebem un timestamp, de seguida el passem a timestamptz amb at time zone
.
si rebem un date, primer el passem a timestamp
Amb el timezone del client configurat a 'Europe/Madrid', dóna
select
'2021-01-01'::date as adate,
'2021-01-01'::date at time zone 'Europe/Madrid', -- timestamp 😲 ❌
'2021-01-01'::date::timestamp at time zone 'Europe/Madrid' -- timestamptz 👍
adate | timezone | timezone |
---|---|---|
2021-01-01 | 2021-01-01 00:00:00 | 2021-01-01 00:00:00+01 |
Sources amb naïf i columna dst⚓
Ho passem a timestamp i convertim a la timezone que correspongui abans d'insertar-ho en timestamptz
CASE
WHEN estiu=1 AND (sistema = 'PEN' or sistema = 'BAL')
THEN data::timestamp AT TIME ZONE 'CEST'
WHEN estiu=0 AND (sistema = 'PEN' or sistema = 'BAL')
THEN data::timestamp AT TIME ZONE 'CET'
WHEN estiu=1 AND sistema = 'CAN'
THEN data::timestamp AT TIME ZONE 'WETDST'
WHEN estiu=0 AND sistema = 'CAN'
THEN data::timestamp AT TIME ZONE 'WET'
END AS end_hour_aware
Idealment afegiríem una columna amb el timezone en sintaxi postgres ('Europe/Madrid', 'Atlantic/Canary') per després poder fer
select
end_hour_aware,
end_hour_aware at time zone timezone as end_hour_local,
date_trunc('day', end_hour_aware, timezone) as day_local
from (
values
('2021-01-01 10:00:00'::timestamp at time zone 'Europe/Madrid', 'Europe/Madrid'),
('2021-01-01 10:00:00'::timestamp at time zone 'Atlantic/Canary', 'Atlantic/Canary')
) as foo(end_hour_aware, timezone);
end_hour_aware | end_hour_local | day_local |
---|---|---|
2021-01-01 10:00:00+01 | 2021-01-01 10:00:00 | 2021-01-01 00:00:00+01 |
2021-01-01 11:00:00+01 | 2021-01-01 10:00:00 | 2021-01-01 01:00:00+01 |
Podeu veure la casuística:
select
end_hour_aware at time zone 'UTC' as end_hour_naif_utc_in_db, -- db *always* stores naïf unix timestamps, utc, even if datatype is timestamptz
end_hour_aware as end_hour_aware_at_configured_timezone, -- depends on show time zone; of your client/server
end_hour_aware at time zone timezone as end_hour_local, -- timestamp naïf (can't be otherwise once we localize)
date_trunc('day', end_hour_aware, timezone) as midnight_local, -- midnight local seen by `show time zone;`, it's timestamptz, hence automatically converted for display. really unix_timestamp in db
date_trunc('day', end_hour_aware, timezone)::date as day_local,
date_trunc('day', end_hour_aware)::date as day_naif_local_and_wrong, -- ❌ implicit conversion to `show time zone`
time_bucket('1 day', end_hour_aware, timezone) as day_bucket
from (
values
('2021-01-01 20:00:00'::timestamp at time zone 'Europe/Madrid', 'Europe/Madrid'),
('2021-01-01 20:00:00'::timestamp at time zone 'Atlantic/Canary', 'Atlantic/Canary'),
('2021-01-01 20:00:00'::timestamp at time zone 'PST', 'PST')
) as foo(end_hour_aware, timezone);
end_hour_naif_utc_in_db | end_hour_aware_at_configured_timezone | end_hour_local | midnight_local | day_local | day_naif_local_and_wrong | day_bucket |
---|---|---|---|---|---|---|
2021-01-01 19:00:00 | 2021-01-01 20:00:00+01 | 2021-01-01 20:00:00 | 2021-01-01 00:00:00+01 | 2021-01-01 | 2021-01-01 | 2021-01-01 00:00:00+01 |
2021-01-01 20:00:00 | 2021-01-01 21:00:00+01 | 2021-01-01 20:00:00 | 2021-01-01 01:00:00+01 | 2021-01-01 | 2021-01-01 | 2021-01-01 01:00:00+01 |
2021-01-02 04:00:00 | 2021-01-02 05:00:00+01 | 2021-01-01 20:00:00 | 2021-01-01 09:00:00+01 | 2021-01-01 | 2021-01-02 | 2021-01-01 09:00:00+01 |
Agregacions⚓
Les agregacions (de calendari) sempre amb el time zone que sigui rellevant. Una agregació diaria està sempre lligada a un timezone concret, perquè el dia està definit només dins d'un timezone, sinó parlaríem d'agregacions de 24h, que faríem en utc.
select
-- date_trunc per defecte fa servir el time zone configurat
date_trunc('day', '2021-01-01 01:00:00+05:00'::timestamptz) as date_trunc_local,
date_trunc('day', '2021-01-01 01:00:00+05:00'::timestamptz, 'Europe/Madrid') as date_trunc_local_explicit,
-- time_bucket per defecte fa servir utc
time_bucket('1 day', '2021-01-01 01:00:00+05:00'::timestamptz) as time_bucket_utc,
time_bucket('1 day', '2021-01-01 01:00:00+05:00'::timestamptz, 'Europe/Madrid') as time_bucket_local;
date_trunc_local | date_trunc_local_explicit | time_bucket_utc | time_bucket_local |
---|---|---|---|
2020-12-31 00:00:00+01 | 2020-12-31 00:00:00+01 | 2020-12-31 01:00:00+01 | 2020-12-31 00:00:00+01 |
En general ho faríem tot en el time zone de l'Estat, però depèn del use case (Veure Excepcions a la norma).
Si hem de convertir a date caldrà passar-ho al time zone que toqui abans de convertir a date, com que no tenim una gunció time_bucket(timestamptz)->date (ni date_trunc) cal que abans de convertir a date ho passem a naïf del timezone que toqui per a què postgres no li apliqui el time zone que tinguem configurat.
select '2022-12-31 23:00:00+00'::timestamptz,
'2022-12-31 23:00:00+00'::timestamptz at time zone 'Europe/Madrid',
'2022-12-31 23:00:00'::timestamp at time zone 'Europe/Madrid' as naif,
date_trunc('day', '2022-12-31 23:00:00+00'::timestamptz, 'Europe/Madrid'),
date_trunc('day', '2022-12-31 23:00:00+00'::timestamptz, 'Europe/Madrid')::date as incorrect_dt_based_on_config,
(date_trunc('day', '2022-12-31 23:00:00+00'::timestamptz, 'Europe/Madrid') at time zone 'Europe/Madrid')::date as correct_dt,
(time_bucket('1 day', '2022-12-31 23:00:00+00'::timestamptz, 'Europe/Madrid') at time zone 'Europe/Madrid'),
(time_bucket('1 day', '2022-12-31 23:00:00+00'::timestamptz, 'Europe/Madrid'))::date as incorrect_tb_based_on_config,
(time_bucket('1 day', '2022-12-31 23:00:00+00'::timestamptz, 'Europe/Madrid') at time zone 'Europe/Madrid')::date as correct
timestamptz | timezone | naif | date_trunc | incorrect_dt_based_on_config | correct_dt | timezone | incorrect_tb_based_on_config | correct |
---|---|---|---|---|---|---|---|---|
2022-12-31 23:00:00+00 | 2023-01-01 00:00:00 | 2022-12-31 22:00:00+00 | 2022-12-31 23:00:00+00 | 2022-12-31 | 2023-01-01 | 2023-01-01 00:00:00 | 2022-12-31 | 2023-01-01 |
Excepcions a la norma⚓
Pel cas que estem tractant actualment, previsió de la demanda, fem servir el dia local (a picture of a clock) perquè el què ens interessa no és comparar intervals de temps, sinó comportaments del dilluns, del cap de setmana, etc. Les hores a agrupar, els dies a agrupar, són culturals, encara que de fet representin packets d'hores universals diferents [citation needed].
problemes⚓
En general aquest recull Don't do this està força bé.
timestamptz a tot arreu⚓
timestamptz
no està suportat a tot arreu, hi ha ORMs que no ho suporten bé.
Tothom ha de ser conscient que ha d'inserir explicitant el timezone o bé assegurant-se que el timezone configurat del servidor és el què ell assumeix que és. Com que això últim és un pitfall, millor sempre passar a timestamptz de seguida i explicitament.
timestamp naïf utc⚓
Això implica que tothom sàpiga que els timestamps són naïfs semànticament. TODO: Elaborar maneres alternatives de fer inserts a la proposada al Don't do this
timestamp naïf 'Europe/Madrid'⚓
Si no tindràs mai de la vida altres timezones... però no ho recomanem, perquè després passa el què passa