La classe tzinfo è una classe di base astratta, vale a dire che questa classe non dovrebbe venire istanziata direttamente. Occorre invece derivarne una classe concreta e, come minimo, fornire un'implementazione dei metodi standard di tzinfo necessari per i metodi della classe datetime che si intende utilizzare. Il modulo datetime non fornisce nessuna classe derivata concreta di tzinfo.
Un'istanza di (una classe concreta di) tzinfo può venire passata al costruttore per ogetti di tipo datetime e time. Questi ultimi considerano il tempo espresso dai loro attributi come tempo locale, e l'oggetto tzinfo associato ad essi fornisce metodi per determinare la distanza tra il tempo locale e l'UTC, il nome del fuso orario, l'offset DST (NdT: la differenza di ora legale), tutti relativi ad un oggetto di tipo datetime o time passato come argomento.
Requisiti speciali per effettuare la serializzazione: una classe derivata di tzinfo deve avere un metodo __init__ che possa venire chiamato senza argomenti, altrimenti tale oggetto potrà comunque venire serializzato, ma potrebbe succedere di non poterlo più deserializzare. Questo è un requisito tecnico che potrebbe essere reso meno rigido in futuro.
Una classe derivata concreta di tzinfo può dover implementare i seguenti metodi. Quali metodi esattamente siano necessari dipende dall'uso che si è fatto degli oggetti datetime di tipo ``complesso''. Se avete dei dubbi in proposito, semplicemente implementateli tutti.
self, dt) |
None
.
Altrimenti, il valore restituito deve essere un oggetto
timedelta che specifichi il numero complessivo di minuti
nell'intervallo da -1439 a 1439, limiti inclusi ( 1440 = 24*60; la
grandezza della distanza deve essere inferiore ad un giorno). La
maggior parte delle implementazioni di utcoffset() saranno
probabilmente simili ad una di queste due:
return CONSTANT # classe con distanza fissa da UTC return CONSTANT + self.dst(dt) # classe consapevole delle #+ correzioni per l'ora legale
Se utcoffset() non restituisce None
, allora non
dovrebbe farlo neanche dst().
L'implementazione predefinita di utcoffset() solleva l'eccezione NotImplementedError.
self, dt) |
None
se quest'informazione non è nota. Restituisce
timedelta(0)
se l'ora legale non è applicata. Se l'ora è
applicata, restituisce la correzione di tempo come un oggetto
timedelta (vedete utcoffset() per i dettagli).
Notate che la differenza per l'ora legale, se applicabile, è già stata
aggiunta alla differenza di fuso orario restituita da
utcoffset(), per cui non vi è necessità di chiamare
dst(), a meno che non siate interessati ad ottenere
l'informazione sull'ora legale separatamente. Per esempio,
datetime.timetuple() chiama il metodo dst()
dell'oggetto referenziato dal suo attributo tzinfo per
stabilire come l'opzione tm_isdst dovrebbe venire impostata,
e tzinfo.fromutc() chiama dst() per tener conto dei
cambi di ora legale quado si attraversano i fusi orari.
Un'istanza tz di una classe derivata di tzinfo che modellizzi sia l'ora solare che quella legale deve essere consistente, nel senso che l'espressione:
tz.utcoffset(dt) - tz.dst(dt)
deve restituire lo stesso risultato per ogni oggetto dt di
tipo datetime tale che
dt.tzinfo == tz
.
Per classi derivate di tzinfo bene implementate, questa
espressione corrisponde alla ``differenza standard'', che non dovrebbe
dipendere dalla data o dal tempo, ma solo dalla posizione geografica.
L'implementazione di datetime.astimezone() fa affidamento
su questa assunzione, ma non è in grado di individuarne le
violazioni; è responsabilità del programmatore fare in modo che non
ve ne siano. Se una classe derivata di tzinfo non può
garantire tale condizione, può alternativamente tentare di sostituire
l'implementazione predefinita di tzinfo.fromutc() in modo
da funzionare correttamente con astimezone(),
indipendentemente dal fatto che la condizione sia vera.
La maggior parte delle iplementazioni di dst() probabilmente somiglieranno ad una di queste due:
def dst(self): # una classe con distanza fissa: non tiene conto del DST return timedelta(0)
oppure
def dst(self): # Codice per porre il valore di "dston" e "dstoff" ai tempi di # transizione dell'ora legale per il fuso orario considerato, # basandosi su dt.year ed esprimendo tali tempi in tempo locale. # Quindi: if dston <= dt.replace(tzinfo=None) < dstoff: return timedelta(hours=1) else: return timedelta(0)
L'implementazione predefinita di dst() solleva l'eccezione NotImplementedError.
self, dt) |
None
se il nome di una stringa
non è noto. Notate che la ragione principale per cui questo è un
metodo piuttosto che una stringa di valore costante, consiste nel
fatto che è possibile che una classe derivata di tzinfo
voglia restituire nomi differenti per diversi valori dell'argomento,
specialmente nel caso in cui la classe tzinfo tenga conto
dell'ora legale.
L'implementazione predefinita di tzname() solleva l'eccezione NotImplementedError.
Questi metodi vengono chiamati da un oggetto di tipo datetime
o time, in risposta a chiamate dei loro metodi con lo stesso
nome. Un oggetto datetime passa se stesso come argomento
mentre un oggetto time passa None
come argomento. I
metodi di una classe derivata di tzinfo devono dunque essere
preparati ad accettare come argomento sia un'istanza di
datetime, ossia dt, che None
.
Quando None
viene passato come argomento, il compito di
decidere la risposta migliore viene lasciato al progettista della
classe derivata di tzinfo. Per esempio, restituire None
è appropriato se nell'implementazione della classe si vuole specificare
che gli oggetti di tipo time non sono interessati alla gestione
del fuso orario. Può però essere più utile che utcoffset(None)
restituisca la distanza standard dall'UTC, visto che non vi è altro
modo per stabilire la distanza standard.
Quando viene passato un oggetto datetime come risultato di una
chiamata ad un metodo di datetime, allora dt.tzinfo
sarà uguale a self. I metodi di tzinfo possono fare
affidamento su questo, a meno che il codice utente non chiami
direttamente i metodi della classe tzinfo. La ragione di ciò è
di consentire che i metodi di tzinfo considerino l'argomento
dt come rappresentante il tempo locale, senza preoccuparsi di
oggetti rappresentanti un tempo espresso in fusi orari differenti.
Vi è un altro metodo della classe tzinfo che le classi derivate possono voler sostituire:
self, dt) |
dt.tzinfo
corrisponde a self, e gli attributi di
data e tempo di dt vanno considerati come rappresentanti un
tempo con riferimento UTC. Lo scopo di fromutc() è quello
di correggere gli attributi di data e tempo, resituendo un oggetto
datetime equivalente ma rappresentante il tempo nel fuso orario di
self.
La maggior parte delle classi derivate di tzinfo dovrebbero essere in grado di ereditare l'implementazione predefinita di fromutc() senza problemi. Tale implementazione è abbastanza robusta da gestire fusi orari con differenza oraria costante e fusi orari che tengano conto sia dell'ora solare (standard) che dell'ora legale, ed in quest'ultimo caso può persino gestire i casi in cui i tempi di passaggio all'ora legale cambino da un anno all'altro. Un esempio di una situazione che l'implementazione predefinita di fromutc() potrebbe non gestire sempre correttamente si ha quando la differenza oraria standard (rispetto all'UTC) dipende da una specifica data e tempo passato, situazione che si può verificare per motivi politici. L'implementazione predefinita di astimezone() e fromutc() può non produrre il risultato voluto, se il risultato è una delle ore successive al momento in cui la differenza oraria è cambiata.
Ignorando il codice necessario per gestire i casi di errore, l'implementazione di default di fromutc() funziona così:
def fromutc(self, dt): # solleva l'eccezione ValueError se dt.tzinfo è diverso da self dtoff = dt.utcoffset() dtdst = dt.dst() # solleva ValueError se dtoff o dtdts sono uguali a None delta = dtoff - dtdst # questa è la differenza oraria #+ standard di self if delta: dt += delta # converte nel tempo locale standard dtdst = dt.dst() # solleva l'eccezione ValueError se dtdst vale None if dtdst: return dt + dtdst else: return dt
Esempi di classi derivate di tzinfo:
from datetime import tzinfo, timedelta, datetime ZERO = timedelta(0) HOUR = timedelta(hours=1) # Una classe UTC. class UTC(tzinfo): """UTC""" def utcoffset(self, dt): return ZERO def tzname(self, dt): return "UTC" def dst(self, dt): return ZERO utc = UTC() # Una classe che crea oggetti tzinfo per fusi orari con distanza # oraria fissa dall'UTC. Notate che FixedOffset(0, "UTC") è un # altro modo per creare un oggetto tzinfo rappresentante l'UTC. class FixedOffset(tzinfo): """Distanza fissa in minuti ad est dell'UTC.""" def __init__(self, offset, name): self.__offset = timedelta(minutes = offset) self.__name = name def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self, dt): return ZERO # Una classe che cattura il concetto di tempo locale #+ supportato dalla piattaforma import time as _time STDOFFSET = timedelta(seconds = -_time.timezone) if _time.daylight: DSTOFFSET = timedelta(seconds = -_time.altzone) else: DSTOFFSET = STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET class LocalTimezone(tzinfo): def utcoffset(self, dt): if self._isdst(dt): return DSTOFFSET else: return STDOFFSET def dst(self, dt): if self._isdst(dt): return DSTDIFF else: return ZERO def tzname(self, dt): return _time.tzname[self._isdst(dt)] def _isdst(self, dt): tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1) stamp = _time.mktime(tt) tt = _time.localtime(stamp) return tt.tm_isdst > 0 Local = LocalTimezone() # Una comleta implementazioni delle regole correnti di DST (ora legale) # per i principali fusi orari degli Stati Uniti. def first_sunday_on_or_after(dt): days_to_go = 6 - dt.weekday() if days_to_go: dt += timedelta(days_to_go) return dt # Negli Stati Uniti, il DST comincia alle 2 AM (tempo standard) della # prima domenica di Aprile. DSTSTART = datetime(1, 4, 1, 2) # e termina alle 2 AM (tempo DST: 1 AM tempo standard) dell'ultima # domenica di ottobre, cioè la prima domenica a partire dal 25 Ottobre, # incluso questo giorno. DSTEND = datetime(1, 10, 25, 1) class USTimeZone(tzinfo): def __init__(self, hours, reprname, stdname, dstname): self.stdoffset = timedelta(hours=hours) self.reprname = reprname self.stdname = stdname self.dstname = dstname def __repr__(self): return self.reprname def tzname(self, dt): if self.dst(dt): return self.dstname else: return self.stdname def utcoffset(self, dt): return self.stdoffset + self.dst(dt) def dst(self, dt): if dt is None or dt.tzinfo is None: # Un'eccezione può essere una buona idea qui, in uno od # entrambi i casi. Dipende da come li si vuole trattare. # L'implementazione di default di fromutc() ( chiamata dalla # implementazione di default di astimezone() ) passa un oggetto # datetime "dt" in cui dt.tzinfo è uguale a self. return ZERO assert dt.tzinfo is self # Trova la prima domenica di Aprile e l'ultima di Ottobre. start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) # Non è possibile confrontare oggetti consapevoli con altri # semplicistici, per cui è meglio prima rimuovere da dt # l'informazione sul fuso orario. if start <= dt.replace(tzinfo=None) < end: return HOUR else: return ZERO Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") Central = USTimeZone(-6, "Central", "CST", "CDT") Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Notate le inevitabili sottigliezze necessarie per implementare una classe derivata di tzinfo che tenga conto sia del tempo standard che dell'ora legale, in corrispondenza dei punti di transizione dell'ora legale, due volte all'anno. Per concretezza, considerate il fuso orario Est degli Stati Uniti, dove l' EDT (NdT: East Daylight Time, ora legale per il fuso Est) comincia il minuto successivo alle 1:59 (EST) della prima domenica di Aprile e termina il minuto successivo alle 1:59 (EDT) dell'ultima domenica di Ottobre:
UTC 3:MM 4:MM 5:MM 6:MM 7:MM 8:MM EST 22:MM 23:MM 0:MM 1:MM 2:MM 3:MM EDT 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM start 22:MM 23:MM 0:MM 1:MM 3:MM 4:MM end 23:MM 0:MM 1:MM 1:MM 2:MM 3:MM
Quando l'ora legale inizia (la riga "start"), le lancette degli
orologi locali saltano dalle 1:59 alle 3:00. Un tempo di orologio del
tipo 2:MM non ha proprio senso in quel giorno, per cui
astimezone(Eastern)
non restituirà alcun risultato quando
l'attributo hour
vale 2
nel giorno di inizio del DST.
Perché sia possibile che il metodo astimezone() rispetti
questa garanzia, il metodo rzinfo.dst() deve considerare
l'intervallo di tempo compreso nell'ora mancante (2:MM per la zona
Est) come espresso in ora legale.
Quando l'ora legale termina (la riga "end"), vi è un problema potenzialmente peggiore: vi è infatti un'ora che non può venire indicata in modo non ambiguo con un tempo di orologio locale, vale a dire l'ultima ora del periodo di ora legale. Nella zona Est, questi sono i tempi del tipo 5:MM UTC del giorno in cui l'ora legale finisce. Le lancette degli orologi locali saltano all'indietro dalle 1:59 (ora legale) fino a tornare alle 1:00 (ora solare). Tempi locali espressi nella forma 1:MM sono ambigui. Il metodo astimezone() imita il comportamento del tempo di un orologio locale, facendo quindi corrispondere due ore UTC adiacenti alla stessa ora locale. Nell'esempio della zona Est, i tempi UTC del tipo 5:MM e 6:MM corrispondono entrambi a tempi del tipo 1:MM, una volta convertiti in ora locale. Allo scopo di consentire a astimezone() di garantire questo comportamento, il metodo tzinfo.dst() deve considerare i tempi nella "ora ripetuta" come espressi in ora solare standard. Questo viene facilmente ottenuto, come nell'esempio, rappresentando i tempi DST corrispondenti al passaggio all'ora legale e viceversa nel tempo solare standard del fuso orario interessato.
Applicazioni che non sono in grado di gestire questo tipo di ambiguità dovrebbero evitare di usare classi derivate ibride di tzinfo; non vi sono ambiguità quando si usa l'UTC, o ogni altra classe derivata di tzinfo con una differenza oraria fissa rispetto all'UTC (come ad esempio una classe rappresentante solo l'EST (differenza costante -5 ore) o solo l'EDT (differenza costante -4 ore)).
Vedete Circa questo documento... per informazioni su modifiche e suggerimenti.