[Python-checkins] python/dist/src/Modules datetimemodule.c,1.8,1.9

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Sat, 21 Dec 2002 19:43:41 -0800


Update of /cvsroot/python/python/dist/src/Modules
In directory sc8-pr-cvs1:/tmp/cvs-serv23750/python/Modules

Modified Files:
	datetimemodule.c 
Log Message:
Implemented a Wiki suggestion:

{timetz,datetimetz}.{utcoffset,dst}() now return a timedelta (or None)
instead of an int (or None).

tzinfo.{utcoffset,dst)() can now return a timedelta (or an int, or None).

Curiously, this was much easier to do in the C implementation than in the
Python implementation (which lives in the Zope3 code tree) -- the C code
already had lots of hair to extract C ints from offset objects, and used
C ints internally.


Index: datetimemodule.c
===================================================================
RCS file: /cvsroot/python/python/dist/src/Modules/datetimemodule.c,v
retrieving revision 1.8
retrieving revision 1.9
diff -C2 -d -r1.8 -r1.9
*** datetimemodule.c	20 Dec 2002 01:31:27 -0000	1.8
--- datetimemodule.c	22 Dec 2002 03:43:39 -0000	1.9
***************
*** 550,553 ****
--- 550,587 ----
   */
  
+ /* Ensure that p is None or of a tzinfo subclass.  Return 0 if OK; if not
+  * raise TypeError and return -1.
+  */
+ static int
+ check_tzinfo_subclass(PyObject *p)
+ {
+ 	if (p == Py_None || PyTZInfo_Check(p))
+ 		return 0;
+ 	PyErr_Format(PyExc_TypeError,
+ 		     "tzinfo argument must be None or of a tzinfo subclass, "
+ 		     "not type '%s'",
+ 		     p->ob_type->tp_name);
+ 	return -1;
+ }
+ 
+ /* Return tzinfo.methname(self), without any checking of results.
+  * If tzinfo is None, returns None.
+  */
+ static PyObject *
+ call_tzinfo_method(PyObject *self, PyObject *tzinfo, char *methname)
+ {
+ 	PyObject *result;
+ 
+ 	assert(self && tzinfo && methname);
+ 	assert(check_tzinfo_subclass(tzinfo) >= 0);
+ 	if (tzinfo == Py_None) {
+ 		result = Py_None;
+ 		Py_INCREF(result);
+ 	}
+ 	else
+ 		result = PyObject_CallMethod(tzinfo, methname, "O", self);
+ 	return result;
+ }
+ 
  /* If self has a tzinfo member, return a BORROWED reference to it.  Else
   * return NULL, which is NOT AN ERROR.  There are no error returns here,
***************
*** 567,592 ****
  }
  
- /* Ensure that p is None or of a tzinfo subclass.  Return 0 if OK; if not
-  * raise TypeError and return -1.
-  */
- static int
- check_tzinfo_subclass(PyObject *p)
- {
- 	if (p == Py_None || PyTZInfo_Check(p))
- 		return 0;
- 	PyErr_Format(PyExc_TypeError,
- 		     "tzinfo argument must be None or of a tzinfo subclass, "
- 		     "not type '%s'",
- 		     p->ob_type->tp_name);
- 	return -1;
- }
- 
  /* 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.
   */
--- 601,613 ----
  }
  
  /* 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 or timedelta, TypeError is raised and this
!  * returns -1.  If it returns an int or long, but is outside the valid
!  * range for a UTC minute offset, or it returns a timedelta and the value is
!  * out of range or isn't a whole number of minutes, ValueError is raised and
!  * this returns -1.
   * Else *none is set to 0 and the integer method result is returned.
   */
***************
*** 603,607 ****
  
  	*none = 0;
! 	u = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg);
  	if (u == NULL)
  		return -1;
--- 624,628 ----
  
  	*none = 0;
! 	u = call_tzinfo_method(tzinfoarg, tzinfo, name);
  	if (u == NULL)
  		return -1;
***************
*** 615,624 ****
  	if (PyInt_Check(u))
  		result = PyInt_AS_LONG(u);
  	else if (PyLong_Check(u))
  		result = PyLong_AsLong(u);
  	else {
  		PyErr_Format(PyExc_TypeError,
! 			     "tzinfo.%s() must return None or int or long",
! 			     name);
  		goto Done;
  	}
