[Python-checkins] python/dist/src/Doc/lib libdatetime.tex,1.28,1.29 tzinfo-examples.py,1.3,1.4

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Fri, 03 Jan 2003 22:03:17 -0800


Update of /cvsroot/python/python/dist/src/Doc/lib
In directory sc8-pr-cvs1:/tmp/cvs-serv8819/python/Doc/lib

Modified Files:
	libdatetime.tex tzinfo-examples.py 
Log Message:
A new implementation of astimezone() that does what we agreed on in all
cases, plus even tougher tests of that.  This implementation follows
the correctness proof very closely, and should also be quicker (yes,
I wrote the proof before the code, and the code proves the proof <wink>).


Index: libdatetime.tex
===================================================================
RCS file: /cvsroot/python/python/dist/src/Doc/lib/libdatetime.tex,v
retrieving revision 1.28
retrieving revision 1.29
diff -C2 -d -r1.28 -r1.29
*** libdatetime.tex	2 Jan 2003 21:28:07 -0000	1.28
--- libdatetime.tex	4 Jan 2003 06:03:14 -0000	1.29
***************
*** 925,933 ****
  
    must return the same result for every \class{datetimetz} \var{dt}
!   in a given year with \code{dt.tzinfo==tz}  For sane \class{tzinfo}
!   subclasses, this expression yields the time zone's "standard offset"
!   within the year, which should be the same across all days in the year.
!   The implementation of \method{datetimetz.astimezone()} relies on this,
!   but cannot detect violations; it's the programmer's responsibility to
    ensure it.
  
--- 925,933 ----
  
    must return the same result for every \class{datetimetz} \var{dt}
!   with \code{dt.tzinfo==tz}  For sane \class{tzinfo} subclasses, this
!   expression yields the time zone's "standard offset", which should not
!   depend on the date or the time, but only on geographic location.  The
!   implementation of \method{datetimetz.astimezone()} relies on this, but
!   cannot detect violations; it's the programmer's responsibility to
    ensure it.
  
***************
*** 970,973 ****
--- 970,1017 ----
  
  \verbatiminput{tzinfo-examples.py}
+ 
+ Note that there are unavoidable subtleties twice per year in a tzinfo
+ subclass accounting for both standard and daylight time, at the DST
+ transition points.  For concreteness, consider US Eastern (UTC -0500),
+ where EDT begins the minute after 1:59 (EST) on the first Sunday in
+ April, and ends the minute after 1:59 (EDT) on the last Sunday in October:
+ 
+ \begin{verbatim}
+     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
+ \end{verbatim}
+ 
+ When DST starts (the "start" line), the local wall clock leaps from 1:59
+ to 3:00.  A wall time of the form 2:MM doesn't really make sense on that
+ day, so astimezone(Eastern) won't deliver a result with hour=2 on the
+ day DST begins.  How an Eastern class chooses to interpret 2:MM on
+ that day is its business.  The example Eastern class above chose to
+ consider it as a time in EDT, simply because it "looks like it's
+ after 2:00", and so synonymous with the EST 1:MM times on that day.
+ Your Eastern class may wish, for example, to raise an exception instead
+ when it sees a 2:MM time on the day Eastern begins.
+ 
+ When DST ends (the "end" line), there's a potentially worse problem:
+ there's an hour that can't be spelled at all in local wall time, the
+ hour beginning at the moment DST ends.  In this example, that's times of
+ the form 6:MM UTC on the day daylight time ends.  The local wall clock
+ leaps from 1:59 (daylight time) back to 1:00 (standard time) again.
+ 1:MM is taken as daylight time (it's "before 2:00"), so maps to 5:MM UTC.
+ 2:MM is taken as standard time (it's "after 2:00"), so maps to 7:MM UTC.
+ There is no local time that maps to 6:MM UTC on this day.
+ 
+ Just as the wall clock does, astimezone(Eastern) maps both UTC hours 5:MM
+ and 6:MM to Eastern hour 1:MM on this day.  However, this result is
+ ambiguous (there's no way for Eastern to know which repetition of 1:MM
+ is intended).  Applications that can't bear such ambiguity even one hour
+ per year should avoid using hybrid tzinfo classes; there are no
+ ambiguities when using UTC, or any other fixed-offset tzinfo subclass
+ (such as a class representing only EST (fixed offset -5 hours), or only
+ EDT (fixed offset -4 hours)).
  
  

Index: tzinfo-examples.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Doc/lib/tzinfo-examples.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -C2 -d -r1.3 -r1.4
*** tzinfo-examples.py	3 Jan 2003 22:26:57 -0000	1.3
--- tzinfo-examples.py	4 Jan 2003 06:03:14 -0000	1.4
***************
*** 1,5 ****
! from datetime import tzinfo, timedelta
  
  ZERO = timedelta(0)
  
  # A UTC class.
--- 1,6 ----
! from datetime import tzinfo, timedelta, datetime
  
  ZERO = timedelta(0)
+ HOUR = timedelta(hours=1)
  
  # A UTC class.
***************
*** 77,78 ****
--- 78,139 ----
  
  Local = LocalTimezone()
+ 
+ 
+ # A complete implementation of current DST rules for major US time zones.
+ 
+ def first_sunday_on_or_after(dt):
+     days_to_go = 6 - dt.weekday()
+     if days_to_go:
+         dt += timedelta(days_to_go)
+     return dt
+ 
+ # In the US, DST starts at 2am (standard time) on the first Sunday in April.
+ DSTSTART = datetime(1, 4, 1, 2)
+ # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
+ # which is the first Sunday on or after Oct 25.
+ DSTEND = datetime(1, 10, 25, 2)
+ 
+ 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:
+             # An exception may be sensible here, in one or both cases.
+             # It depends on how you want to treat them.  The astimezone()
+             # implementation always passes a datetimetz with
+             # dt.tzinfo == self.
+             return ZERO
+         assert dt.tzinfo is self
+ 
+         # Find first Sunday in April & the last in October.
+         start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
+         end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
+ 
+         # Can't compare naive to aware objects, so strip the timezone from
+         # dt first.
+         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")