Overzicht



Datums en tijdstippen zijn gevoelig voor bugs door kleine verschillen. Dit artikel schetst snel de concepten van naive/aware, ISO8601, timestamp (UNIX epoch) bij het werken met tijd in Django en Python, en biedt veilige gebruiksmethoden met voorbeelden.


De twee gezichten van datetime: naive vs aware

datetime objecten worden onderverdeeld op basis van de aanwezigheid van tijdzone-informatie.

  • naive: object zonder tijdzone-informatie (tzinfo=None)

  • aware: object met tijdzone-informatie (tzinfo=...)

De regels voor operaties zijn eenvoudig. Alleen binnen hetzelfde type kunnen optellingen en aftrekkingen worden uitgevoerd.

  • naive - naive ✅ (mogelijk)

  • aware - aware (zelfde tijdzone) ✅ (mogelijk)

  • naive - aware ❌ (TypeError optredend)

from datetime import datetime, timezone

# naive: geen tijdzone-informatie
naive = datetime(2025, 11, 14, 9, 25, 1)                 # tzinfo=None

# aware: tijdzone-informatie (in dit geval UTC) expliciet vermeld
aware = datetime(2025, 11, 14, 9, 25, 1, tzinfo=timezone.utc)

_ = aware - datetime.now(timezone.utc)  # OK (aware - aware)
# _ = aware - datetime.now()            # TypeError (aware - naive)

Django's timezone en USE_TZ



De USE_TZ instelling in settings.py van Django is cruciaal.

  • USE_TZ=True (aanbevolen): django.utils.timezone.now() retourneert een aware object gebaseerd op UTC.

  • USE_TZ=False: retourneert een naive object gebaseerd op lokale servertijd.

Voor de meeste webapplicaties is het het veiligst om USE_TZ=True in te stellen en alle interne logica en gegevensopslag in UTC te houden. Enkel wanneer er aan de gebruiker gepresenteerd wordt, wordt het omgezet naar de lokale tijd.

from django.utils import timezone

# Met USE_TZ=True, retourneert now() altijd een aware UTC object
now = timezone.now()

ISO8601 en isoformat()

De methode datetime.isoformat() genereert een string volgens de ISO8601 standaard.

  • Een UTC aware object bevat meestal een offset zoals 2025-11-14T09:25:01+00:00.

  • Z is een andere notatie die UTC (+00:00) betekent. (bijv. ...T09:25:01Z)

Belangrijke valstrik: De datetime.fromisoformat() van de Python standaardbibliotheek kan het Z achtervoegsel niet direct parseren. Als je met Z notaties te maken hebt, is het veiliger om django.utils.dateparse.parse_datetime() te gebruiken.

from datetime import datetime, timezone
from django.utils.dateparse import parse_datetime

dt = datetime(2025, 11, 14, 9, 25, 1, tzinfo=timezone.utc)
s  = dt.isoformat()                      # '2025-11-14T09:25:01+00:00'

_aware = datetime.fromisoformat(s)       # OK
_aware2 = parse_datetime("2025-11-14T09:25:01Z")  # OK (Django util)

Timestamp (UNIX epoch) goed begrijpen

Timestamp is de waarde die aangeeft hoeveel seconden er zijn verstreken sinds 1970-01-01 00:00:00 UTC (UNIX epoch).

  • Geheel gedeelte: het aantal "seconden" sinds het referentiepunt

  • Decimaal gedeelte: precisie onder het niveau van seconden (microseconden)

De time.time() van Python retourneert de huidige UTC epoch seconden. De .timestamp() methode van een datetime object gebruikt ook dezelfde UTC epoch basis.

import time
from datetime import datetime, timezone

# huidige tijd in epoch (deze waarden zijn feitelijk hetzelfde)
t1 = time.time()
t2 = datetime.now(timezone.utc).timestamp()

# specifieke tijd ↔ epoch wederzijds omzetten
exp_ts = 1731576301.25  # decimaal gedeelte 0.25 seconde (250ms)
exp_dt = datetime.fromtimestamp(exp_ts, tz=timezone.utc)  # aware UTC
back_ts = exp_dt.timestamp()  # 1731576301.25

Timestamp is nuttig wanneer het nodig is om gegevens tussen servers uit te wisselen of om vervaltijden te berekenen, omdat het numerieke berekeningen vermijdt.


Veilige conversie-/rekenpatronen (voorbeeld)

1. String (ISO8601) → datetime

Gebruik parse_datetime om Z achtervoegsels of verschillende offsets veilig te verwerken.

from django.utils.dateparse import parse_datetime
from django.utils import timezone

s = "2025-11-14T09:25:01Z"  # of ...+00:00
dt = parse_datetime(s)

if dt is None:
    raise ValueError("Ongeldige datetime string")

# Als de geparseerde tijd naive is (als er geen offset-informatie was)
if timezone.is_naive(dt):
    # Specificeer expliciet de tijdzone volgens de bedrijfsregels (bijv. als UTC beschouwen)
    from zoneinfo import ZoneInfo
    dt = timezone.make_aware(dt, ZoneInfo("UTC"))

2. datetime (aware) ↔ timestamp

Het is duidelijker om altijd te converteren op basis van timezone.utc.

from datetime import datetime, timezone

dt = datetime(2025, 11, 14, 9, 25, 1, tzinfo=timezone.utc)
ts = dt.timestamp()                               # float
dt2 = datetime.fromtimestamp(ts, tz=timezone.utc) # wederzijdse conversie OK

3. Berekenen van de resterende tijd (seconden) tot verval

Trek twee aware objecten af om een timedelta te krijgen en gebruik .total_seconds().

from datetime import datetime, timezone

exp = datetime(2025, 11, 14, 9, 25, 1, tzinfo=timezone.utc)
now = datetime.now(timezone.utc)

remaining = (exp - now).total_seconds()  # float seconden (negatief mogelijk)

Hoofdpunt: Alle bewerkingen moeten worden uitgevoerd met aware objecten (bij voorkeur UTC) voor maximale veiligheid. Let op: het tz|tzinfo argument voor datetime() moet timezone.utc zijn, en niet django.utils.timezone, maar datetime.timezone.


Veelvoorkomende valstrikken en snelle uithoeken

  • aware - naive operaties: Dit veroorzaakt een TypeError.

    • → Zorg ervoor dat beide aware (bij voorkeur UTC) zijn of beide naive, maar gebruik ze bewust.
  • fromisoformat() en het "Z" achtervoegsel:

    • → De standaardbibliotheek ondersteunt Z niet. Gebruik Django's parse_datetime(), of behandel het met replace("Z", "+00:00").
  • Afhankelijkheid van lokale tijd (datetime.now()):

    • → Dit kan variëren afhankelijk van de serveruitvoeringsomgeving (lokaal, ontwikkelingsserver, productie), wat fouten kan veroorzaken. Interne logica moet altijd op basis van UTC (timezone.now()) worden geschreven.

Conclusie

  • De eerste regel van tijdsberekeningen is niet te mixen tussen naive en aware. Streef naar eenheid met aware (UTC).

  • Gebruik ISO8601-indeling voor het omgaan met strings, maar overweeg eerst parse_datetime() voor parsing die Z kan verwerken.

  • Gebruik timestamps voor numerieke berekeningen zoals het berekenen van vervaltijden.