Utfasing av egenix-mx-base

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.dateTIMESTAMP 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.DateTimedatetime.date
  • datetime.datetimedatetime.date
  • datetime.datedatetime.date
  • NoneNone (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.DateTimedatetime.datetime
  • datetime.datetimedatetime.datetime
  • datetime.datedatetime.datetime
  • NoneNone (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.DateTimedatetime.datetime
  • datetime.datetimedatetime.datetime
  • datetime.datedatetime.datetime
  • NoneNone (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
Publisert 26. jan. 2021 11:32 - Sist endret 15. apr. 2021 12:17