summaryrefslogtreecommitdiffstats
path: root/Modules/datetimemodule.c
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2002-12-31 17:36:56 (GMT)
committerTim Peters <tim.peters@gmail.com>2002-12-31 17:36:56 (GMT)
commit521fc15e626ed53ba90c966c4a2392ab487fd62e (patch)
tree586b22e2773638958fe158c29a04d41d1af179e1 /Modules/datetimemodule.c
parentba2f875d90c5b8650d6bde2a152be8f899e15469 (diff)
downloadcpython-521fc15e626ed53ba90c966c4a2392ab487fd62e.zip
cpython-521fc15e626ed53ba90c966c4a2392ab487fd62e.tar.gz
cpython-521fc15e626ed53ba90c966c4a2392ab487fd62e.tar.bz2
A new, and much hairier, implementation of astimezone(), building on
an idea from Guido. This restores that the datetime implementation never passes a datetime d to a tzinfo method unless d.tzinfo is the tzinfo instance whose method is being called. That in turn allows enormous simplifications in user-written tzinfo classes (see the Python sandbox US.py and EU.py for fully fleshed-out examples). d.astimezone(tz) also raises ValueError now if d lands in the one hour of the year that can't be expressed in tz (this can happen iff tz models both standard and daylight time). That it used to return a nonsense result always ate at me, and it turned out that it seemed impossible to force a consistent nonsense result under the new implementation (which doesn't know anything about how tzinfo classes implement their methods -- it can only infer properties indirectly). Guido doesn't like this -- expect it to change. New tests of conversion between adjacent DST-aware timezones don't pass yet, and are commented out. Running the datetime tests in a loop under a debug build leaks 9 references per test run, but I don't believe the datetime code is the cause (it didn't leak the last time I changed the C code, and the leak is the same if I disable all the tests that invoke the only function that changed here). I'll pursue that next.
Diffstat (limited to 'Modules/datetimemodule.c')
-rw-r--r--Modules/datetimemodule.c150
1 files changed, 126 insertions, 24 deletions
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c
index 48445a1..40f4773 100644
--- a/Modules/datetimemodule.c
+++ b/Modules/datetimemodule.c
@@ -4751,6 +4751,11 @@ datetimetz_astimezone(PyDateTime_DateTimeTZ *self, PyObject *args,
int ss = DATE_GET_SECOND(self);
int us = DATE_GET_MICROSECOND(self);
+ PyObject *result;
+ PyObject *temp;
+ int myoff, otoff, newoff;
+ int none;
+
PyObject *tzinfo;
static char *keywords[] = {"tz", NULL};
@@ -4760,30 +4765,127 @@ datetimetz_astimezone(PyDateTime_DateTimeTZ *self, PyObject *args,
if (check_tzinfo_subclass(tzinfo) < 0)
return NULL;
- if (tzinfo != Py_None && self->tzinfo != Py_None) {
- int none;
- int selfoffset;
- selfoffset = call_utcoffset(self->tzinfo,
- (PyObject *)self,
- &none);
- if (selfoffset == -1 && PyErr_Occurred())
- return NULL;
- if (! none) {
- int tzoffset;
- tzoffset = call_utcoffset(tzinfo,
- (PyObject *)self,
- &none);
- if (tzoffset == -1 && PyErr_Occurred())
- return NULL;
- if (! none) {
- mm -= selfoffset - tzoffset;
- if (normalize_datetime(&y, &m, &d,
- &hh, &mm, &ss, &us) < 0)
- return NULL;
- }
- }
- }
- return new_datetimetz(y, m, d, hh, mm, ss, us, tzinfo);
+ /* Don't call utcoffset unless necessary. */
+ result = new_datetimetz(y, m, d, hh, mm, ss, us, tzinfo);
+ if (result == NULL ||
+ tzinfo == Py_None ||
+ 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.
+ */
+ myoff = call_utcoffset(self->tzinfo, (PyObject *)self, &none);
+ if (myoff == -1 && PyErr_Occurred())
+ goto Fail;
+ if (none)
+ return result;
+
+ otoff = call_utcoffset(tzinfo, result, &none);
+ if (otoff == -1 && PyErr_Occurred())
+ goto Fail;
+ if (none)
+ return result;
+
+ /* Add otoff-myoff to result. */
+ mm += otoff - myoff;
+ if (normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
+ goto Fail;
+ temp = new_datetimetz(y, m, d, hh, mm, ss, us, tzinfo);
+ if (temp == NULL)
+ goto Fail;
+ Py_DECREF(result);
+ result = temp;
+
+ /* If tz is a fixed-offset class, we're done, but we can't know
+ * whether it is. If it's a DST-aware class, and we're not near a
+ * DST boundary, we're also done. If we crossed a DST boundary,
+ * the offset will be different now, and that's our only clue.
+ * Unfortunately, we can be in trouble even if we didn't cross a
+ * DST boundary, if we landed on one of the DST "problem hours".
+ */
+ newoff = call_utcoffset(tzinfo, result, &none);
+ if (newoff == -1 && PyErr_Occurred())
+ goto Fail;
+ if (none)
+ goto Inconsistent;
+
+ if (newoff != otoff) {
+ /* We did cross a boundary. Try to correct. */
+ mm += newoff - otoff;
+ if (normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
+ goto Fail;
+ temp = new_datetimetz(y, m, d, hh, mm, ss, us, tzinfo);
+ if (temp == NULL)
+ goto Fail;
+ Py_DECREF(result);
+ result = temp;
+
+ otoff = call_utcoffset(tzinfo, result, &none);
+ if (otoff == -1 && PyErr_Occurred())
+ goto Fail;
+ if (none)
+ goto Inconsistent;
+ }
+ /* If this is the first hour of DST, it may be a local time that
+ * doesn't make sense on the local clock, in which case the naive
+ * hour before it (in standard time) is equivalent and does make
+ * sense on the local clock. So force that.
+ */
+ hh -= 1;
+ if (normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
+ goto Fail;
+ temp = new_datetimetz(y, m, d, hh, mm, ss, us, tzinfo);
+ if (temp == NULL)
+ goto Fail;
+ newoff = call_utcoffset(tzinfo, temp, &none);
+ if (newoff == -1 && PyErr_Occurred()) {
+ Py_DECREF(temp);
+ goto Fail;
+ }
+ if (none) {
+ Py_DECREF(temp);
+ goto Inconsistent;
+ }
+ /* Are temp and result really the same time? temp == result iff
+ * temp - newoff == result - otoff, iff
+ * (result - HOUR) - newoff = result - otoff, iff
+ * otoff - newoff == HOUR
+ */
+ if (otoff - newoff == 60) {
+ /* use the local time that makes sense */
+ Py_DECREF(result);
+ return temp;
+ }
+ Py_DECREF(temp);
+
+ /* There's still a problem with the unspellable (in local time)
+ * hour after DST ends.
+ */
+ temp = datetime_richcompare((PyDateTime_DateTime *)self,
+ result, Py_EQ);
+ if (temp == NULL)
+ goto Fail;
+ if (temp == Py_True) {
+ Py_DECREF(temp);
+ return result;
+ }
+ Py_DECREF(temp);
+ /* Else there's no way to spell self in zone other.tz. */
+ PyErr_SetString(PyExc_ValueError, "astimezone(): the source "
+ "datetimetz can't be expressed in the target "
+ "timezone's local time");
+ goto Fail;
+
+Inconsistent:
+ PyErr_SetString(PyExc_ValueError, "astimezone(): tz.utcoffset() "
+ "gave inconsistent results; cannot convert");
+
+ /* fall thru to failure */
+Fail:
+ Py_DECREF(result);
+ return NULL;
}
static PyObject *