--- 636,668 ----
  	if (PyInt_Check(u))
  		result = PyInt_AS_LONG(u);
+ 
  	else if (PyLong_Check(u))
  		result = PyLong_AsLong(u);
+ 
+ 	else if (PyDelta_Check(u)) {
+ 		const int days = GET_TD_DAYS(u);
+ 		if (days < -1 || days > 0)
+ 			result = 24*60;	/* trigger ValueError below */
+ 		else {
+ 			/* next line can't overflow because we know days
+ 			 * is -1 or 0 now
+ 			 */
+ 			int ss = days * 24 * 3600 + GET_TD_SECONDS(u);
+ 			result = divmod(ss, 60, &ss);
+ 			if (ss || GET_TD_MICROSECONDS(u)) {
+ 				PyErr_Format(PyExc_ValueError,
+ 					     "tzinfo.%s() must return a "
+ 					     "whole number of minutes",
+ 					     name);
+ 				result = -1;
+ 				goto Done;
+ 			}
+ 		}
+ 	}
  	else {
  		PyErr_Format(PyExc_TypeError,
! 			     "tzinfo.%s() must return None, integer or "
! 			     "timedelta, not '%s'",
! 			     name, u->ob_type->tp_name);
  		goto Done;
  	}
***************
*** 650,653 ****
--- 694,723 ----
  }
  
+ static PyObject *new_delta(int d, int sec, int usec, int normalize);
+ 
+ /* Call tzinfo.name(self) and return the offset as a timedelta or None. */
+ static PyObject *
+ offset_as_timedelta(PyObject *self, PyObject *tzinfo, char *name) {
+ 	PyObject *result;
+ 
+ 	if (tzinfo == Py_None) {
+ 		result = Py_None;
+ 		Py_INCREF(result);
+ 	}
+ 	else {
+ 		int none;
+ 		int offset = call_utc_tzinfo_method(tzinfo, name, self, &none);
+ 		if (offset < 0 && PyErr_Occurred())
+ 			return NULL;
+ 		if (none) {
+ 			result = Py_None;
+ 			Py_INCREF(result);
+ 		}
+ 		else
+ 			result = new_delta(0, offset * 60, 0, 1);
+ 	}
+ 	return result;
+ }
+ 
  /* Call tzinfo.dst(tzinfoarg), and extract an integer from the
   * result.  tzinfo must be an instance of the tzinfo class.  If dst()
***************
*** 664,683 ****
  }
  
! /* Call tzinfo.tzname(tzinfoarg), and return the result.  tzinfo must be
!  * an instance of the tzinfo class.  If tzname() doesn't return None or
!  * a string, TypeError is raised and this returns NULL.
   */
  static PyObject *
! call_tzname(PyObject *tzinfo, PyObject *tzinfoarg)
  {
  	PyObject *result;
  
  	assert(tzinfo != NULL);
! 	assert(PyTZInfo_Check(tzinfo));
! 	assert(tzinfoarg != NULL);
  
! 	result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg);
! 	if (result != NULL && result != Py_None && !PyString_Check(result)) {
! 		PyErr_Format(PyExc_TypeError, ".tzinfo.tzname() must "
  			     "return None or a string, not '%s'",
  			     result->ob_type->tp_name);
--- 734,760 ----
  }
  
! /* Call tzinfo.tzname(self), and return the result.  tzinfo must be
!  * an instance of the tzinfo class or None.  If tzinfo isn't None, and
!  * tzname() doesn't return None ora string, TypeError is raised and this
!  * returns NULL.
   */
  static PyObject *
