[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():