This PEP adds a new attributefold to instances of thedatetime.time anddatetime.datetime classes that can be usedto differentiate between two moments in time for which local times arethe same. The allowed values for thefold attribute will be 0 and 1with 0 corresponding to the earlier and 1 to the later of the twopossible readings of an ambiguous local time.
In most world locations, there have been and will be times whenlocal clocks are moved back.[1] In those times, intervals areintroduced in which local clocks show the same time twice in the sameday. In these situations, the information displayed on a local clock(or stored in a Python datetime instance) is insufficient to identifya particular moment in time. The proposed solution is to add anattribute to thedatetime instances taking values of 0 and 1 thatwill enumerate the two ambiguous times.

It is less common, but occasionally clocks can be moved back forother reasons. For example, Ukraine skipped the spring-forwardtransition in March 1990 and instead, moved their clocks back onJuly 1, 1990, switching from Moscow Time to Eastern European Time.In that case, standard (winter) time was in effect before and afterthe transition.
Both DST and standard time changes may result in time shifts otherthan an hour.
When clocks are moved back, we say that afold[2] is created in time.When the clocks are moved forward, agap is created. A local timethat falls in the fold is calledambiguous. A local time that fallsin the gap is calledmissing.
We propose adding an attribute calledfold to instances of thedatetime.time anddatetime.datetime classes. This attributeshould have the value 0 for all instances except those that representthe second (chronologically) moment in time in an ambiguous case. Forthose instances, the value will be 1.[3]
fold=1 in a non-ambiguous case issaid to represent an invalid time (or is invalid for short), butusers are not prevented from creating invalid instances by passingfold=1 to a constructor or to areplace() method. Thisis similar to the current situation with the instances that fall inthe spring-forward gap. Such instances don’t represent any validtime, but neither the constructors nor thereplace() methodscheck whether the instances that they produce are valid. Moreover,this PEP specifies how various functions should behave when given aninvalid instance.Instances ofdatetime.time anddatetime.datetime classes willget a new attributefold with two possible values: 0 and 1.
The__new__ methods of thedatetime.time anddatetime.datetime classes will get a new keyword-only argumentcalledfold with the default value 0. The value of thefold argument will be used to initialize the value of thefold attribute in the returned instance.
Thereplace() methods of thedatetime.time anddatetime.datetime classes will get a new keyword-only argumentcalledfold. It will behave similarly to the otherreplace()arguments: if thefold argument is specified and given a value 0or 1, the new instance returned byreplace() will have itsfold attribute set to that value. In CPython, any non-integervalue offold will raise aTypeError, but otherimplementations may allow the valueNone to behave the same aswhenfold is not given.[4] (This isa nod to the existing difference in treatment ofNone argumentsin other positions of this method across Python implementations;it is not intended to leave the door open for future alternativeinterpretation offold=None.) If thefold argument is notspecified, the original value of thefold attribute is copied tothe result.
None to mean “no change to existingattribute” for all other attributes inreplace().Access macros will be defined to extract the value offold fromPyDateTime_DateTime andPyDateTime_Time objects.
intPyDateTime_DATE_GET_FOLD(PyDateTime_DateTime*o)
Return the value offold as a Cint.
intPyDateTime_TIME_GET_FOLD(PyDateTime_Time*o)
Return the value offold as a Cint.
New constructors will be defined that will take an additionalargument to specify the value offold in the createdinstance:
PyObject*PyDateTime_FromDateAndTimeAndFold(intyear,intmonth,intday,inthour,intminute,intsecond,intusecond,intfold)
Return adatetime.datetime object with the specified year, month,day, hour, minute, second, microsecond and fold.
PyObject*PyTime_FromTimeAndFold(inthour,intminute,intsecond,intusecond,intfold)
Return adatetime.time object with the specified hour, minute,second, microsecond and fold.
Thedatetime.now() method called without arguments will setfold=1 when returning the second of the two ambiguous times in asystem local time fold. When called with atzinfo argument, thevalue of thefold will be determined by thetzinfo.fromutc()implementation. When an instance of thedatetime.timezone class(the stdlib’s fixed-offsettzinfo subclass,e.g.datetime.timezone.utc) is passed astzinfo, thereturned datetime instance will always havefold=0.Thedatetime.utcnow() method is unaffected.
A new feature is proposed to facilitate conversion from naive datetimeinstances to aware.
Theastimezone() method will now work for naiveself. Thesystem local timezone will be assumed in this case and thefoldflag will be used to determine which local timezone is in effectin the ambiguous case.
For example, on a system set to US/Eastern timezone:
>>>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'
An implication is thatdatetime.now(tz) is fully equivalent todatetime.now().astimezone(tz) (assumingtz is an instance of apost-PEPtzinfo implementation, i.e. one that correctly handlesand setsfold).
Thefromtimestamp() static method ofdatetime.datetime willset thefold attribute appropriately in the returned object.
For example, on a system set to US/Eastern timezone:
>>>datetime.fromtimestamp(1414906200)datetime.datetime(2014, 11, 2, 1, 30)>>>datetime.fromtimestamp(1414906200+3600)datetime.datetime(2014, 11, 2, 1, 30, fold=1)
Thetimestamp() method ofdatetime.datetime will return differentvalues fordatetime.datetime instances that differ only by the valueof theirfold attribute if and only if these instances represent anambiguous or a missing time.
When adatetime.datetime instancedt represents an ambiguoustime, there are two valuess0 ands1 such that:
datetime.fromtimestamp(s0)==datetime.fromtimestamp(s1)==dt
(This is because== disregards the value of fold – see below.)
In this case,dt.timestamp() will return the smaller ofs0ands1 values ifdt.fold==0 and the larger otherwise.
For example, on a system set to US/Eastern timezone:
>>>datetime(2014,11,2,1,30,fold=0).timestamp()1414906200.0>>>datetime(2014,11,2,1,30,fold=1).timestamp()1414909800.0
When adatetime.datetime instancedt represents a missingtime, there is no values for which:
datetime.fromtimestamp(s)==dt
but we can form two “nice to know” values ofs that differby the size of the gap in seconds. One is the value ofsthat would correspond todt in a timezone where the UTC offsetis always the same as the offset right before the gap and theother is the similar value but in a timezone the UTC offsetis always the same as the offset right after the gap.
The value returned bydt.timestamp() given a missingdt will be the greater of the two “nice to know” valuesifdt.fold==0 and the smaller otherwise.(This is not a typo – it’s intentionally backwards from the rule forambiguous times.)
For example, on a system set to US/Eastern timezone:
>>>datetime(2015,3,8,2,30,fold=0).timestamp()1425799800.0>>>datetime(2015,3,8,2,30,fold=1).timestamp()1425796200.0
Users of pre-PEP implementations oftzinfo will not see anychanges in the behavior of their aware datetime instances. Two suchinstances that differ only by the value of thefold attribute willnot be distinguishable by any means other than an explicit access tothefold value. (This is because these pre-PEP implementationsare not using thefold attribute.)
On the other hand, if an object’stzinfo is set to a fold-awareimplementation, then in a fold or gap the value offold willaffect the result of several methods:utcoffset(),dst(),tzname(),astimezone(),strftime() (if the “%Z” or “%z” directive is used in the formatspecification),isoformat(), andtimetuple().
Thedatetime.datetime.combine() method will copy the value of thefold attribute to the resultingdatetime.datetime instance.
Thedatetime.datetime.time() method will copy the value of thefold attribute to the resultingdatetime.time instance.
The value of the fold attribute will only be saved in pickles createdwith protocol version 4 (introduced in Python 3.4) or greater.
Pickle sizes for thedatetime.datetime anddatetime.timeobjects will not change. Thefold value will be encoded in thefirst bit of the 3rd byte of thedatetime.datetimepickle payload; and in the first bit of the 1st byte of thedatetime.time payload. In thecurrent implementationthese bytes are used to store the month (1-12) and hour (0-23) valuesand the first bit is always 0. We picked these bytes because they arethe only bytes that are checked by the current unpickle code. Thusloading post-PEPfold=1 pickles in a pre-PEP Python will result inan exception rather than an instance with out of range components.
No new implementations ofdatetime.tzinfo abstract class areproposed in this PEP. The existing (fixed offset) timezones donot introduce ambiguous local times and theirutcoffset()implementation will return the same constant value as they do nowregardless of the value offold.
The basic implementation offromutc() in the abstractdatetime.tzinfo class will not change. It is currently not usedanywhere in the stdlib because the only includedtzinfoimplementation (thedatetime.timezone class implementing fixedoffset timezones) overridesfromutc(). Keeping the defaultimplementation unchanged has the benefit that pre-PEP 3rd partyimplementations that inherit the defaultfromutc() are notaccidentally affected.
Implementors of concretedatetime.tzinfo subclasses who want tosupport variable UTC offsets (due to DST and other causes) should followthese guidelines.
New implementations ofutcoffset(),tzname() anddst()methods should ignore the value offold unless they are called onthe ambiguous or missing times.
New subclasses should override the base-classfromutc() method andimplement it so that in all cases where two different UTC timesu0 andu1 (u0 <u1) correspond to the same local timet,fromutc(u0) will return an instance withfold=0 andfromutc(u1) will return an instance withfold=1. In allother cases the returned instance should havefold=0.
Theutcoffset(),tzname() anddst() methods should use thevalue of the fold attribute to determine whether an otherwiseambiguous timet corresponds to the time before or after thetransition. By definition,utcoffset() is greater before andsmaller after any transition that creates a fold. The values returnedbytzname() anddst() may or may not depend on the value ofthefold attribute depending on the kind of the transition.
The sketch above illustrates the relationship between the UTC andlocal time around a fall-back transition. The zig-zag line is a graphof the function implemented byfromutc(). Two intervals on theUTC axis adjacent to the transition point and having the size of thetime shift at the transition are mapped to the same interval on thelocal axis. New implementations offromutc() method should setthe fold attribute to 1 whenself is in the region marked inyellow on the UTC axis. (All intervals should be treated as closed onthe left and open on the right.)
Thefromutc() method should never produce a time in the gap.
If theutcoffset(),tzname() ordst() method is called on alocal time that falls in a gap, the rules in effect before thetransition should be used iffold=0. Otherwise, the rules ineffect after the transition should be used.
The sketch above illustrates the relationship between the UTC andlocal time around a spring-forward transition. At the transition, thelocal clock is advanced skipping the times in the gap. For thepurposes of determining the values ofutcoffset(),tzname()anddst(), the line before the transition is extended forward tofind the UTC time corresponding to the time in the gap withfold=0and for instances withfold=1, the line after the transition isextended back.
On ambiguous/missing timesutcoffset() should return valuesaccording to the following table:
| fold=0 | fold=1 | |
|---|---|---|
| Fold | oldoff | newoff = oldoff - delta |
| Gap | oldoff | newoff = oldoff + delta |
whereoldoff (newoff) is the UTC offset before (after) thetransition anddelta is the absolute size of the fold or the gap.
Note that the interpretation of the fold attribute is consistent inthe fold and gap cases. In both cases,fold=0 (fold=1) meansusefromutc() line before (after) the transition to find the UTCtime. Only in the “Fold” case, the UTC timesu0 andu1 are“real” solutions for the equationfromutc(u)==t, while in the“Gap” case they are “imaginary” solutions.
On a missing time introduced at the start of DST, the values returnedbyutcoffset() anddst() methods should be as follows
| fold=0 | fold=1 | |
|---|---|---|
| utcoffset() | stdoff | stdoff + dstoff |
| dst() | zero | dstoff |
On an ambiguous time introduced at the end of DST, the values returnedbyutcoffset() anddst() methods should be as follows
| fold=0 | fold=1 | |
|---|---|---|
| utcoffset() | stdoff + dstoff | stdoff |
| dst() | dstoff | zero |
wherestdoff is the standard (non-DST) offset,dstoff is theDST correction (typicallydstoff=timedelta(hours=1)) andzero=timedelta(0).
Inmathematicks he was greaterThan Tycho Brahe, or Erra Pater:For he, by geometric scale,Could take the size of pots of ale;Resolve, by sines and tangents straight,If bread or butter wanted weight,And wisely tell what hour o’ th’ dayThe clock does strike by algebra.– “Hudibras” by Samuel Butler
The value of thefold attribute will be ignored in all operationswith naive datetime instances. As a consequence, naivedatetime.datetime ordatetime.time instances that differ onlyby the value offold will compare as equal. Applications thatneed to differentiate between such instances should check the value offold explicitly or convert those instances to a timezone that doesnot have ambiguous times (such as UTC).
The value offold will also be ignored whenever a timedelta isadded to or subtracted from a datetime instance which may be eitheraware or naive. The result of addition (subtraction) of a timedeltato (from) a datetime will always havefold set to 0 even if theoriginal datetime instance hadfold=1.
No changes are proposed to the way the differencet-s iscomputed for datetime instancest ands. If both instancesare naive ort.tzinfo is the same instance ass.tzinfo(t.tzinfoiss.tzinfo evaluates toTrue) thent-s is atimedeltad such thats+d==t. As explained in theprevious paragraph, timedelta addition ignores bothfold andtzinfo attributes and so does intra-zone or naive datetimesubtraction.
Naive and intra-zone comparisons will ignore the value offold andreturn the same results as they do now. (This is the only way topreserve backward compatibility. If you need an aware intra-zonecomparison that uses the fold, convert both sides to UTC first.)
The inter-zone subtraction will be defined as it is now:t-s iscomputed as(t-t.utcoffset())-(s-s.utcoffset()).replace(tzinfo=t.tzinfo), but the result willdepend on the values oft.fold ands.fold when eithert.tzinfo ors.tzinfo is post-PEP.[5]
s==t buts-u!=t-u. Such paradoxes arenot really new and are inherent in the overloading of the minusoperator differently for intra- and inter-zone operations. Forexample, one can easily construct datetime instancest andswith some variable offsettzinfo and a datetimeu withtzinfo=timezone.utc such that(t-u)-(s-u)!=t-s.The explanation for this paradox is that the minuses inside theparentheses and the two other minuses are really three differentoperations: inter-zone datetime subtraction, timedelta subtraction,and intra-zone datetime subtraction, which each have the mathematicalproperties of subtraction separately, but not when combined in asingle expression.The aware datetime comparison operators will work the same as they donow, with results indirectly affected by the value offold whenevertheutcoffset() value of one of the operands depends on it, with oneexception. Whenever one or both of the operands in inter-zone comparison issuch that itsutcoffset() depends on the value of itsfoldfold attribute, the result isFalse.[6]
Formally,t==s whent.tzinfoiss.tzinfo evaluates toFalse can be defined as follows. Lettoutc(t,fold) be afunction that takes an aware datetime instancet and returns anaive instance representing the same time in UTC assuming a givenvalue offold:
deftoutc(t,fold):u=t-t.replace(fold=fold).utcoffset()returnu.replace(tzinfo=None)
Thent==s is equivalent to
toutc(t,fold=0)==toutc(t,fold=1)==toutc(s,fold=0)==toutc(s,fold=1)
This proposal will have little effect on the programs that do not readthefold flag explicitly or use tzinfo implementations that do.The only visible change for such programs will be that conversions toand from POSIX timestamps will now round-trip correctly (up tofloating point rounding). Programs that implemented a work-around tothe old incorrect behavior may need to be modified.
Pickles produced by older programs will remain fully forwardcompatible. Only datetime/time instances withfold=1 pickledin the new versions will become unreadable by the older Pythonversions. Pickles of instances withfold=0 (which is thedefault) will remain unchanged.
(same characters, an hour later)
While thetm_isdst field of thetime.struct_time object can beused to disambiguate local times in the fold, the semantics of suchdisambiguation are completely different from the proposal in this PEP.
The main problem with thetm_isdst field is that it is impossibleto know what value is appropriate fortm_isdst without knowing thedetails about the time zone that are only available to thetzinfoimplementation. Thus whiletm_isdst is useful in theoutput ofmethods such astime.localtime, it is cumbersome as aninput ofmethods such astime.mktime.
If the programmer misspecified a non-negative value oftm_isdst totime.mktime, the result will be time that is 1 hour off and sincethere is rarely a way to know anything about DSTbefore a call totime.mktime is made, the only sane choice is usuallytm_isdst=-1.
Unliketm_isdst, the proposedfold attribute has no effect onthe interpretation of the datetime instance unless without thatattribute two (or no) interpretations are possible.
Since it would be very confusing to have something calledisdstthat does not have the same semantics astm_isdst, we need adifferent name. Moreover, thedatetime.datetime class already hasa method calleddst() and if we calledfold “isdst”, we wouldnecessarily have situations when “isdst” is zero butdst() is notor the other way around.
Suggested by Guido van Rossum and favored by one (but initiallydisfavored by another) author. A consensus was reached after theallowed values for the attribute were changed from False/True to 0/1.The noun “fold” has correct connotations and easy mnemonic rules, butat the same time does not invite unbased assumptions.
This was a working name of the attribute chosen initially because theobvious alternative (“second”) conflicts with the existing attribute.It was rejected mostly on the grounds that it would make True adefault value.
The following alternative names have also been considered:
later=True instance converted to an earlier time by.astimezone(timezone.utc) that that withlater=False.Yet again, this can be interpreted as a desirable indication thatthe original time is invalid.localtime functionbranch index wasindependently proposed for the name of thedisambiguation attribute and receivedsome support.ltdf=False is theearlier by those who didn’t want to endorse any of the alternatives.)Several reasons have been raised to allow aNone or -1 value forthefold attribute: backward compatibility, analogy withtm_isdstand strict checking for invalid times.
It has been suggested that backward compatibility can be improved ifthe default value of thefold flag wasNone which wouldsignal that pre-PEP behavior is requested. Based on the analysisbelow, we believe that the proposed changes with thefold=0default are sufficiently backward compatible.
This PEP provides only three ways for a program to discover that twootherwise identical datetime instances have different values offold: (1) an explicit check of thefold attribute; (2) ifthe instances are naive - conversion to another timezone using theastimezone() method; and (3) conversion tofloat using thetimestamp() method.
Sincefold is a new attribute, the first option is not availableto the existing programs. Note that option (2) only works for naivedatetimes that happen to be in a fold or a gap in the system timezone. In all other cases, the value offold will be ignored inthe conversion unless the instances use afold-awaretzinfowhich would not be available in a pre-PEP program. Similarly, theastimezone() called on a naive instance will not be available insuch program becauseastimezone() does not currently work withnaive datetimes.
This leaves us with only one situation where an existing program canstart producing different results after the implementation of this PEP:when adatetime.timestamp() method is called on a naive datetimeinstance that happen to be in the fold or the gap. In the currentimplementation, the result is undefined. Depending on the systemmktime implementation, the programs can see different results orerrors in those cases. With this PEP in place, the value of timestampwill be well-defined in those cases but will depend on the value ofthefold flag. We consider the change indatetime.timestamp() method behavior a bug fix enabled by thisPEP. The old behavior can still be emulated by the users who dependon it by writingtime.mktime(dt.timetuple())+1e-6*dt.microsecondinstead ofdt.timestamp().
Thetime.mktime interface allows three values for thetm_isdstflag: -1, 0, and 1. As we explained above, -1 (askingmktime todetermine whether DST is in effect for the given time from the rest ofthe fields) is the only choice that is useful in practice.
With thefold flag, however,datetime.timestamp() will returnthe same value asmktime withtm_isdst=-1 in 99.98% of thetime for most time zones with DST transitions. Moreover,tm_isdst=-1-like behavior is specifiedregardless of the valueoffold.
It is only in the 0.02% cases (2 hours per year) that thedatetime.timestamp() andmktime withtm_isdst=-1 maydisagree. However, even in this case, most of themktimeimplementations will return thefold=0 or thefold=1value even though relevant standards allowmktime to return -1 andset an error code in those cases.
In other words,tm_isdst=-1 behavior is not missing from this PEP.To the contrary, it is the only behavior provided in two differentwell-defined flavors. The behavior that is missing is when a givenlocal hour is interpreted as a different local hour because of themisspecifiedtm_isdst.
For example, in the DST-observing time zones in the Northernhemisphere (where DST is in effect in June) one can get
>>>fromtimeimportmktime,localtime>>>t=mktime((2015,6,1,12,0,0,-1,-1,0))>>>localtime(t)[:](2015, 6, 1, 13, 0, 0, 0, 152, 1)
Note that 12:00 was interpreted as 13:00 bymktime. With thedatetime.timestamp,datetime.fromtimestamp, it is currentlyguaranteed that
>>>t=datetime.datetime(2015,6,1,12).timestamp()>>>datetime.datetime.fromtimestamp(t)datetime.datetime(2015, 6, 1, 12, 0)
This PEP extends the same guarantee to both values offold:
>>>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)
Thus one of the suggested uses forfold=-1 – to match the legacybehavior – is not needed. Either choice offold will match theold behavior except in the few cases where the old behavior wasundefined.
Another suggestion was to usefold=-1 orfold=None toindicate that the program truly has no means to deal with the foldsand gaps anddt.utcoffset() should raise an error wheneverdtrepresents an ambiguous or missing local time.
The main problem with this proposal, is thatdt.utcoffset() isused internally in situations where raising an error is not an option:for example, in dictionary lookups or list/set membership checks. Sostrict gap/fold checking behavior would need to be controlled by aseparate flag, saydt.utcoffset(raise_on_gap=True,raise_on_fold=False). However, this functionality can be easilyimplemented in user code:
defutcoffset(dt,raise_on_gap=True,raise_on_fold=False):u=dt.utcoffset()v=dt.replace(fold=notdt.fold).utcoffset()ifu==v:returnuif(u<v)==dt.fold:ifraise_on_fold:raiseAmbiguousTimeErrorelse:ifraise_on_gap:raiseMissingTimeErrorreturnu
Moreover, raising an error in the problem cases is only one of manypossible solutions. An interactive program can ask the user foradditional input, while a server process may log a warning and take anappropriate default action. We cannot possibly provide functions forall possible user requirements, but this PEP provides the means toimplement any desired behavior in a few lines of code.
This document has been placed in the public domain.
This image is a work of a U.S. military or Department of Defenseemployee, taken or made as part of that person’s official duties. As awork of the U.S. federal government, the image is in the publicdomain.
Source:https://github.com/python/peps/blob/main/peps/pep-0495.rst
Last modified:2025-02-01 08:59:27 GMT