PEP 495 – Lokale Zeit-Diskriminierung
- Autor:
- Alexander Belopolsky <alexander.belopolsky at gmail.com>, Tim Peters <tim.peters at gmail.com>
- Discussions-To:
- Datetime-SIG Liste
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 02-Aug-2015
- Python-Version:
- 3.6
- Resolution:
- Datetime-SIG Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Begründung
- Terminologie
- Vorschlag
- Implementierungen von tzinfo in der Standardbibliothek
- Richtlinien für neue tzinfo-Implementierungen
- Temporäre Arithmetik und Vergleichsoperatoren
- Rückwärts- und Vorwärtskompatibilität
- Fragen und Antworten
- Implementierung
- Urheberrecht
- Bildnachweis
Zusammenfassung
Dieser PEP fügt Instanzen der Klassen datetime.time und datetime.datetime ein neues Attribut fold hinzu, das verwendet werden kann, um zwischen zwei Zeitpunkten zu unterscheiden, für die die lokale Zeit dieselbe ist. Die zulässigen Werte für das fold-Attribut sind 0 und 1, wobei 0 dem früheren und 1 dem späteren der beiden möglichen Interpretationen einer mehrdeutigen lokalen Zeit entspricht.
Begründung
An den meisten Orten der Welt gab und gibt es Zeiten, in denen die lokale Uhr zurückgestellt wird. [1] Zu diesen Zeiten werden Intervalle eingeführt, in denen die lokale Uhr zweimal am selben Tag die gleiche Zeit anzeigt. In diesen Situationen reichen die Informationen, die auf einer lokalen Uhr angezeigt werden (oder in einer Python-Datetime-Instanz gespeichert sind), nicht aus, um einen bestimmten Zeitpunkt zu identifizieren. Die vorgeschlagene Lösung besteht darin, den datetime-Instanzen ein Attribut mit den Werten 0 und 1 hinzuzufügen, das die beiden mehrdeutigen Zeiten auflistet.
Terminologie
Wenn Uhren zurückgestellt werden, sagen wir, dass ein Fold [2] in der Zeit erzeugt wird. Wenn Uhren vorgestellt werden, wird eine Lücke erzeugt. Eine lokale Zeit, die in den Fold fällt, wird als mehrdeutig bezeichnet. Eine lokale Zeit, die in die Lücke fällt, wird als fehlend bezeichnet.
Vorschlag
Das „fold“-Attribut
Wir schlagen vor, den Instanzen der Klassen datetime.time und datetime.datetime ein Attribut namens fold hinzuzufügen. Dieses Attribut sollte den Wert 0 für alle Instanzen haben, außer für diejenigen, die den zweiten (chronologisch) Zeitpunkt in einem mehrdeutigen Fall darstellen. Für diese Instanzen ist der Wert 1. [3]
fold=1 hat, wird als ungültige Zeit bezeichnet (oder kurz: ungültig), aber Benutzer werden nicht daran gehindert, ungültige Instanzen zu erstellen, indem sie fold=1 an einen Konstruktor oder eine replace()-Methode übergeben. Dies ähnelt der aktuellen Situation mit den Instanzen, die in die Frühjahreslücke fallen. Solche Instanzen stellen keine gültige Zeit dar, aber weder die Konstruktoren noch die replace()-Methoden prüfen, ob die von ihnen erzeugten Instanzen gültig sind. Darüber hinaus legt dieser PEP fest, wie sich verschiedene Funktionen verhalten, wenn sie mit einer ungültigen Instanz aufgerufen werden.Betroffene APIs
Attribute
Instanzen der Klassen datetime.time und datetime.datetime erhalten ein neues Attribut fold mit zwei möglichen Werten: 0 und 1.
Konstruktoren
Die Methoden __new__ der Klassen datetime.time und datetime.datetime erhalten ein neues schlüsselwortexklusives Argument namens fold mit dem Standardwert 0. Der Wert des Arguments fold wird verwendet, um den Wert des Attributs fold in der zurückgegebenen Instanz zu initialisieren.
Methoden
Die Methoden replace() der Klassen datetime.time und datetime.datetime erhalten ein neues schlüsselwortexklusives Argument namens fold. Es verhält sich ähnlich wie die anderen replace()-Argumente: Wenn das Argument fold angegeben und mit dem Wert 0 oder 1 versehen ist, wird das von replace() zurückgegebene neue Instanz das Attribut fold auf diesen Wert gesetzt haben. In CPython löst jeder nicht-ganzzahlige Wert von fold einen TypeError aus, aber andere Implementierungen können zulassen, dass der Wert None sich genauso verhält, als wäre fold nicht angegeben. [4] (Dies ist eine Anspielung auf den bestehenden Unterschied in der Behandlung von None-Argumenten in anderen Positionen dieser Methode über Python-Implementierungen hinweg; es ist nicht beabsichtigt, die Tür für zukünftige alternative Interpretationen von fold=None offen zu lassen.) Wenn das Argument fold nicht angegeben ist, wird der ursprüngliche Wert des Attributs fold in das Ergebnis kopiert.
None für alle anderen Attribute in replace(), um „keine Änderung des vorhandenen Attributs“ zu bedeuten.C-API
Zugriffs-Makros werden definiert, um den Wert von fold aus PyDateTime_DateTime und PyDateTime_Time-Objekten zu extrahieren.
int PyDateTime_DATE_GET_FOLD(PyDateTime_DateTime *o)
Gibt den Wert von fold als C int zurück.
int PyDateTime_TIME_GET_FOLD(PyDateTime_Time *o)
Gibt den Wert von fold als C int zurück.
Neue Konstruktoren werden definiert, die ein zusätzliches Argument zum Festlegen des Werts von fold in der erstellten Instanz erhalten.
PyObject* PyDateTime_FromDateAndTimeAndFold(
int year, int month, int day, int hour, int minute,
int second, int usecond, int fold)
Gibt ein datetime.datetime-Objekt mit dem angegebenen Jahr, Monat, Tag, Stunde, Minute, Sekunde, Mikrosekunde und Fold zurück.
PyObject* PyTime_FromTimeAndFold(
int hour, int minute, int second, int usecond, int fold)
Gibt ein datetime.time-Objekt mit der angegebenen Stunde, Minute, Sekunde, Mikrosekunde und Fold zurück.
Betroffenes Verhalten
Wie spät ist es?
Die Methode datetime.now(), ohne Argumente aufgerufen, setzt fold=1, wenn sie die zweite der beiden mehrdeutigen Zeiten in einem System-Lokalzeit-Fold zurückgibt. Wenn sie mit einem tzinfo-Argument aufgerufen wird, wird der Wert von fold durch die tzinfo.fromutc()-Implementierung bestimmt. Wenn eine Instanz der Klasse datetime.timezone (die Standardbibliotheks-Unterklasse für feste Offset-Zeitzonen, z. B. datetime.timezone.utc) als tzinfo übergeben wird, hat die zurückgegebene Datetime-Instanz immer fold=0. Die Methode datetime.utcnow() ist davon unberührt.
Konvertierung von naiv zu bewusst
Es wird eine neue Funktion vorgeschlagen, um die Konvertierung von naiven Datetime-Instanzen in bewusste zu erleichtern.
Die Methode astimezone() funktioniert nun auch für naive self. In diesem Fall wird die System-Lokalzeitzone angenommen, und das fold-Flag wird verwendet, um zu bestimmen, welche Lokalzeitzone im mehrdeutigen Fall gültig ist.
Zum Beispiel, auf einem System, das auf die US/Eastern-Zeitzone eingestellt ist
>>> dt = datetime(2014, 11, 2, 1, 30)
>>> dt.astimezone().strftime('%D %T %Z%z')
'11/02/14 01:30:00 EDT-0400'
>>> dt.replace(fold=1).astimezone().strftime('%D %T %Z%z')
'11/02/14 01:30:00 EST-0500'
Eine Folge davon ist, dass datetime.now(tz) vollständig äquivalent zu datetime.now().astimezone(tz) ist (vorausgesetzt, tz ist eine Instanz einer Post-PEP-Implementierung von tzinfo, d.h. eine, die fold korrekt verarbeitet und setzt).
Konvertierung von POSIX-Sekunden seit EPOCH
Die statische Methode fromtimestamp() von datetime.datetime setzt das Attribut fold im zurückgegebenen Objekt entsprechend.
Zum Beispiel, auf einem System, das auf die US/Eastern-Zeitzone eingestellt ist
>>> datetime.fromtimestamp(1414906200)
datetime.datetime(2014, 11, 2, 1, 30)
>>> datetime.fromtimestamp(1414906200 + 3600)
datetime.datetime(2014, 11, 2, 1, 30, fold=1)
Konvertierung zu POSIX-Sekunden seit EPOCH
Die Methode timestamp() von datetime.datetime gibt unterschiedliche Werte für datetime.datetime-Instanzen zurück, die sich nur durch den Wert ihres fold-Attributs unterscheiden, und zwar nur dann, wenn diese Instanzen eine mehrdeutige oder fehlende Zeit darstellen.
Wenn eine datetime.datetime-Instanz dt eine mehrdeutige Zeit darstellt, gibt es zwei Werte s0 und s1, so dass
datetime.fromtimestamp(s0) == datetime.fromtimestamp(s1) == dt
(Dies liegt daran, dass == den Wert von fold ignoriert – siehe unten.)
In diesem Fall gibt dt.timestamp() den kleineren der Werte s0 und s1 zurück, wenn dt.fold == 0 ist, und den größeren sonst.
Zum Beispiel, auf einem System, das auf die US/Eastern-Zeitzone eingestellt ist
>>> datetime(2014, 11, 2, 1, 30, fold=0).timestamp()
1414906200.0
>>> datetime(2014, 11, 2, 1, 30, fold=1).timestamp()
1414909800.0
Wenn eine datetime.datetime-Instanz dt eine fehlende Zeit darstellt, gibt es keinen Wert s, für den
datetime.fromtimestamp(s) == dt
aber wir können zwei „nice to know“-Werte von s bilden, die sich um die Größe der Lücke in Sekunden unterscheiden. Einer ist der Wert von s, der dt in einer Zeitzone entsprechen würde, in der der UTC-Offset immer derselbe ist wie der Offset unmittelbar vor der Lücke, und der andere ist der ähnliche Wert, aber in einer Zeitzone, in der der UTC-Offset immer derselbe ist wie der Offset unmittelbar nach der Lücke.
Der von dt.timestamp() zurückgegebene Wert für ein fehlendes dt ist der größere der beiden „nice to know“-Werte, wenn dt.fold == 0 ist, und der kleinere sonst. (Dies ist kein Tippfehler – es ist absichtlich rückwärts gerichtet gegenüber der Regel für mehrdeutige Zeiten.)
Zum Beispiel, auf einem System, das auf die US/Eastern-Zeitzone eingestellt ist
>>> datetime(2015, 3, 8, 2, 30, fold=0).timestamp()
1425799800.0
>>> datetime(2015, 3, 8, 2, 30, fold=1).timestamp()
1425796200.0
Bewusste Datetime-Instanzen
Benutzer von Pre-PEP-Implementierungen von tzinfo werden keine Änderungen im Verhalten ihrer bewussten Datetime-Instanzen feststellen. Zwei solche Instanzen, die sich nur durch den Wert des fold-Attributs unterscheiden, sind durch nichts anderes als durch direkten Zugriff auf den fold-Wert unterscheidbar. (Dies liegt daran, dass diese Pre-PEP-Implementierungen das fold-Attribut nicht verwenden.)
Wenn andererseits tzinfo eines Objekts auf eine Fold-fähige Implementierung gesetzt ist, dann beeinflusst im Fold oder in der Lücke der Wert von fold das Ergebnis mehrerer Methoden: utcoffset(), dst(), tzname(), astimezone(), strftime() (wenn die Direktive „%Z“ oder „%z“ in der Formatangabe verwendet wird), isoformat() und timetuple().
Kombinieren und Aufteilen von Datum und Zeit
Die Methode datetime.datetime.combine() kopiert den Wert des Attributs fold in die resultierende datetime.datetime-Instanz.
Die Methode datetime.datetime.time() kopiert den Wert des Attributs fold in die resultierende datetime.time-Instanz.
Pickles
Der Wert des Fold-Attributs wird nur in Pickles gespeichert, die mit dem Protokollversion 4 (eingeführt in Python 3.4) oder höher erstellt wurden.
Die Pickle-Größen für die Objekte datetime.datetime und datetime.time ändern sich nicht. Der fold-Wert wird im ersten Bit des 3. Bytes der datetime.datetime-Pickle-Nutzlast kodiert; und im ersten Bit des 1. Bytes der datetime.time-Nutzlast. In der aktuellen Implementierung werden diese Bytes verwendet, um die Monats- (1-12) und Stundenwerte (0-23) zu speichern, und das erste Bit ist immer 0. Wir haben diese Bytes gewählt, da sie die einzigen sind, die vom aktuellen Unpickle-Code überprüft werden. Das Laden von Pre-PEP fold=1-Pickles in einem Pre-PEP-Python führt daher zu einer Ausnahme anstelle einer Instanz mit Komponenten außerhalb des Bereichs.
Implementierungen von tzinfo in der Standardbibliothek
Es werden keine neuen Implementierungen der abstrakten Klasse datetime.tzinfo in diesem PEP vorgeschlagen. Die bestehenden (Fest-Offset-)Zeitzonen führen keine mehrdeutigen Lokalzeiten ein, und ihre utcoffset()-Implementierung gibt unabhängig vom Wert von fold denselben konstanten Wert zurück wie bisher.
Die grundlegende Implementierung von fromutc() in der abstrakten Klasse datetime.tzinfo bleibt unverändert. Sie wird derzeit nirgends in der Standardbibliothek verwendet, da die einzige enthaltene tzinfo-Implementierung (die Klasse datetime.timezone, die Zeitzonen mit festem Offset implementiert) fromutc() überschreibt. Die unveränderte Beibehaltung der Standardimplementierung hat den Vorteil, dass Pre-PEP-Drittanbieter-Implementierungen, die von der Standard- fromutc() erben, nicht versehentlich betroffen sind.
Richtlinien für neue tzinfo-Implementierungen
Implementierer von konkreten datetime.tzinfo-Unterklassen, die variable UTC-Offsets (aufgrund von MESZ und anderen Ursachen) unterstützen möchten, sollten die folgenden Richtlinien befolgen.
Ignoranz ist Seligkeit
Neue Implementierungen der Methoden utcoffset(), tzname() und dst() sollten den Wert von fold ignorieren, es sei denn, sie werden bei mehrdeutigen oder fehlenden Zeiten aufgerufen.
Im Fold
Neue Unterklassen sollten die fromutc()-Methode der Basisklasse überschreiben und so implementieren, dass in allen Fällen, in denen zwei verschiedene UTC-Zeiten u0 und u1 (u0 <u1) derselben lokalen Zeit t entsprechen, fromutc(u0) eine Instanz mit fold=0 zurückgibt und fromutc(u1) eine Instanz mit fold=1 zurückgibt. In allen anderen Fällen sollte die zurückgegebene Instanz fold=0 haben.
Die Methoden utcoffset(), tzname() und dst() sollten den Wert des Fold-Attributs verwenden, um zu bestimmen, ob eine ansonsten mehrdeutige Zeit t der Zeit vor oder nach dem Übergang entspricht. Per Definition ist utcoffset() vor und kleiner nach jedem Übergang, der einen Fold erzeugt. Die von tzname() und dst() zurückgegebenen Werte können vom Wert des fold-Attributs abhängen oder auch nicht, je nach Art des Übergangs.
Die obige Skizze veranschaulicht die Beziehung zwischen UTC und lokaler Zeit um einen Rückstell-Übergang herum. Die Zickzacklinie ist ein Graph der von fromutc() implementierten Funktion. Zwei Intervalle auf der UTC-Achse, die an den Übergangspunkt angrenzen und die Größe der Zeitverschiebung beim Übergang haben, werden auf dasselbe Intervall auf der lokalen Achse abgebildet. Neue Implementierungen der fromutc()-Methode sollten das Fold-Attribut auf 1 setzen, wenn self sich im gelb markierten Bereich auf der UTC-Achse befindet. (Alle Intervalle sollten als links geschlossen und rechts offen behandelt werden.)
Achten Sie auf die Lücke
Die Methode fromutc() sollte niemals eine Zeit in der Lücke erzeugen.
Wenn die Methoden utcoffset(), tzname() oder dst() auf eine lokale Zeit aufgerufen werden, die in eine Lücke fällt, sollten die Regeln vor dem Übergang verwendet werden, wenn fold=0 ist. Andernfalls sollten die Regeln nach dem Übergang verwendet werden.
Die obige Skizze veranschaulicht die Beziehung zwischen UTC und lokaler Zeit um einen Vorstell-Übergang herum. Beim Übergang wird die lokale Uhr vorgestellt, wobei die Zeiten in der Lücke übersprungen werden. Für die Bestimmung der Werte von utcoffset(), tzname() und dst() wird die Linie vor dem Übergang nach vorne verlängert, um die UTC-Zeit zu finden, die der Zeit in der Lücke mit fold=0 entspricht, und für Instanzen mit fold=1 wird die Linie nach dem Übergang nach hinten verlängert.
Zusammenfassung der Regeln bei einem Übergang
Bei mehrdeutigen/fehlenden Zeiten sollte utcoffset() Werte gemäß der folgenden Tabelle zurückgeben
| fold=0 | fold=1 | |
|---|---|---|
| Fold | oldoff | newoff = oldoff - delta |
| Gap | oldoff | newoff = oldoff + delta |
wobei oldoff (newoff) der UTC-Offset vor (nach) dem Übergang ist und delta die absolute Größe des Folds oder der Lücke ist.
Beachten Sie, dass die Interpretation des Fold-Attributs sowohl im Fold- als auch im Gap-Fall konsistent ist. In beiden Fällen bedeutet fold=0 (fold=1), die fromutc()-Linie vor (nach) dem Übergang zu verwenden, um die UTC-Zeit zu finden. Nur im „Fold“-Fall sind die UTC-Zeiten u0 und u1 „reale“ Lösungen für die Gleichung fromutc(u) == t, während sie im „Gap“-Fall „imaginäre“ Lösungen sind.
Die MESZ-Übergänge
Bei einer fehlenden Zeit, die zu Beginn der MESZ eingeführt wurde, sollten die von den Methoden utcoffset() und dst() zurückgegebenen Werte wie folgt sein
| fold=0 | fold=1 | |
|---|---|---|
| utcoffset() | stdoff | stdoff + dstoff |
| dst() | zero | dstoff |
Bei einer mehrdeutigen Zeit, die am Ende der MESZ eingeführt wurde, sollten die von den Methoden utcoffset() und dst() zurückgegebenen Werte wie folgt sein
| fold=0 | fold=1 | |
|---|---|---|
| utcoffset() | stdoff + dstoff | stdoff |
| dst() | dstoff | zero |
wobei stdoff der Standard-Offset (nicht-MESZ) ist, dstoff die MESZ-Korrektur ist (typischerweise dstoff = timedelta(hours=1)) und zero = timedelta(0).
Temporäre Arithmetik und Vergleichsoperatoren
In der Mathematik war er größerAls Tycho Brahe oder Erra PaterDenn er, nach geometrischer Skala,Konnte die Größe von Krügen Ale messen;Löse, mit Sinus und Tangens gerade,Ob Brot oder Butter Gewicht fehlte,Und sagte weise, wie spät es am Tage warDie Uhr schlägt nach Algebra.– „Hudibras“ von Samuel Butler
Der Wert des Attributs fold wird bei allen Operationen mit naiven Datetime-Instanzen ignoriert. Folglich werden naive datetime.datetime- oder datetime.time-Instanzen, die sich nur durch den Wert von fold unterscheiden, als gleich verglichen. Anwendungen, die solche Instanzen unterscheiden müssen, sollten den Wert von fold explizit überprüfen oder diese Instanzen in eine Zeitzone konvertieren, die keine mehrdeutigen Zeiten hat (wie z. B. UTC).
Der Wert von fold wird auch ignoriert, wenn ein timedelta zu oder von einer Datetime-Instanz addiert oder subtrahiert wird, die entweder bewusst oder naiv sein kann. Das Ergebnis der Addition (Subtraktion) eines timedeltas zu (von) einer Datetime hat immer fold auf 0 gesetzt, auch wenn die ursprüngliche Datetime-Instanz fold=1 hatte.
Es werden keine Änderungen an der Art und Weise vorgeschlagen, wie die Differenz t - s für Datetime-Instanzen t und s berechnet wird. Wenn beide Instanzen naiv sind oder t.tzinfo dieselbe Instanz wie s.tzinfo ist (t.tzinfo is s.tzinfo ergibt True), dann ist t - s ein timedelta d, so dass s + d == t. Wie im vorherigen Absatz erläutert, ignoriert die timedelta-Addition sowohl die Attribute fold als auch tzinfo, und das gilt auch für intra-zone oder naive Datetime-Subtraktion.
Naive und intra-zone Vergleiche ignorieren den Wert von fold und geben die gleichen Ergebnisse zurück wie bisher. (Dies ist die einzige Möglichkeit, die Abwärtskompatibilität zu gewährleisten. Wenn Sie einen bewussten intra-zone Vergleich benötigen, der fold verwendet, konvertieren Sie beide Seiten zuerst in UTC.)
Die inter-zone Subtraktion wird wie bisher definiert: t - s wird berechnet als (t - t.utcoffset()) - (s - s.utcoffset()).replace(tzinfo=t.tzinfo), aber das Ergebnis hängt von den Werten von t.fold und s.fold ab, wenn entweder t.tzinfo oder s.tzinfo Post-PEP ist. [5]
s == t, aber s - u != t - u. Solche Paradoxien sind nicht wirklich neu und sind dem Überladen des Minusoperators, der für intra- und inter-zone Operationen unterschiedlich behandelt wird, inhärent. Zum Beispiel kann man leicht Datetime-Instanzen t und s mit einer variablen Offset- tzinfo und einer Datetime u mit tzinfo=timezone.utc konstruieren, so dass (t - u) - (s - u) != t - s. Die Erklärung für dieses Paradox ist, dass die Minuse in den Klammern und die beiden anderen Minuse drei verschiedene Operationen sind: inter-zone Datetime-Subtraktion, timedelta-Subtraktion und intra-zone Datetime-Subtraktion, die jeweils die mathematischen Eigenschaften der Subtraktion separat haben, aber nicht, wenn sie in einem einzigen Ausdruck kombiniert werden.Bewusster Datetime-Gleichheitsvergleich
Die bewussten Datetime-Vergleichsoperatoren funktionieren wie bisher, wobei die Ergebnisse indirekt vom Wert von fold beeinflusst werden, wann immer der Wert utcoffset() eines der Operanden davon abhängt, mit einer Ausnahme. Wann immer einer oder beide Operanden im inter-zone Vergleich so sind, dass sein utcoffset() vom Wert seines fold-Attributs abhängt, ist das Ergebnis False. [6]
Formal lässt sich t == s, wenn t.tzinfo is s.tzinfo zu False ausgewertet wird, wie folgt definieren. Sei toutc(t, fold) eine Funktion, die eine bewusste Datetime-Instanz t nimmt und eine naive Instanz zurückgibt, die dieselbe Zeit in UTC darstellt, unter Annahme eines gegebenen Werts von fold.
def toutc(t, fold):
u = t - t.replace(fold=fold).utcoffset()
return u.replace(tzinfo=None)
Dann ist t == s äquivalent zu
toutc(t, fold=0) == toutc(t, fold=1) == toutc(s, fold=0) == toutc(s, fold=1)
Rückwärts- und Vorwärtskompatibilität
Dieser Vorschlag wird wenig Auswirkungen auf Programme haben, die das fold-Flag nicht explizit lesen oder Zeitzonenimplementierungen verwenden, die dies tun. Die einzige sichtbare Änderung für solche Programme wird sein, dass Konvertierungen von und zu POSIX-Zeitstempeln nun korrekt (bis auf Rundungsfehler bei Gleitkommazahlen) hin- und zurücklaufen. Programme, die einen Workaround für das alte fehlerhafte Verhalten implementiert haben, müssen möglicherweise geändert werden.
Von älteren Programmen erzeugte Pickles bleiben vollständig abwärtskompatibel. Nur Datetime/Time-Instanzen mit fold=1, die in den neuen Versionen gepickelt wurden, werden von älteren Python-Versionen nicht mehr lesbar sein. Pickles von Instanzen mit fold=0 (was der Standard ist) bleiben unverändert.
Fragen und Antworten
Warum nicht die neue Flagge „isdst“ nennen?
Eine nicht-technische Antwort
- Alice: Bob – lass uns morgen um 01:30 Uhr eine Sternenbeobachtungsparty machen!
- Bob: Soll ich annehmen, dass die Sommerzeit für die angegebene Zeit in Kraft ist oder nicht?
- Alice: Hä?
- Bob: Alice – lass uns morgen um 01:30 Uhr eine Sternenbeobachtungsparty machen!
- Alice: Weißt du, Bob, 01:30 Uhr wird morgen zweimal vorkommen. An welche Zeit denkst du?
- Bob: Daran habe ich nicht gedacht, aber nehmen wir die erste.
(die gleichen Charaktere, eine Stunde später)
- Bob: Alice – dieses Py-O-Clock-Gadget von mir fragt mich nach der Wahl zwischen fold=0 und fold=1, wenn ich es für morgen 01:30 Uhr einstelle. Was soll ich tun?
- Alice: Ich habe noch nie von einem Py-O-Clock gehört, aber ich schätze, fold=0 ist das erste 01:30 Uhr und fold=1 ist das zweite.
Ein technischer Grund
Während das Feld tm_isdst des time.struct_time-Objekts zur Entambiguierung lokaler Zeiten in Bezug auf den "fold" verwendet werden kann, sind die Semantiken einer solchen Entambiguierung völlig anders als der Vorschlag in diesem PEP.
Das Hauptproblem mit dem Feld tm_isdst ist, dass es unmöglich ist zu wissen, welcher Wert für tm_isdst angemessen ist, ohne die Details über die Zeitzone zu kennen, die nur der tzinfo-Implementierung zur Verfügung stehen. Daher ist tm_isdst zwar nützlich bei der **Ausgabe** von Methoden wie time.localtime, aber umständlich als **Eingabe** für Methoden wie time.mktime.
Wenn der Programmierer einen nicht-negativen Wert von tm_isdst für time.mktime falsch angegeben hat, wird das Ergebnis eine Zeit sein, die 1 Stunde daneben liegt. Da es selten eine Möglichkeit gibt, etwas über die Sommerzeit **vor** einem Aufruf von time.mktime zu wissen, ist die einzig sinnvolle Wahl normalerweise tm_isdst=-1.
Im Gegensatz zu tm_isdst hat das vorgeschlagene fold-Attribut keinen Einfluss auf die Interpretation der Datetime-Instanz, es sei denn, es wären ohne dieses Attribut zwei (oder keine) Interpretationen möglich.
Da es sehr verwirrend wäre, etwas namens isdst zu haben, das nicht dieselben Semantiken wie tm_isdst hat, benötigen wir einen anderen Namen. Außerdem hat die Klasse datetime.datetime bereits eine Methode namens dst(), und wenn wir fold "isdst" nennen würden, gäbe es zwangsläufig Situationen, in denen "isdst" Null wäre, aber dst() nicht, oder umgekehrt.
Warum „fold“?
Vorgeschlagen von Guido van Rossum und von einem (aber zunächst von einem anderen) Autor bevorzugt. Ein Konsens wurde nach der Änderung der erlaubten Werte für das Attribut von False/True auf 0/1 erreicht. Das Nomen "fold" hat die richtigen Konnotationen und einfache mnemonische Regeln, lädt aber gleichzeitig nicht zu unbegründeten Annahmen ein.
Was ist „first“?
Dies war ein Arbeitsname des Attributs, der ursprünglich gewählt wurde, da die offensichtliche Alternative ("second") mit dem bestehenden Attribut kollidiert. Er wurde hauptsächlich aus dem Grund abgelehnt, dass True ein Standardwert wäre.
Die folgenden alternativen Namen wurden ebenfalls in Betracht gezogen.
- später
- Ein knapper Konkurrent zu "fold". Ein Autor lehnt es ab, da es mit dem ähnlich passenden "latter" verwechselt werden kann, aber im Zeitalter von Auto-Completion überall ist dies eine geringe Überlegung. Ein stärkerer Einwand mag sein, dass im Falle einer fehlenden Zeit wir eine
later=TrueInstanz haben werden, die durch.astimezone(timezone.utc)in eine frühere Zeit konvertiert wird als die mitlater=False. Aber auch dies kann als wünschenswerte Anzeige interpretiert werden, dass die ursprüngliche Zeit ungültig ist. - welcher
- Der ursprüngliche Platzhaltername für den Index des
localtime-Funktionszweigs wurde unabhängig vorgeschlagen für den Namen des Entambiguierungsattributs und erhielt einige Unterstützung. - wiederholt
- Erhielt keine Unterstützung auf der Mailingliste.
- ltdf
- (Local Time Disambiguation Flag) - kurz und niemand wird versuchen zu erraten, was es bedeutet, ohne die Doku zu lesen. (Diese Abkürzung wurde in PEP-Diskussionen mit der Bedeutung
ltdf=Falseist die frühere verwendet, von denen, die keine der Alternativen unterstützen wollten.)
Reichen zwei Werte aus?
Es wurden mehrere Gründe angeführt, um einen None- oder -1-Wert für das fold-Attribut zuzulassen: Abwärtskompatibilität, Analogie zu tm_isdst und strenge Überprüfung auf ungültige Zeiten.
Abwärtskompatibilität
Es wurde vorgeschlagen, dass die Abwärtskompatibilität verbessert werden könnte, wenn der Standardwert des fold-Flags None wäre, was signalisieren würde, dass das Verhalten vor dem PEP angefordert wird. Basierend auf der unten stehenden Analyse glauben wir, dass die vorgeschlagenen Änderungen mit dem fold=0-Standard ausreichend abwärtskompatibel sind.
Dieses PEP bietet nur drei Möglichkeiten für ein Programm, zu erkennen, dass zwei ansonsten identische Datetime-Instanzen unterschiedliche Werte für fold haben: (1) eine explizite Prüfung des fold-Attributs; (2) wenn die Instanzen naiv sind - Konvertierung in eine andere Zeitzone mittels der astimezone()-Methode; und (3) Konvertierung in float mittels der timestamp()-Methode.
Da fold ein neues Attribut ist, ist die erste Option für bestehende Programme nicht verfügbar. Beachten Sie, dass Option (2) nur für naive Datumsangaben funktioniert, die zufällig in einem "fold" oder einer Lücke in der Systemzeitzone liegen. In allen anderen Fällen wird der Wert von fold bei der Konvertierung ignoriert, es sei denn, die Instanzen verwenden eine fold-bewusste tzinfo, die in einem Programm vor dem PEP nicht verfügbar wäre. Ebenso ist astimezone(), das auf einer naiven Instanz aufgerufen wird, in einem solchen Programm nicht verfügbar, da astimezone() derzeit nicht mit naiven Datumsangaben funktioniert.
Dies lässt uns mit nur einer Situation, in der ein bestehendes Programm nach der Implementierung dieses PEP unterschiedliche Ergebnisse liefern kann: wenn eine datetime.timestamp()-Methode auf einer naiven Datetime-Instanz aufgerufen wird, die zufällig in einem Fold oder einer Lücke liegt. In der aktuellen Implementierung ist das Ergebnis undefiniert. Abhängig von der System- mktime-Implementierung können die Programme in diesen Fällen unterschiedliche Ergebnisse oder Fehler sehen. Mit diesem PEP wird der Zeitstempelwert in diesen Fällen gut definiert sein, aber vom Wert des fold-Flags abhängen. Wir betrachten die Änderung des Verhaltens der datetime.timestamp()-Methode als Fehlerbehebung, die durch dieses PEP ermöglicht wird. Das alte Verhalten kann von Benutzern, die darauf angewiesen sind, weiterhin emuliert werden, indem sie time.mktime(dt.timetuple()) + 1e-6*dt.microsecond anstelle von dt.timestamp() schreiben.
Analogie zu tm_isdst
Die time.mktime-Schnittstelle erlaubt drei Werte für das tm_isdst-Flag: -1, 0 und 1. Wie wir oben erklärt haben, ist -1 (was mktime auffordert, anhand der restlichen Felder zu ermitteln, ob die Sommerzeit für die gegebene Zeit gilt) die einzige praktisch nützliche Wahl.
Mit dem fold-Flag gibt datetime.timestamp() jedoch in 99,98 % der Fälle für die meisten Zeitzonen mit Sommerzeitübergängen denselben Wert zurück wie mktime mit tm_isdst=-1. Darüber hinaus ist das Verhalten ähnlich wie tm_isdst=-1 **unabhängig** vom Wert von fold spezifiziert.
Nur in den 0,02 % der Fälle (2 Stunden pro Jahr) können datetime.timestamp() und mktime mit tm_isdst=-1 abweichen. Doch selbst in diesem Fall werden die meisten mktime-Implementierungen den Wert fold=0 oder den Wert fold=1 zurückgeben, obwohl relevante Standards mktime erlauben, -1 zurückzugeben und in diesen Fällen einen Fehlercode zu setzen.
Mit anderen Worten, das Verhalten von tm_isdst=-1 fehlt in diesem PEP nicht. Im Gegenteil, es ist das einzige Verhalten, das in zwei verschiedenen gut definierten Varianten bereitgestellt wird. Das fehlende Verhalten ist, wenn eine gegebene lokale Stunde aufgrund des falsch spezifizierten tm_isdst als eine andere lokale Stunde interpretiert wird.
Zum Beispiel kann man in den Sommerzeit beobachtenden Zeitzonen der Nordhalbkugel (wo die Sommerzeit im Juni in Kraft ist) Folgendes erhalten:
>>> from time import mktime, localtime
>>> t = mktime((2015, 6, 1, 12, 0, 0, -1, -1, 0))
>>> localtime(t)[:]
(2015, 6, 1, 13, 0, 0, 0, 152, 1)
Beachten Sie, dass 12:00 von mktime als 13:00 interpretiert wurde. Mit datetime.timestamp und datetime.fromtimestamp ist derzeit garantiert, dass
>>> t = datetime.datetime(2015, 6, 1, 12).timestamp()
>>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0)
Dieses PEP erweitert dieselbe Garantie auf beide Werte von fold.
>>> t = datetime.datetime(2015, 6, 1, 12, fold=0).timestamp()
>>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0)
>>> t = datetime.datetime(2015, 6, 1, 12, fold=1).timestamp()
>>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0)
Somit ist einer der vorgeschlagenen Verwendungszwecke für fold=-1 – um das Legacy-Verhalten abzugleichen – nicht erforderlich. Jede Wahl von fold wird das alte Verhalten abgleichen, mit Ausnahme der wenigen Fälle, in denen das alte Verhalten undefiniert war.
Strikte Prüfung ungültiger Zeiten
Ein weiterer Vorschlag war, fold=-1 oder fold=None zu verwenden, um anzuzeigen, dass das Programm wirklich keine Möglichkeit hat, mit den Folds und Lücken umzugehen, und dt.utcoffset() einen Fehler auslösen sollte, wann immer dt eine mehrdeutige oder fehlende lokale Zeit darstellt.
Das Hauptproblem mit diesem Vorschlag ist, dass dt.utcoffset() intern in Situationen verwendet wird, in denen das Auslösen eines Fehlers keine Option ist: zum Beispiel bei Dictionary-Lookups oder Listen-/Set-Mitgliedschaftsprüfungen. Eine strenge Lücken-/Fold-Prüfung müsste daher durch ein separates Flag gesteuert werden, z. B. dt.utcoffset(raise_on_gap=True, raise_on_fold=False). Diese Funktionalität kann jedoch leicht im Benutzercode implementiert werden.
def utcoffset(dt, raise_on_gap=True, raise_on_fold=False):
u = dt.utcoffset()
v = dt.replace(fold=not dt.fold).utcoffset()
if u == v:
return u
if (u < v) == dt.fold:
if raise_on_fold:
raise AmbiguousTimeError
else:
if raise_on_gap:
raise MissingTimeError
return u
Darüber hinaus ist das Auslösen eines Fehlers in den Problemfällen nur eine von vielen möglichen Lösungen. Ein interaktives Programm kann den Benutzer um zusätzliche Eingaben bitten, während ein Serverprozess eine Warnung protokollieren und eine angemessene Standardaktion durchführen kann. Wir können unmöglich Funktionen für alle möglichen Benutzeranforderungen bereitstellen, aber dieses PEP bietet die Mittel, um jedes gewünschte Verhalten in wenigen Codezeilen zu implementieren.
Implementierung
- Github Fork: https://github.com/abalkin/cpython/tree/issue24773-s3
- Tracker-Issue: http://bugs.python.org/issue24773
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Bildnachweis
Dieses Bild ist das Werk eines Angestellten des US-Militärs oder des Verteidigungsministeriums, aufgenommen oder erstellt als Teil der offiziellen Pflichten dieser Person. Als Werk der US-Bundesregierung befindet sich das Bild in der Public Domain.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0495.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT