[Python-checkins] python/dist/src/Modules datetimemodule.c,1.41,1.42

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Thu, 23 Jan 2003 08:36:14 -0800


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

Modified Files:
	datetimemodule.c 
Log Message:
Bringing the code and test suite into line with doc and NEWS changes
checked in two days agao:

Refactoring of, and new rules for, dt.astimezone(tz).

dt must be aware now, and tz.utcoffset() and tz.dst() must not return None.
The old dt.astimezone(None) no longer works to change an aware datetime
into a naive datetime; use dt.replace(tzinfo=None) instead.

The tzinfo base class now supplies a new fromutc(self, dt) method, and
datetime.astimezone(tz) invokes tz.fromutc().  The default implementation
of fromutc() reproduces the same results as the old astimezone()
implementation, but tzinfo subclasses can override fromutc() if the
default implementation isn't strong enough to get the correct results
in all cases (for example, this may be necessary if a tzinfo subclass
models a time zone whose "standard offset" (wrt UTC) changed in some
year(s), or in some variations of double-daylight time -- the creativity
of time zone politics can't be captured in a single default implementation).


Index: datetimemodule.c
===================================================================
RCS file: /cvsroot/python/python/dist/src/Modules/datetimemodule.c,v
retrieving revision 1.41
retrieving revision 1.42
diff -C2 -d -r1.41 -r1.42
*** datetimemodule.c	20 Jan 2003 22:54:38 -0000	1.41
--- datetimemodule.c	23 Jan 2003 16:36:11 -0000	1.42
***************
*** 2726,2730 ****
  /* Methods.  A subclass must implement these. */
  
! static PyObject*
  tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt)
  {
--- 2726,2730 ----
  /* Methods.  A subclass must implement these. */
  
! static PyObject *
  tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt)
  {
***************
*** 2732,2736 ****
  }
  
! static PyObject*
  tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt)
  {
--- 2732,2736 ----
  }
  
! static PyObject *
  tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt)
  {
***************
*** 2738,2742 ****
  }
  
! static PyObject*
  tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt)
  {
--- 2738,2742 ----
  }
  
! static PyObject *
  tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt)
  {
***************
*** 2744,2747 ****
--- 2744,2829 ----
  }
  
+ static PyObject *
+ tzinfo_fromutc(PyDateTime_TZInfo *self, PyDateTime_DateTime *dt)
+ {
+ 	int y, m, d, hh, mm, ss, us;
+ 
+ 	PyObject *result;
+ 	int off, dst;
+ 	int none;
+ 	int delta;
+ 
+ 	if (! PyDateTime_Check(dt)) {
+ 		PyErr_SetString(PyExc_TypeError,
+ 				"fromutc: argument must be a datetime");
+ 		return NULL;
+ 	}
+ 	if (! HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) {
+ 	    	PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo "
+ 	    			"is not self");
+ 	    	return NULL;
+ 	}
+ 
+ 	off = call_utcoffset(dt->tzinfo, (PyObject *)dt, &none);
+ 	if (off == -1 && PyErr_Occurred())
+ 		return NULL;
+ 	if (none) {
+ 		PyErr_SetString(PyExc_ValueError, "fromutc: non-None "
+ 				"utcoffset() result required");
+ 		return NULL;
+ 	}
+ 
+ 	dst = call_dst(dt->tzinfo, (PyObject *)dt, &none);
+ 	if (dst == -1 && PyErr_Occurred())
+ 		return NULL;
+ 	if (none) {
+ 		PyErr_SetString(PyExc_ValueError, "fromutc: non-None "
+ 				"dst() result required");
+ 		return NULL;
+ 	}
+ 
+ 	y = GET_YEAR(dt);
+ 	m = GET_MONTH(dt);
+ 	d = GET_DAY(dt);
+ 	hh = DATE_GET_HOUR(dt);
+ 	mm = DATE_GET_MINUTE(dt);
+ 	ss = DATE_GET_SECOND(dt);
+ 	us = DATE_GET_MICROSECOND(dt);
+ 
+ 	delta = off - dst;
+ 	mm += delta;
+ 	if ((mm < 0 || mm >= 60) &&
+ 	    normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
+ 		goto Fail;
+ 	result = new_datetime(y, m, d, hh, mm, ss, us, dt->tzinfo);
+ 	if (result == NULL)
+ 		return result;
+ 
+ 	dst = call_dst(dt->tzinfo, result, &none);
+ 	if (dst == -1 && PyErr_Occurred())
+ 		goto Fail;
+ 	if (none)
+ 		goto Inconsistent;
+ 	if (dst == 0)
+ 		return result;
+ 
+ 	mm += dst;
+ 	if ((mm < 0 || mm >= 60) &&
+ 	    normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
+ 		goto Fail;
+ 	Py_DECREF(result);
+ 	result = new_datetime(y, m, d, hh, mm, ss, us, dt->tzinfo);
+ 	return result;
+ 
+ Inconsistent:
+ 	PyErr_SetString(PyExc_ValueError, "fromutc: tz.dst() gave"
+ 			"inconsistent results; cannot convert");
+ 
+ 	/* fall thru to failure */
+ Fail:
+ 	Py_DECREF(result);
+ 	return NULL;
+ }
+ 
  /*
   * Pickle support.  This is solely so that tzinfo subclasses can use
***************
*** 2773,2776 ****
--- 2855,2861 ----
  	 PyDoc_STR("datetime -> DST offset in minutes east of UTC.")},
  
+ 	{"fromutc",	(PyCFunction)tzinfo_fromutc,		METH_O,
+ 	 PyDoc_STR("datetime in UTC -> datetime in local time.")},
+ 
  	{NULL, NULL}
  };
***************
*** 4037,4143 ****
  datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
  {
! 	int y = GET_YEAR(self);
! 	int m = GET_MONTH(self);
! 	int d = GET_DAY(self);
! 	int hh = DATE_GET_HOUR(self);
! 	int mm = DATE_GET_MINUTE(self);
! 	int ss = DATE_GET_SECOND(self);
! 	int us = DATE_GET_MICROSECOND(self);
! 
  	PyObject *result;
! 	PyObject *temp;
! 	int selfoff, resoff, dst1;
! 	int none;
! 	int delta;
  
  	PyObject *tzinfo;
  	static char *keywords[] = {"tz", NULL};
  
! 	if (! PyArg_ParseTupleAndKeywords(args, kw, "O:astimezone", keywords,
! 					  &tzinfo))
! 		return NULL;
! 	if (check_tzinfo_subclass(tzinfo) < 0)
  		return NULL;
  
!         /* Don't call utcoffset unless necessary. */
! 	result = new_datetime(y, m, d, hh, mm, ss, us, tzinfo);
! 	if (result == NULL ||
! 	    tzinfo == Py_None ||
! 	    ! HASTZINFO(self) ||
! 	    self->tzinfo == Py_None ||
! 	    self->tzinfo == tzinfo)
! 		return result;
! 
!         /* Get the offsets.  If either object turns out to be naive, again
!          * there's no conversion of date or time fields.
!          */
! 	selfoff = call_utcoffset(self->tzinfo, (PyObject *)self, &none);
! 	if (selfoff == -1 && PyErr_Occurred())
! 		goto Fail;
! 	if (none)
! 		return result;
! 
! 	resoff = call_utcoffset(tzinfo, result, &none);
! 	if (resoff == -1 && PyErr_Occurred())
! 		goto Fail;
! 	if (none)
! 		return result;
  
! 	/* See the long comment block at the end of this file for an
! 	 * explanation of this algorithm.  That it always works requires a
! 	 * pretty intricate proof.  There are many equivalent ways to code
! 	 * up the proof as an algorithm.  This way favors calling dst() over
! 	 * calling utcoffset(), because "the usual" utcoffset() calls dst()
! 	 * itself, and calling the latter instead saves a Python-level
! 	 * function call.  This way of coding it also follows the proof
! 	 * closely, w/ x=self, y=result, z=result, and z'=temp.
! 	 */
! 	dst1 = call_dst(tzinfo, result, &none);
! 	if (dst1 == -1 && PyErr_Occurred())
! 		goto Fail;
! 	if (none) {
! 		PyErr_SetString(PyExc_ValueError, "astimezone(): utcoffset() "
! 		"returned a duration but dst() returned None");
! 		goto Fail;
  	}
- 	delta = resoff - dst1 - selfoff;
- 	if (delta) {
- 		mm += delta;
- 		if ((mm < 0 || mm >= 60) &&
- 		    normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
- 			goto Fail;
- 		temp = new_datetime(y, m, d, hh, mm, ss, us, tzinfo);
- 		if (temp == NULL)
- 			goto Fail;
- 		Py_DECREF(result);
- 		result = temp;
  
! 		dst1 = call_dst(tzinfo, result, &none);
! 		if (dst1 == -1 && PyErr_Occurred())
! 			goto Fail;
! 		if (none)
! 			goto Inconsistent;
! 	}
! 	if (dst1 == 0)
! 		return result;
  
! 	mm += dst1;
  	if ((mm < 0 || mm >= 60) &&
  	    normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
! 		goto Fail;
! 	temp = new_datetime(y, m, d, hh, mm, ss, us, tzinfo);
! 	if (temp == NULL)
! 		goto Fail;
! 	Py_DECREF(result);
! 	result = temp;
! 	return result;
  
! Inconsistent:
! 	PyErr_SetString(PyExc_ValueError, "astimezone(): tz.dst() gave"
! 			"inconsistent results; cannot convert");
  
! 	/* fall thru to failure */
! Fail:
! 	Py_DECREF(result);
  	return NULL;
  }
--- 4122,4178 ----
  datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
  {
! 	int y, m, d, hh, mm, ss, us;
  	PyObject *result;
! 	int offset, none;
  
  	PyObject *tzinfo;
  	static char *keywords[] = {"tz", NULL};
  
! 	if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:astimezone", keywords,
! 					  &PyDateTime_TZInfoType, &tzinfo))
  		return NULL;
  
!         if (!HASTZINFO(self) || self->tzinfo == Py_None)
!         	goto NeedAware;
  
!         /* Conversion to self's own time zone is a NOP. */
! 	if (self->tzinfo == tzinfo) {
! 		Py_INCREF(self);
! 		return (PyObject *)self;
  	}
  
!         /* Convert self to UTC. */
!         offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none);
!         if (offset == -1 && PyErr_Occurred())
!         	return NULL;
!         if (none)
!         	goto NeedAware;
  
! 	y = GET_YEAR(self);
! 	m = GET_MONTH(self);
! 	d = GET_DAY(self);
! 	hh = DATE_GET_HOUR(self);
! 	mm = DATE_GET_MINUTE(self);
! 	ss = DATE_GET_SECOND(self);
! 	us = DATE_GET_MICROSECOND(self);
! 
! 	mm -= offset;
  	if ((mm < 0 || mm >= 60) &&
  	    normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
! 		return NULL;
  
! 	/* Attach new tzinfo and let fromutc() do the rest. */
! 	result = new_datetime(y, m, d, hh, mm, ss, us, tzinfo);
! 	if (result != NULL) {
! 		PyObject *temp = result;
  
! 		result = PyObject_CallMethod(tzinfo, "fromutc", "O", temp);
! 		Py_DECREF(temp);
! 	}
! 	return result;
! 
! NeedAware:
! 	PyErr_SetString(PyExc_ValueError, "astimezone() cannot be applied to "
! 					  "a naive datetime");
  	return NULL;
  }