diff options
author | Tim Peters <tim.peters@gmail.com> | 2002-12-31 17:36:56 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2002-12-31 17:36:56 (GMT) |
commit | 521fc15e626ed53ba90c966c4a2392ab487fd62e (patch) | |
tree | 586b22e2773638958fe158c29a04d41d1af179e1 /Modules/datetimemodule.c | |
parent | ba2f875d90c5b8650d6bde2a152be8f899e15469 (diff) | |
download | cpython-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.c | 150 |
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 * |