! call_tzname(PyObject *self, PyObject *tzinfo)
  {
  	PyObject *result;
  
+ 	assert(self != NULL);
  	assert(tzinfo != NULL);
! 	assert(check_tzinfo_subclass(tzinfo) >= 0);
  
! 	if (tzinfo == Py_None) {
! 		result = Py_None;
! 		Py_INCREF(result);
! 	}
! 	else
! 		result = PyObject_CallMethod(tzinfo, "tzname", "O", self);
! 
! 	if (result != NULL && result != Py_None && ! PyString_Check(result)) {
! 		PyErr_Format(PyExc_TypeError, "tzinfo.tzname() must "
  			     "return None or a string, not '%s'",
  			     result->ob_type->tp_name);
***************
*** 700,704 ****
  	       * datetime,
  	       * datetimetz with None tzinfo,
! 	       * datetimetz where utcoffset() return None
  	       * time,
  	       * timetz with None tzinfo,
--- 777,781 ----
  	       * datetime,
  	       * datetimetz with None tzinfo,
! 	       * datetimetz where utcoffset() returns None
  	       * time,
  	       * timetz with None tzinfo,
***************
*** 920,925 ****
  				if (Zreplacement == NULL) goto Done;
  				if (tzinfo != Py_None && tzinfo != NULL) {
! 					PyObject *temp = call_tzname(tzinfo,
! 								     object);
  					if (temp == NULL) goto Done;
  					if (temp != Py_None) {
--- 997,1002 ----
  				if (Zreplacement == NULL) goto Done;
  				if (tzinfo != Py_None && tzinfo != NULL) {
! 					PyObject *temp = call_tzname(object,
! 								     tzinfo);
  					if (temp == NULL) goto Done;
  					if (temp != Py_None) {
***************
*** 3918,3953 ****
  
  /*
!  * Indirect access to tzinfo methods.  One more "convenience function" and
!  * it won't be possible to find the useful methods anymore <0.5 wink>.
   */
  
- static PyObject *
- timetz_convienience(PyDateTime_TimeTZ *self, char *name)
- {
- 	PyObject *result;
- 
- 	if (self->tzinfo == Py_None) {
- 		result = Py_None;
- 		Py_INCREF(result);
- 	}
- 	else
- 		result = PyObject_CallMethod(self->tzinfo, name, "O", self);
- 	return result;
- }
- 
  /* These are all METH_NOARGS, so don't need to check the arglist. */
  static PyObject *
  timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) {
! 	return timetz_convienience(self, "utcoffset");
  }
  
  static PyObject *
! timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) {
! 	return timetz_convienience(self, "tzname");
  }
  
  static PyObject *
! timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
! 	return timetz_convienience(self, "dst");
  }
  
--- 3995,4016 ----
  
  /*
!  * Indirect access to tzinfo methods.
   */
  
  /* These are all METH_NOARGS, so don't need to check the arglist. */
  static PyObject *
  timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) {
! 	return offset_as_timedelta((PyObject *)self, self->tzinfo,
! 				   "utcoffset");
  }
  
  static PyObject *
! timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
! 	return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
  }
  
  static PyObject *
! timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) {
! 	return call_tzname((PyObject *)self, self->tzinfo);
  }
  
***************
*** 4326,4360 ****
   */
  
- /* Internal helper.
-  * Call a tzinfo object's method, or return None if tzinfo is None.
-  */
- static PyObject *
- datetimetz_convienience(PyDateTime_DateTimeTZ *self, char *name)
- {
- 	PyObject *result;
- 
- 	if (self->tzinfo == Py_None) {
- 		result = Py_None;
- 		Py_INCREF(result);
- 	}
- 	else
- 		result = PyObject_CallMethod(self->tzinfo, name, "O", self);
- 	return result;
- }
- 
  /* These are all METH_NOARGS, so don't need to check the arglist. */
  static PyObject *
  datetimetz_utcoffset(PyDateTime_DateTimeTZ *self, PyObject *unused) {
! 	return datetimetz_convienience(self, "utcoffset");
  }
  
  static PyObject *
! datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) {
! 	return datetimetz_convienience(self, "tzname");
  }
  
  static PyObject *
! datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
! 	return datetimetz_convienience(self, "dst");
  }
  
--- 4389,4407 ----
   */
  
  /* These are all METH_NOARGS, so don't need to check the arglist. */
  static PyObject *
  datetimetz_utcoffset(PyDateTime_DateTimeTZ *self, PyObject *unused) {
! 	return offset_as_timedelta((PyObject *)self, self->tzinfo,
! 				   "utcoffset");
  }
  
  static PyObject *
! datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
! 	return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
  }
  
  static PyObject *
! datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) {
! 	return call_tzname((PyObject *)self, self->tzinfo);
  }