[Python-checkins] CVS: python/nondist/sandbox/datetime datetime.py,1.27,1.28 test_datetime.py,1.19,1.20

Guido van Rossum gvanrossum@users.sourceforge.net
Sun, 03 Mar 2002 15:16:23 -0800


Update of /cvsroot/python/python/nondist/sandbox/datetime
In directory usw-pr-cvs1:/tmp/cvs-serv22462

Modified Files:
	datetime.py test_datetime.py 
Log Message:
Attempt to fix ctime() to avoid dependence on time.localtime().
XXX This probably breaks when the time module's epoch isn't 1/1/1970!

- Add global arrays _MONTHNAMES and _DAYNAMES

- Augment class tmxxx:
  - Note in time() docstring that it assumes UTC
  - Add ctime() method

- Add _localtzoffset() function which attempts to calculate the
  tzoffset for a given local time.  It asks localtime() if possible,
  but if that fails just assumes time.timezone.

- Use _localtzoffset() to calculate the default tzoffset; correct, but
  even slower than before

- Change _mktime() to use tmxxx() instead of the time module

- Change _utc_ymdHM() to use tmxxx() instead of repeating the work

- Change ctime() and utcctime() to do the right thing, using tmxxx
  (mostly; there's some stuff with renormalizing for the local time
  zone that I've probably gotten wrong)

- Fix a comment or two



Index: datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.py,v
retrieving revision 1.27
retrieving revision 1.28
diff -C2 -d -r1.27 -r1.28
*** datetime.py	3 Mar 2002 21:24:33 -0000	1.27
--- datetime.py	3 Mar 2002 23:16:21 -0000	1.28
***************
*** 92,95 ****
--- 92,100 ----
      return year, month, n-dbm
  
+ # Month and day names.  For localized versions, see the calendar module.
+ _MONTHNAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ _DAYNAMES = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
+ 
  # This is a start at a struct tm workalike.  Goals:
  #
***************
*** 141,145 ****
  
      def time(self):
!         "Return Unixish timestamp, as a float."
          if self.ordinal is None:
              self.ordinal = _ymd2ord(self.year, self.month, self.day)
--- 146,150 ----
  
      def time(self):
!         "Return Unixish timestamp, as a float (assuming UTC)."
          if self.ordinal is None:
              self.ordinal = _ymd2ord(self.year, self.month, self.day)
***************
*** 148,151 ****
--- 153,183 ----
          return seconds + self.second + self.microsecond / 1e6
  
+     def ctime(self):
+         "Return ctime() style string."
+         if self.ordinal is None:
+             self.ordinal = _ymd2ord(self.year, self.month, self.day)
+         weekday = self.ordinal % 7 or 7
+         return "%s %s %2d %02d:%02d:%02d %04d" % (
+             _DAYNAMES[weekday - 1],
+             _MONTHNAMES[self.month - 1],
+             self.day,
+             self.hour, self.minute, self.second,
+             self.year)
+ 
+ 
+ def _localtzoffset(year, month, day, hour, minute):
+     "Return tzoffset for local time corresponding to given date/time."
+     try:
+         t = _time.mktime((year, month, day, hour, minute, 0, -1, -1, -1))
+         tm = _time.localtime(t)
+         if tm.tm_isdst > 0:
+             return - (_time.altzone // 60)
+         else:
+             return - (_time.timezone // 60)
+     except (ValueError, OverflowError):
+         # XXX Dates out of range for mktime() never see DST
+         return - (_time.timezone // 60)
+ 
+ 
  class basetime(object):
      """Abstract date/time type.
***************
*** 331,336 ****
      """
  
-     __isdst = None
- 
      def __init__(self, year, month, day, hour=0, minute=0, second=0,
                   microsecond=0, tzoffset=None):
--- 363,366 ----
***************
*** 361,372 ****
              raise ValueError('microsecond must be in 0..999999', microsecond)
          if tzoffset is None:
!             # XXX This decides whether local time is DST or not once,
!             # and never changes its mind, regardless how long we run
!             if datetime.__isdst is None:
!                 datetime.__isdst = _time.localtime()[-1]
!             if datetime.__isdst > 0:
!                 tzoffset = -(_time.altzone // 60)
!             else:
!                 tzoffset = -(_time.timezone // 60)
          if not -1439 <= tzoffset <= 1439:
              raise ValueError('tzoffset must be in -1439..1439', tzoffset)
--- 391,396 ----
              raise ValueError('microsecond must be in 0..999999', microsecond)
          if tzoffset is None:
!             # XXX Correct, but expensive
!             tzoffset = _localtzoffset(year, month, day, hour, minute)
          if not -1439 <= tzoffset <= 1439:
              raise ValueError('tzoffset must be in -1439..1439', tzoffset)
***************
*** 457,467 ****
      # Standard conversions, __cmp__, __hash__ (and helpers)
  
      def _mktime(self):
!         # Helper to call time.mktime()
!         y, m, d = self.__year, self.__month, self.__day
!         hh, mm, ss = self.__hour, self.__minute, self.__second
!         mm -= self.__tzoffset # tzoffset is negative in the US
!         ss -= _time.timezone # but time.timezone has the opposite sign!
!         return _time.mktime((y, m, d, hh, mm, ss, -1, -1, -1))
  
      def _timestamp(self):
--- 481,491 ----
      # Standard conversions, __cmp__, __hash__ (and helpers)
  
+     # XXX These should be done without reference to the time module
+ 
      def _mktime(self):
!         # Helper to return a POSIX-ish timestamp
!         t = tmxxx(self.__year, self.__month, self.__day,
!                   self.__hour, self.__minute - self.__tzoffset, self.__second)
!         return t.time() + self.__microsecond / 1e6
  
      def _timestamp(self):
***************
*** 505,539 ****
      def _utc_ymdHM(self):
          # Helper to return (year, month, day, hour, minute) in UTC equivalent
!         y, m, d = self.__year, self.__month, self.__day
!         H, M = self.__hour, self.__minute
!         if self.__tzoffset:
!             M -= self.__tzoffset
!             if not 0 <= M <= 59:
!                 carry, M = divmod(M, 60)
!                 H += carry
!                 if not 0 <= H <= 23:
!                     carry, H = divmod(H, 24)
!                     # tzoffset is less than a day, so carry is no more than 1.
!                     if carry > 0:
!                         assert carry == 1
!                         d += 1
!                         if d > _days_in_month(m, y):
!                             d = 1
!                             m += 1
!                             if m > 12:
!                                 m = 1
!                                 y += 1
!                     else:
!                         assert carry == -1
!                         d -= 1
!                         if d == 0:
!                             m -= 1
!                             if m > 0:
!                                 d = _days_in_month(m, y)
!                             else:
!                                 d = 31
!                                 m = 12
!                                 y -= 1
!         return y, m, d, H, M
  
      def __hash__(self):
--- 529,535 ----
      def _utc_ymdHM(self):
          # Helper to return (year, month, day, hour, minute) in UTC equivalent
!         t = tmxxx(self.__year, self.__month, self.__day,
!                   self.__hour, self.__minute - self.__tzoffset)
!         return t.year, t.month, t.day, t.hour, t.minute
  
      def __hash__(self):
***************
*** 559,563 ****
      def ctime(self):
          "Format a la ctime() in local time."
!         return _time.strftime("%c", _time.localtime(self._mktime()))
  
      def strftime(self, fmt):
--- 555,568 ----
      def ctime(self):
          "Format a la ctime() in local time."
!         tzoffset = _localtzoffset(self.__year, self.__month, self.__day, 0, 0)
!         t = tmxxx(self.__year, self.__month, self.__day, self.__hour,
!                   self.__minute - self.__tzoffset + tzoffset, self.__second)
!         tzoffset1 = _localtzoffset(t.year, t.month, t.day, t.hour, t.minute)
!         if tzoffset1 != tzoffset:
!             t = tmxxx(t.year, t.month, t.day, t.hour,
!                       t.minute - tzoffset + tzoffset1, self.__second)
!             tzoffset2 = _localtzoffset(t.year, t.month, t.day, t.hour, t.minute)
!             assert tzoffset2 == tzoffset1 # XXX Hah!
!         return t.ctime()
  
      def strftime(self, fmt):
***************
*** 567,571 ****
      def utcctime(self):
          "Format a la ctime() in UTC."
!         return _time.strftime("%c", _time.gmtime(self._mktime()))
  
      def utcstrftime(self, fmt):
--- 572,578 ----
      def utcctime(self):
          "Format a la ctime() in UTC."
!         t = tmxxx(self.__year, self.__month, self.__day,
!                   self.__hour, self.__minute - self.__tzoffset, self.__second)
!         return t.ctime()
  
      def utcstrftime(self, fmt):
***************
*** 652,656 ****
          week1monday = _isoweek1monday(year)
          today = _ymd2ord(self.__year, self.__month, self.__day)
!         # Internally, week has origin 0
          week, day = divmod(today - week1monday, 7)
          if week < 0:
--- 659,663 ----
          week1monday = _isoweek1monday(year)
          today = _ymd2ord(self.__year, self.__month, self.__day)
!         # Internally, week and day have origin 0
          week, day = divmod(today - week1monday, 7)
          if week < 0:

Index: test_datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/test_datetime.py,v
retrieving revision 1.19
retrieving revision 1.20
diff -C2 -d -r1.19 -r1.20
*** test_datetime.py	3 Mar 2002 21:24:33 -0000	1.19
--- test_datetime.py	3 Mar 2002 23:16:21 -0000	1.20
***************
*** 316,319 ****
--- 316,334 ----
              self.assertEqual(timestamp, tm.time())
  
+     def test_ctime(self):
+         t = datetime(2002, 3, 2, 18, 3, 5, 123)
+         self.assertEqual(t.ctime(), "Sat Mar  2 18:03:05 2002")
+         # XXX Can only test utcctime() in known timezone :-(
+         # XXX What about the southern hemisphere, where this is DST?
+         import time
+         if time.timezone == 5*3600:
+             self.assertEqual(t.utcctime(), "Sat Mar  2 23:03:05 2002")
+         elif time.timezone == 0:
+             self.assertEqual(t.utcctime(), "Sat Mar  2 18:03:05 2002")
+         elif time.timezone == 3600:
+             self.assertEqual(t.utcctime(), "Sat Mar  2 17:03:05 2002")
+         else:
+             self.fail("Can't test utcctime()")
+ 
  def test_suite():
      s1 = unittest.makeSuite(TestDateTime, 'test')