[Python-checkins] python/nondist/sandbox/datetime datetime.c,1.73,1.74 datetime.py,1.105,1.106 doc.txt,1.52,1.53 obj_datetimetz.c,1.23,1.24 test_both.py,1.86,1.87

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Sat, 14 Dec 2002 21:14:54 -0800


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

Modified Files:
	datetime.c datetime.py doc.txt obj_datetimetz.c test_both.py 
Log Message:
In both implementations, changed datetimetz.timetuple() to consult the
tzinfo.dst() method for the proper setting of the tm_isdst flag.  Added a
new test for this.  Updated the datetimetz.timetuple() docs.

In the Python implementation, added sane error-checking to tzinfo
utcoffset() and dst() method calls.  When those guys returned results of
the wrong types, or ints out of range, sometimes no error was triggered,
but more often a baffling error got triggered somewhere in the guts.


Index: datetime.c
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.c,v
retrieving revision 1.73
retrieving revision 1.74
diff -C2 -d -r1.73 -r1.74
*** datetime.c	15 Dec 2002 03:58:28 -0000	1.73
--- datetime.c	15 Dec 2002 05:14:52 -0000	1.74
***************
*** 561,577 ****
  }
  
! /* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the
!  * result.  tzinfo must be an instance of the tzinfo class.  If utcoffset()
!  * returns None, call_utcoffset returns 0 and sets *none to 1.  If uctoffset()
!  & doesn't return a Python int or long, TypeError is raised and this
!  * returns -1.  If utcoffset() returns an int outside the legitimate range
!  * for a UTC offset, ValueError is raised and this returns -1.  Else
!  * *none is set to 0 and the offset is returned.
   */
  static int
! call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
  {
  	PyObject *u;
! 	long result = -1;	/* Py{Inr,Long}_AsLong return long */
  
  	assert(tzinfo != NULL);
--- 561,579 ----
  }
  
! /* Internal helper.
!  * Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the
!  * result.  tzinfo must be an instance of the tzinfo class.  If the method
!  * returns None, this returns 0 and sets *none to 1.  If the method doesn't
!  * return a Python int or long, TypeError is raised and this returns -1.
!  * If it does return an int or long, but is outside the valid range for
!  * a UTC minute offset, ValueError is raised and this returns -1.
!  * Else *none is set to 0 and the integer method result is returned.
   */
  static int
! call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
! 		       int *none)
  {
  	PyObject *u;
! 	long result = -1;	/* Py{Int,Long}_AsLong return long */
  
  	assert(tzinfo != NULL);
***************
*** 580,584 ****
  
  	*none = 0;
! 	u = PyObject_CallMethod(tzinfo, "utcoffset", "O", tzinfoarg);
  	if (u == NULL)
  		return -1;
--- 582,586 ----
  
  	*none = 0;
! 	u = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg);
  	if (u == NULL)
  		return -1;
***************
*** 595,600 ****
  		result = PyLong_AsLong(u);
  	else {
! 		PyErr_SetString(PyExc_TypeError,
! 				"utcoffset() must return None or int or long");
  		goto Done;
  	}
--- 597,603 ----
  		result = PyLong_AsLong(u);
  	else {
! 		PyErr_Format(PyExc_TypeError,
! 			     "tzinfo.%s() must return None or int or long",
! 			     name);
  		goto Done;
  	}
***************
*** 604,613 ****
  	if (result < -1439 || result > 1439) {
  		PyErr_Format(PyExc_ValueError,
! 			     "utcoffset() returned %ld; must be in "
  			     "-1439 .. 1439",
! 			     result);
  		result = -1;
  	}
  	return (int)result;
  }
  
--- 607,644 ----
  	if (result < -1439 || result > 1439) {
  		PyErr_Format(PyExc_ValueError,
! 			     "tzinfo.%s() returned %ld; must be in "
  			     "-1439 .. 1439",
! 			     name, result);
  		result = -1;
  	}
  	return (int)result;
+ }
+ 
+ /* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the
+  * result.  tzinfo must be an instance of the tzinfo class.  If utcoffset()
+  * returns None, call_utcoffset returns 0 and sets *none to 1.  If uctoffset()
+  & doesn't return a Python int or long, TypeError is raised and this
+  * returns -1.  If utcoffset() returns an int outside the legitimate range
+  * for a UTC offset, ValueError is raised and this returns -1.  Else
+  * *none is set to 0 and the offset is returned.
+  */
+ static int
+ call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
+ {
+ 	return call_utc_tzinfo_method(tzinfo, "utcoffset", tzinfoarg, none);
+ }
+ 
+ /* Call tzinfo.dst(tzinfoarg), and extract an integer from the
+  * result.  tzinfo must be an instance of the tzinfo class.  If dst()
+  * returns None, call_dst returns 0 and sets *none to 1.  If dst()
+  & doesn't return a Python int or long, TypeError is raised and this
+  * returns -1.  If dst() returns an int outside the legitimate range
+  * for a UTC offset, ValueError is raised and this returns -1.  Else
+  * *none is set to 0 and the offset is returned.
+  */
+ static int
+ call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
+ {
+ 	return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none);
  }
  

