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.
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c
index 6dff659..9a9ab7e 100644
--- a/Modules/datetimemodule.c
+++ b/Modules/datetimemodule.c
@@ -549,6 +549,40 @@
  * tzinfo helpers.
  */
 
+/* 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,
  * and the caller must not decref the result.
@@ -566,28 +600,15 @@
 	return tzinfo;
 }
 
-/* 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.
+ * 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.
  */
 static int
@@ -602,7 +623,7 @@
 	assert(tzinfoarg != NULL);
 
 	*none = 0;
-	u = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg);
+	u = call_tzinfo_method(tzinfoarg, tzinfo, name);
 	if (u == NULL)
 		return -1;
 
@@ -614,12 +635,35 @@
 
 	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 or int or long",
-			     name);
+			     "tzinfo.%s() must return None, integer or "
+			     "timedelta, not '%s'",
+			     name, u->ob_type->tp_name);
 		goto Done;
 	}
 
@@ -649,6 +693,32 @@
 	return call_utc_tzinfo_method(tzinfo, "utcoffset", tzinfoarg, none);
 }
 
+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()
  * returns None, call_dst returns 0 and sets *none to 1.  If dst()
@@ -663,22 +733,29 @@
 	return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none);
 }
 
-/* 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.
+/* 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 *tzinfo, PyObject *tzinfoarg)
+call_tzname(PyObject *self, PyObject *tzinfo)
 {
 	PyObject *result;
 
+	assert(self != NULL);
 	assert(tzinfo != NULL);
-	assert(PyTZInfo_Check(tzinfo));
-	assert(tzinfoarg != NULL);
+	assert(check_tzinfo_subclass(tzinfo) >= 0);
 
-	result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg);
-	if (result != NULL && result != Py_None && !PyString_Check(result)) {
-		PyErr_Format(PyExc_TypeError, ".tzinfo.tzname() must "
+	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);
 		Py_DECREF(result);
@@ -699,7 +776,7 @@
 	      /* date,
 	       * datetime,
 	       * datetimetz with None tzinfo,
-	       * datetimetz where utcoffset() return None
+	       * datetimetz where utcoffset() returns None
 	       * time,
 	       * timetz with None tzinfo,
 	       * timetz where utcoffset() returns None
@@ -919,8 +996,8 @@
 				Zreplacement = PyString_FromString("");
 				if (Zreplacement == NULL) goto Done;
 				if (tzinfo != Py_None && tzinfo != NULL) {
-					PyObject *temp = call_tzname(tzinfo,
-								     object);
+					PyObject *temp = call_tzname(object,
+								     tzinfo);
 					if (temp == NULL) goto Done;
 					if (temp != Py_None) {
 						assert(PyString_Check(temp));
@@ -3917,38 +3994,24 @@
 }
 
 /*
- * Indirect access to tzinfo methods.  One more "convenience function" and
- * it won't be possible to find the useful methods anymore <0.5 wink>.
+ * Indirect access to tzinfo methods.
  */
 
-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");
+	return offset_as_timedelta((PyObject *)self, self->tzinfo,
+				   "utcoffset");
 }
 
 static PyObject *
 timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
-	return timetz_convienience(self, "dst");
+	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);
 }
 
 /*
@@ -4325,37 +4388,21 @@
  * Indirect access to tzinfo methods.
  */
 
-/* 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");
+	return offset_as_timedelta((PyObject *)self, self->tzinfo,
+				   "utcoffset");
 }
 
 static PyObject *
 datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
-	return datetimetz_convienience(self, "dst");
+	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);
 }
 
 /*