Corso Python / Date, intervalli e fusi orari con datetime e zoneinfo

Usa date quando il dato rappresenta soltanto un giorno del calendario; usa datetime e zoneinfo quando contano un orario e la sua interpretazione in un fuso orario. Distingui i valori con informazioni di fuso, detti aware, da quelli senza tali informazioni, detti naive, e definisci separatamente le regole di memorizzazione e visualizzazione.

Distinguere giorno, durata e istante

date rappresenta una data di calendario, datetime combina data e ora e timedelta rappresenta un intervallo. Un orario come 09:00 non identifica da solo un istante: 09:00 a Seoul e 09:00 in UTC possono riferirsi a momenti diversi. UTC è il Tempo Coordinato Universale.

Un datetime naive non significa automaticamente UTC. Può rappresentare un’ora locale o una convenzione applicativa, ma quella convenzione deve essere nota. Un datetime aware contiene invece l’informazione necessaria a determinarne l’offset rispetto a UTC nelle condizioni previste dal fuso.

date.fromisoformat interpreta una data in un formato ISO supportato e rifiuta date impossibili con ValueError. Aggiungere timedelta(days=30) significa aggiungere trenta giorni, non avanzare di un mese del calendario: il comportamento a fine mese richiede una scelta esplicita. La documentazione di datetime distingue i tipi e le relative operazioni.

Convertire un istante UTC nell’ora di Seoul

Salva il codice completo come dates.py, in UTF-8, in una cartella di esercizio. Zoneinfo è disponibile nella libreria standard da Python 3.9. Se il sistema non dispone dei dati IANA dei fusi orari e ottieni ZoneInfoNotFoundError, installa il pacchetto dati nel tuo ambiente di progetto con python -m pip install tzdata, usando lo stesso interprete del programma.

Dal terminale con quell’interprete selezionato esegui python dates.py. Fuori dall’ambiente virtuale su macOS o Ubuntu usa python3 se è il nome scelto. Il programma usa una data fissa, quindi il risultato non dipende dall’orologio attuale del computer.

from datetime import date, datetime, timedelta, timezone
from zoneinfo import ZoneInfo

day = date.fromisoformat("2026-10-17")
print(day + timedelta(days=7))
utc = datetime(2026, 10, 17, 0, 0, tzinfo=timezone.utc)
seoul = utc.astimezone(ZoneInfo("Asia/Seoul"))
print(utc.isoformat())
print(seoul.isoformat())

Il risultato previsto è:

2026-10-24
2026-10-17T00:00:00+00:00
2026-10-17T09:00:00+09:00

La prima operazione aggiunge sette giorni a una data. Le altre righe descrivono lo stesso istante con due rappresentazioni: mezzanotte UTC e le 09:00 a Seoul, con offset +09:00. Isoformat rende visibile l’offset; non cambia il momento rappresentato.

Come confronto locale puoi sostituire nella sola conversione Asia/Seoul con Europe/Rome: per questo istante del 17 ottobre 2026, i dati del fuso producono 02:00 con offset +02:00. Non fissare quell’offset per ogni data dell’anno: il nome IANA identifica regole che possono dipendere dalla data e cambiare nel tempo.

Confrontare conversione e assegnazione del fuso

astimezone conserva l’istante e cambia i campi visualizzati secondo il fuso di destinazione. replace(tzinfo=...) mantiene invece i numeri di data e ora e associa loro un’interpretazione di fuso: non sostituisce in generale una conversione.

Salva il secondo programma completo come zones.py e avvialo con python zones.py dalla stessa cartella. Verifica prima che i dati dei fusi siano disponibili anche nell’interprete con cui esegui questo file.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo
utc = datetime(2026, 10, 17, 0, 0, tzinfo=timezone.utc)
print(utc.astimezone(ZoneInfo("Asia/Seoul")).isoformat())
print(utc.replace(tzinfo=ZoneInfo("Asia/Seoul")).isoformat())
try:
    print(datetime(2026, 10, 17) - utc)
except TypeError:
    print("naive and aware cannot be subtracted")

Il risultato previsto è:

2026-10-17T09:00:00+09:00
2026-10-17T00:00:00+09:00
naive and aware cannot be subtracted

La seconda riga conserva 00:00 ma vi associa +09:00, identificando quindi un istante diverso dalla prima. L’ultima operazione tenta di sottrarre un datetime naive da uno aware: Python la rifiuta perché manca una base comune di interpretazione. Non risolvere l’errore applicando arbitrariamente UTC a un valore la cui provenienza non conosci.

Zoneinfo usa i dati IANA del sistema oppure il pacchetto tzdata disponibile nell’ambiente. Se mancano entrambi, la costruzione del fuso può fallire. La documentazione sulle fonti dei dati di zoneinfo descrive la ricerca e la dipendenza consigliata per applicazioni multipiattaforma. L’installazione del pacchetto non cambia il fuso impostato nel sistema operativo.

Definire memorizzazione e prove sui confini di data

Per un evento avvenuto in un preciso istante, considera una rappresentazione coerente in UTC o con offset esplicito e converti per la visualizzazione. Per un dato che significa soltanto «giorno della spesa», una date può essere più chiara di un’ora di mezzanotte inventata. Conserva il significato del dato anche nelle esportazioni.

Se salvi date o datetime in JSON, definisci la conversione in stringa e la lettura corrispondente: json non serializza automaticamente tutti gli oggetti datetime. Una stringa ISO con offset permette di ritrovare un offset, ma non conserva necessariamente il nome IANA e tutte le sue regole future; salva anche quel nome se è parte del requisito.

  1. Nel primo programma cambia soltanto l’ora UTC da 0 a 23. La conversione a Seoul deve mostrare 2026-10-18T08:00:00+09:00, cioè il giorno successivo.
  2. In una prova separata sostituisci la stringa di data con 2026-02-30: fromisoformat deve generare ValueError.
  3. Confronta in zones.py astimezone e replace e identifica quale riga conserva l’istante originale.
  4. Ripristina data e ora iniziali e verifica nuovamente i risultati indicati.

Per distribuzioni riproducibili gestisci anche la versione dei dati dei fusi, oltre al codice Python. Un cambio nelle regole di un paese può modificare conversioni future senza che tu abbia cambiato il programma: per questo una convenzione di memorizzazione chiara conta quanto la chiamata usata per visualizzare l’ora.