Index: datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.py,v
retrieving revision 1.105
retrieving revision 1.106
diff -C2 -d -r1.105 -r1.106
*** datetime.py	14 Dec 2002 16:50:15 -0000	1.105
--- datetime.py	15 Dec 2002 05:14:52 -0000	1.106
***************
*** 190,193 ****
--- 190,203 ----
      return "".join(result)
  
+ def _check_utc_offset(name, offset):
+     if offset is None:
+         return
+     if not isinstance(offset, (int, long)):
+         raise TypeError("%s() must return None, int or long, not %s" %
+                         (name, type(offset)))
+     if -1440 < offset < 1440:
+         return
+     raise ValueError("%s()=%d, must be in -1439..1439" % (name, offset))
+ 
  # This is a start at a struct tm workalike.  Goals:
  #
***************
*** 1049,1055 ****
          else:
              offset = tz.utcoffset(self)
!             if offset is None or -1440 < offset < 1440:
!                 return offset
!             raise ValueError("utcoffset=%s, must be in -1439..1439" % offset)
  
  
--- 1059,1064 ----
          else:
              offset = tz.utcoffset(self)
!             _check_utc_offset("utcoffset", offset)
!             return offset
  
  
***************
*** 1080,1084 ****
              return None
          else:
!             return tz.dst(self)
  
      def __nonzero__(self):
--- 1089,1095 ----
              return None
          else:
!             offset = tz.dst(self)
!             _check_utc_offset("dst", offset)
!             return offset
  
      def __nonzero__(self):
***************
*** 1408,1411 ****
--- 1419,1432 ----
      combine = classmethod(combine)
  
+     def timetuple(self):
+         "Return local time tuple compatible with time.localtime()."
+         dst = self.dst()
+         if dst is None:
+             dst = -1
+         elif dst:
+             dst = 1
+         return _build_struct_time(self.year, self.month, self.day,
+                                   self.hour, self.minute, self.second,
+                                   self.weekday(), self._yday(), dst)
  
      def utctimetuple(self):
***************
*** 1454,1460 ****
          else:
              offset = tz.utcoffset(self)
!             if offset is None or -1440 < offset < 1440:
!                 return offset
!             raise ValueError("utcoffset=%s, must be in -1439..1439" % offset)
  
      def tzname(self):
--- 1475,1480 ----
          else:
              offset = tz.utcoffset(self)
!             _check_utc_offset("utcoffset", offset)
!             return offset
  
      def tzname(self):
***************
*** 1470,1474 ****
              return None
          else:
!             return tz.dst(self)
  
      def __add__(self, other):
--- 1490,1496 ----
              return None
          else:
!             offset = tz.dst(self)
!             _check_utc_offset("dst", offset)
!             return offset
  
      def __add__(self, other):

Index: doc.txt
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/doc.txt,v
retrieving revision 1.52
retrieving revision 1.53
diff -C2 -d -r1.52 -r1.53
*** doc.txt	15 Dec 2002 02:52:51 -0000	1.52
--- doc.txt	15 Dec 2002 05:14:52 -0000	1.53
***************
*** 753,760 ****
      Return the DST offset, in minutes east of UTC, or None if DST
      information isn't known.  Return 0 if DST is not in effect.
!     Note that DST offset, if applicable, has already been added to
!     the UTC offset returned by utcoffset(), so there's no need to
!     consult dst() unless you're interested in displaying DST info
!     separately.
  
  Example tzinfo classes:
--- 753,763 ----
      Return the DST offset, in minutes east of UTC, or None if DST
      information isn't known.  Return 0 if DST is not in effect.
!     If DST is in effect, return an int (or long), in the range
!     -1439 to 1439 inclusive.  Note that DST offset, if applicable,
!     has already been added to the UTC offset returned by utcoffset(),
!     so there's no need to consult dst() unless you're interested in
!     displaying DST info separately.  For example, datetimetz.timetuple()
!     calls its tzinfo object's dst() method to determine how the tm_isdst
!     flag should be set.
  
  Example tzinfo classes:
***************
*** 1064,1075 ****
  
    - timetuple()
