Bakgrunn
Cerebrum benytter seg av tredjepartsbiblioteket egenix-mx-base for håndtering av dato og tid. Mer spesifikt begrenser bruk seg til modulen mx.DateTime.
egenix-mx-base er en CPython-modul, og vedlikeholder eGenix ser ikke ut til å ha noen planer om å skrive om modulen med støtte for Python 3.
Overordnet
Python har i lang tid hatt relativt god støtte for dato og tid i standardbibliotekets datetime-modul. Planen blir derfor å migrere over til denne.
datetime vs date
mx.DateTime skiller ikke på dato-objekter og timestamps — alt representeres med mx.DateTime. Når vi går over til datetime-modulen bør dette endres, slik at datetime.date benyttes der en kun er interessert i dato.
Ved spørringer mot database betyr dette at:
— DATE-felter bør konverteres til datetime.date — TIMESTAMP WITHOUT TIME ZONE-felter bør konverteres til naive datetime.datetime-objekter (TODO: eller bør vi legge på default tz?) — TIMESTAMP WITH TIME ZONE-felter konverteres allerede til tz-aware datetime.datetime-objekter.
Merk at konvertering andre veien fungerer uansett — database-driver tåler å få både mx.DateTime, datetime.date og datetime.datetime. Støtte for førstnevnte vil måtte fjernes.
Tidssoner
All bruk av tid i Cerebrum bør i utgangspunktet benytte seg av tidssone-informasjon for timestamps, rett og slett fordi eksplisitt informasjon er enklere å forholde seg til senere enn implisitt/"out-of-band" informasjon.
Derfor bør all data lagres som TIMESTAMP WITH TIME ZONE, og all bruk av datetime.datetime-objekter internt i Cerebrum bør inkludere tzinfo.
Dette er imidlertid utenfor scope når vi diskuterer avvikling av mx.DateTime som et steg i å støtte Python 3.
Teknisk gjeld
I Cerebrum-databasen er det allerede en del feil når det kommer til dato vs timestamps:
- Kolonner er gjerne navngitt feil (kolonne date inneholder og skal inneholde timestamps)
- Kolonner har feil datatype (TIMESTAMP, men vi er kun interessert i dato)
Fiks av dette er i utgangspunktet utenfor scope.
Arbeid
Bruk av mx.DateTime i Cerebrum er tett knyttet — objekter av denne typen sendes gjennom hele stacken — fra brukergrensesnitt helt inn til databasedriver. Når vi skrur av støtte for mx.DateTime i databasedriver, vil all bruk av database-felter med DATE/TIMESTAMP endre oppførsel.
Før arbeidet med avvikling vil vi derfor lage en feature-branch, nomx-master hvor de mer omfattende endrigene kan skje. Eventuelle endringer som kan gjøres mot master, bør fremdeles gjøres mot master, slik at vi beholder den raske integrasjonsloopen mot produksjon når det lar seg gjøre.
Feature-branch nomx-master bør oppdateres fra master så ofte som mulig.
Felles løsninger
Ved oppdatering av database kan vi fint allerede i master sende datetime.datetime- og datetime.date-objekter som query-params/binds. Endringer til dette kan derfor skje mot master.
Ved lesing/sammenligning av verdier fra database, kan en kompatibilitetsfunksjon benyttes:
- Cerebrum.utils.date_compat.get_date()
I tilfeller hvor en skal ha dato (DATE) fra database. Dersom databasefeltet er obligatorisk (NOT NULL), kan det legges på get_date(allow_none=False).
- mx.DateTime → datetime.date
- datetime.datetime → datetime.date
- datetime.date → datetime.date
- None → None (hvis ikke allow_none=False)
- alt annet → ValueError
- Cerebrum.utils.date_compat.get_datetime_naive()
I tilfeller hvor en skal ha dato og tid (TIMESTAMP [WITHOUT TIME ZONE]) fra database. Dersom databasefeltet er obligatorisk (NOT NULL), kan det legges på get_datetime_naive(allow_none=False).
- mx.DateTime → datetime.datetime
- datetime.datetime → datetime.datetime
- datetime.date → datetime.datetime
- None → None (hvis ikke allow_none=False)
- alt annet → ValueError
Dersom input-verdi er et tz-aware datetime-objekt, vil denne konverteres til angitt tidssone (default cereconf.TIMEZONE) før tidssone-informasjon fjernes.
- Cerebrum.utils.date_compat.get_datetime_tz():
I tilfeller hvor en skal ha dato og tid (TIMESTAMP [WITHOUT TIME ZONE]) fra database. Dersom databasefeltet er obligatorisk (NOT NULL), kan det legges på get_datetime_tz(allow_none=False).
- mx.DateTime → datetime.datetime
- datetime.datetime → datetime.datetime
- datetime.date → datetime.datetime
- None → None (hvis ikke allow_none=False)
- alt annet → ValueError
Alle objekter uten tidssone-informasjon antaes å være i angitt tidssone (default cereconf.TIMEZONE).
Parsing
- ISO-format (Cerebrum.utils.date)
- Alt annet (datetime.datetime.strptime) / evt. passende util-funksjon.
Bofhd
XMLRPC har ingen støtte for TZ - beholder dagens struktur, hvor TZ antas å være lokal tid, serverside. TODO: Legge på default-tz i objekt eller fortsette med naive dt-objekter?
API
Input krever alt tz-aware iso-format.
Caveats
mx.DateTime og datetime.date(time)-objekter har i mange tilfeller ganske ulik oppførsel.
F.eks.:
md = mx.DateTime dt = datetime.datetime td = datetime.timedelta dd = datetime.date # delta md.now() + 1 dt.now() + td(days=1) # conv float(md.now()) # 1611247124.7080307 float(dt.now()) # TypeError str(md.now()) # '2021-01-21 17:40:23' str(dt.now()) # '2021-01-21 17:40:23.936257' # cmp d = dt.now(); d == float(d.strftime('%s.%f')) # False m = md.now(); m == float(m) # True mx.now() < None # False mx.now() > None # True dt.now() > None # TypeError dt.now() < None # TypeError m.pydatetime() == m # False m == m.pydatetime() # False