!     XXX This should use self.dst() to fill in the DST flag.
!     Return a 9-element tuple of the form returned by time.localtime().
!     The DST flag is -1.   d.timetuple() is equivalent to
!         (d.year, d.month, d.day,
!          d.hour, d.minute, d.second,
!          d.weekday(),  # 0 is Monday
!          d.toordinal() - date(d.year, 1, 1).toordinal() + 1, # day of year
!          -1)
  
    - isoformat(sep='T')
--- 1067,1074 ----
  
    - timetuple()
!     Like datetime.timetuple(), but sets the tm_isdst flag according to
!     the dst() method:  if self.dst() returns None, tm_isdst is set to -1;
!     else if self.dst() returns a non-zero value, tm_isdst is set to 1;
!     else tm_isdst is set to 0.
  
    - isoformat(sep='T')

Index: obj_datetimetz.c
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/obj_datetimetz.c,v
retrieving revision 1.23
retrieving revision 1.24
diff -C2 -d -r1.23 -r1.24
*** obj_datetimetz.c	15 Dec 2002 03:58:28 -0000	1.23
--- obj_datetimetz.c	15 Dec 2002 05:14:52 -0000	1.24
***************
*** 134,137 ****
--- 134,140 ----
   */
  
+ /* Internal helper.
+  * Call a tzinfo object's method, or return None if tzinfo is None.
+  */
  static PyObject *
  datetimetz_convienience(PyDateTime_DateTimeTZ *self, char *name)
***************
*** 312,316 ****
  datetimetz_timetuple(PyDateTime_DateTimeTZ *self)
  {
! 	/* XXX Call dst() to do something more intelligent w/ the dstflag. */
  	return build_struct_time(GET_YEAR(self),
  				 GET_MONTH(self),
--- 315,333 ----
  datetimetz_timetuple(PyDateTime_DateTimeTZ *self)
  {
! 	int dstflag = -1;
! 
! 	if (self->tzinfo != Py_None) {
! 		int none;
! 
! 		dstflag = call_dst(self->tzinfo, (PyObject *)self, &none);
! 		if (dstflag == -1 && PyErr_Occurred())
! 			return NULL;
! 
! 		if (none)
! 			dstflag = -1;
! 		else if (dstflag != 0)
! 			dstflag = 1;
! 
! 	}
  	return build_struct_time(GET_YEAR(self),
  				 GET_MONTH(self),
***************
*** 319,323 ****
  				 DATE_GET_MINUTE(self),
  				 DATE_GET_SECOND(self),
! 				 -1);
  }
  
--- 336,340 ----
  				 DATE_GET_MINUTE(self),
  				 DATE_GET_SECOND(self),
! 				 dstflag);
  }
  

Index: test_both.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/test_both.py,v
retrieving revision 1.86
retrieving revision 1.87
diff -C2 -d -r1.86 -r1.87
*** test_both.py	15 Dec 2002 00:42:05 -0000	1.86
--- test_both.py	15 Dec 2002 05:14:52 -0000	1.87
***************
*** 1697,1701 ****
          # Smallest possible after UTC adjustment.
          t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
!         # Largest possible after UCT adjustment.
          t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
                             tzinfo=FixedOffset(-1439, ""))
--- 1697,1701 ----
          # Smallest possible after UTC adjustment.
          t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
!         # Largest possible after UTC adjustment.
          t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
                             tzinfo=FixedOffset(-1439, ""))
***************
*** 2004,2007 ****
--- 2004,2041 ----
          self.assertRaises(TypeError, meth, ts, off42)
          self.assertRaises(TypeError, meth, ts, tzinfo=off42)
+ 
+     def test_tzinfo_timetuple(self):
+         # TestDateTime tested most of this.  datetimetz adds a twist to the
+         # DST flag.
+         class DST(tzinfo):
+             def __init__(self, dstvalue):
+                 self.dstvalue = dstvalue
+             def dst(self, dt):
+                 return self.dstvalue
+ 
+         cls = self.theclass
+         for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
+             d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
+             t = d.timetuple()
+             self.assertEqual(1, t.tm_year)
+             self.assertEqual(1, t.tm_mon)
+             self.assertEqual(1, t.tm_mday)
+             self.assertEqual(10, t.tm_hour)
+             self.assertEqual(20, t.tm_min)
+             self.assertEqual(30, t.tm_sec)
+             self.assertEqual(0, t.tm_wday)
+             self.assertEqual(1, t.tm_yday)
+             self.assertEqual(flag, t.tm_isdst)
+ 
+         # dst() returns wrong type.
+         self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
+ 
+         # dst() at the edge.
+         self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
+         self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
+ 
+         # dst() out of range.
+         self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
+         self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
  
  def test_suite():