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).
This commit is contained in:
parent
9a7c96a2bc
commit
52dcce24e2
|
@ -1313,20 +1313,27 @@ class TestDateTime(TestDate):
|
||||||
self.assertRaises(ValueError, base.replace, year=2001)
|
self.assertRaises(ValueError, base.replace, year=2001)
|
||||||
|
|
||||||
def test_astimezone(self):
|
def test_astimezone(self):
|
||||||
# Pretty boring! The TZ test is more interesting here.
|
# Pretty boring! The TZ test is more interesting here. astimezone()
|
||||||
|
# simply can't be applied to a naive object.
|
||||||
dt = self.theclass.now()
|
dt = self.theclass.now()
|
||||||
f = FixedOffset(44, "")
|
f = FixedOffset(44, "")
|
||||||
for dtz in dt.astimezone(f), dt.astimezone(tz=f):
|
|
||||||
self.failUnless(isinstance(dtz, datetime))
|
|
||||||
self.assertEqual(dt.date(), dtz.date())
|
|
||||||
self.assertEqual(dt.time(), dtz.time())
|
|
||||||
self.failUnless(dtz.tzinfo is f)
|
|
||||||
self.assertEqual(dtz.utcoffset(), timedelta(minutes=44))
|
|
||||||
|
|
||||||
self.assertRaises(TypeError, dt.astimezone) # not enough args
|
self.assertRaises(TypeError, dt.astimezone) # not enough args
|
||||||
self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
|
self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
|
||||||
self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
|
self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
|
||||||
|
self.assertRaises(ValueError, dt.astimezone, f) # naive
|
||||||
|
self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
|
||||||
|
|
||||||
|
class Bogus(tzinfo):
|
||||||
|
def utcoffset(self, dt): return None
|
||||||
|
def dst(self, dt): return timedelta(0)
|
||||||
|
bog = Bogus()
|
||||||
|
self.assertRaises(ValueError, dt.astimezone, bog) # naive
|
||||||
|
|
||||||
|
class AlsoBogus(tzinfo):
|
||||||
|
def utcoffset(self, dt): return timedelta(0)
|
||||||
|
def dst(self, dt): return None
|
||||||
|
alsobog = AlsoBogus()
|
||||||
|
self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
|
||||||
|
|
||||||
class TestTime(unittest.TestCase):
|
class TestTime(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -2443,17 +2450,11 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
|
||||||
|
|
||||||
dt = self.theclass.now(tzinfo=f44m)
|
dt = self.theclass.now(tzinfo=f44m)
|
||||||
self.failUnless(dt.tzinfo is f44m)
|
self.failUnless(dt.tzinfo is f44m)
|
||||||
# Replacing with degenerate tzinfo doesn't do any adjustment.
|
# Replacing with degenerate tzinfo raises an exception.
|
||||||
for x in dt.astimezone(fnone), dt.astimezone(tz=fnone):
|
self.assertRaises(ValueError, dt.astimezone, fnone)
|
||||||
self.failUnless(x.tzinfo is fnone)
|
# Ditto with None tz.
|
||||||
self.assertEqual(x.date(), dt.date())
|
self.assertRaises(TypeError, dt.astimezone, None)
|
||||||
self.assertEqual(x.time(), dt.time())
|
# Replacing with same tzinfo makes no change.
|
||||||
# Ditt with None tz.
|
|
||||||
x = dt.astimezone(tz=None)
|
|
||||||
self.failUnless(x.tzinfo is None)
|
|
||||||
self.assertEqual(x.date(), dt.date())
|
|
||||||
self.assertEqual(x.time(), dt.time())
|
|
||||||
# Ditto replacing with same tzinfo.
|
|
||||||
x = dt.astimezone(dt.tzinfo)
|
x = dt.astimezone(dt.tzinfo)
|
||||||
self.failUnless(x.tzinfo is f44m)
|
self.failUnless(x.tzinfo is f44m)
|
||||||
self.assertEqual(x.date(), dt.date())
|
self.assertEqual(x.date(), dt.date())
|
||||||
|
@ -2603,7 +2604,7 @@ class USTimeZone(tzinfo):
|
||||||
|
|
||||||
# Can't compare naive to aware objects, so strip the timezone from
|
# Can't compare naive to aware objects, so strip the timezone from
|
||||||
# dt first.
|
# dt first.
|
||||||
if start <= dt.astimezone(None) < end:
|
if start <= dt.replace(tzinfo=None) < end:
|
||||||
return HOUR
|
return HOUR
|
||||||
else:
|
else:
|
||||||
return ZERO
|
return ZERO
|
||||||
|
@ -2630,8 +2631,6 @@ class TestTimezoneConversions(unittest.TestCase):
|
||||||
|
|
||||||
# Conversion to our own timezone is always an identity.
|
# Conversion to our own timezone is always an identity.
|
||||||
self.assertEqual(dt.astimezone(tz), dt)
|
self.assertEqual(dt.astimezone(tz), dt)
|
||||||
# Conversion to None is always the same as stripping tzinfo.
|
|
||||||
self.assertEqual(dt.astimezone(None), dt.replace(tzinfo=None))
|
|
||||||
|
|
||||||
asutc = dt.astimezone(utc)
|
asutc = dt.astimezone(utc)
|
||||||
there_and_back = asutc.astimezone(tz)
|
there_and_back = asutc.astimezone(tz)
|
||||||
|
@ -2684,8 +2683,11 @@ class TestTimezoneConversions(unittest.TestCase):
|
||||||
|
|
||||||
# Conversion to our own timezone is always an identity.
|
# Conversion to our own timezone is always an identity.
|
||||||
self.assertEqual(dt.astimezone(tz), dt)
|
self.assertEqual(dt.astimezone(tz), dt)
|
||||||
# Conversion to None is always the same as stripping tzinfo.
|
|
||||||
self.assertEqual(dt.astimezone(None), dt.replace(tzinfo=None))
|
# Converting to UTC and back is an identity too.
|
||||||
|
asutc = dt.astimezone(utc)
|
||||||
|
there_and_back = asutc.astimezone(tz)
|
||||||
|
self.assertEqual(dt, there_and_back)
|
||||||
|
|
||||||
def convert_between_tz_and_utc(self, tz, utc):
|
def convert_between_tz_and_utc(self, tz, utc):
|
||||||
dston = self.dston.replace(tzinfo=tz)
|
dston = self.dston.replace(tzinfo=tz)
|
||||||
|
@ -2737,7 +2739,7 @@ class TestTimezoneConversions(unittest.TestCase):
|
||||||
# 22:00 on day before daylight starts.
|
# 22:00 on day before daylight starts.
|
||||||
fourback = self.dston - timedelta(hours=4)
|
fourback = self.dston - timedelta(hours=4)
|
||||||
ninewest = FixedOffset(-9*60, "-0900", 0)
|
ninewest = FixedOffset(-9*60, "-0900", 0)
|
||||||
fourback = fourback.astimezone(ninewest)
|
fourback = fourback.replace(tzinfo=ninewest)
|
||||||
# 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
|
# 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
|
||||||
# 2", we should get the 3 spelling.
|
# 2", we should get the 3 spelling.
|
||||||
# If we plug 22:00 the day before into Eastern, it "looks like std
|
# If we plug 22:00 the day before into Eastern, it "looks like std
|
||||||
|
@ -2746,17 +2748,17 @@ class TestTimezoneConversions(unittest.TestCase):
|
||||||
# local clock jumps from 1 to 3). The point here is to make sure we
|
# local clock jumps from 1 to 3). The point here is to make sure we
|
||||||
# get the 3 spelling.
|
# get the 3 spelling.
|
||||||
expected = self.dston.replace(hour=3)
|
expected = self.dston.replace(hour=3)
|
||||||
got = fourback.astimezone(Eastern).astimezone(None)
|
got = fourback.astimezone(Eastern).replace(tzinfo=None)
|
||||||
self.assertEqual(expected, got)
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
# Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
|
# Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
|
||||||
# case we want the 1:00 spelling.
|
# case we want the 1:00 spelling.
|
||||||
sixutc = self.dston.replace(hour=6).astimezone(utc_real)
|
sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
|
||||||
# Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
|
# Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
|
||||||
# and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
|
# and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
|
||||||
# spelling.
|
# spelling.
|
||||||
expected = self.dston.replace(hour=1)
|
expected = self.dston.replace(hour=1)
|
||||||
got = sixutc.astimezone(Eastern).astimezone(None)
|
got = sixutc.astimezone(Eastern).replace(tzinfo=None)
|
||||||
self.assertEqual(expected, got)
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
# Now on the day DST ends, we want "repeat an hour" behavior.
|
# Now on the day DST ends, we want "repeat an hour" behavior.
|
||||||
|
@ -2798,6 +2800,66 @@ class TestTimezoneConversions(unittest.TestCase):
|
||||||
def dst(self, dt): return None
|
def dst(self, dt): return None
|
||||||
self.assertRaises(ValueError, now.astimezone, notok())
|
self.assertRaises(ValueError, now.astimezone, notok())
|
||||||
|
|
||||||
|
def test_fromutc(self):
|
||||||
|
self.assertRaises(TypeError, Eastern.fromutc) # not enough args
|
||||||
|
now = datetime.utcnow().replace(tzinfo=utc_real)
|
||||||
|
self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
|
||||||
|
now = now.replace(tzinfo=Eastern) # insert correct tzinfo
|
||||||
|
enow = Eastern.fromutc(now) # doesn't blow up
|
||||||
|
self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
|
||||||
|
self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
|
||||||
|
self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
|
||||||
|
|
||||||
|
# Always converts UTC to standard time.
|
||||||
|
class FauxUSTimeZone(USTimeZone):
|
||||||
|
def fromutc(self, dt):
|
||||||
|
return dt + self.stdoffset
|
||||||
|
FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
|
||||||
|
|
||||||
|
# UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
|
||||||
|
# EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
|
||||||
|
# EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
|
||||||
|
|
||||||
|
# Check around DST start.
|
||||||
|
start = self.dston.replace(hour=4, tzinfo=Eastern)
|
||||||
|
fstart = start.replace(tzinfo=FEastern)
|
||||||
|
for wall in 23, 0, 1, 3, 4, 5:
|
||||||
|
expected = start.replace(hour=wall)
|
||||||
|
if wall == 23:
|
||||||
|
expected -= timedelta(days=1)
|
||||||
|
got = Eastern.fromutc(start)
|
||||||
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
|
expected = fstart + FEastern.stdoffset
|
||||||
|
got = FEastern.fromutc(fstart)
|
||||||
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
|
# Ensure astimezone() calls fromutc() too.
|
||||||
|
got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
|
||||||
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
|
start += HOUR
|
||||||
|
fstart += HOUR
|
||||||
|
|
||||||
|
# Check around DST end.
|
||||||
|
start = self.dstoff.replace(hour=4, tzinfo=Eastern)
|
||||||
|
fstart = start.replace(tzinfo=FEastern)
|
||||||
|
for wall in 0, 1, 1, 2, 3, 4:
|
||||||
|
expected = start.replace(hour=wall)
|
||||||
|
got = Eastern.fromutc(start)
|
||||||
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
|
expected = fstart + FEastern.stdoffset
|
||||||
|
got = FEastern.fromutc(fstart)
|
||||||
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
|
# Ensure astimezone() calls fromutc() too.
|
||||||
|
got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
|
||||||
|
self.assertEqual(expected, got)
|
||||||
|
|
||||||
|
start += HOUR
|
||||||
|
fstart += HOUR
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
allsuites = [unittest.makeSuite(klass, 'test')
|
allsuites = [unittest.makeSuite(klass, 'test')
|
||||||
|
|
|
@ -2725,24 +2725,106 @@ tzinfo_nogo(const char* methodname)
|
||||||
|
|
||||||
/* Methods. A subclass must implement these. */
|
/* Methods. A subclass must implement these. */
|
||||||
|
|
||||||
static PyObject*
|
static PyObject *
|
||||||
tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt)
|
tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt)
|
||||||
{
|
{
|
||||||
return tzinfo_nogo("tzname");
|
return tzinfo_nogo("tzname");
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject *
|
||||||
tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt)
|
tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt)
|
||||||
{
|
{
|
||||||
return tzinfo_nogo("utcoffset");
|
return tzinfo_nogo("utcoffset");
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject *
|
||||||
tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt)
|
tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt)
|
||||||
{
|
{
|
||||||
return tzinfo_nogo("dst");
|
return tzinfo_nogo("dst");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* Pickle support. This is solely so that tzinfo subclasses can use
|
||||||
* pickling -- tzinfo itself is supposed to be uninstantiable. The
|
* pickling -- tzinfo itself is supposed to be uninstantiable. The
|
||||||
|
@ -2772,6 +2854,9 @@ static PyMethodDef tzinfo_methods[] = {
|
||||||
{"dst", (PyCFunction)tzinfo_dst, METH_O,
|
{"dst", (PyCFunction)tzinfo_dst, METH_O,
|
||||||
PyDoc_STR("datetime -> DST offset in minutes east of UTC.")},
|
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}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4036,109 +4121,59 @@ datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
|
datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
|
||||||
{
|
{
|
||||||
int y = GET_YEAR(self);
|
int y, m, d, hh, mm, ss, us;
|
||||||
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 *result;
|
||||||
PyObject *temp;
|
int offset, none;
|
||||||
int selfoff, resoff, dst1;
|
|
||||||
int none;
|
|
||||||
int delta;
|
|
||||||
|
|
||||||
PyObject *tzinfo;
|
PyObject *tzinfo;
|
||||||
static char *keywords[] = {"tz", NULL};
|
static char *keywords[] = {"tz", NULL};
|
||||||
|
|
||||||
if (! PyArg_ParseTupleAndKeywords(args, kw, "O:astimezone", keywords,
|
if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:astimezone", keywords,
|
||||||
&tzinfo))
|
&PyDateTime_TZInfoType, &tzinfo))
|
||||||
return NULL;
|
|
||||||
if (check_tzinfo_subclass(tzinfo) < 0)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Don't call utcoffset unless necessary. */
|
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);
|
result = new_datetime(y, m, d, hh, mm, ss, us, tzinfo);
|
||||||
if (result == NULL ||
|
if (result != NULL) {
|
||||||
tzinfo == Py_None ||
|
PyObject *temp = result;
|
||||||
! HASTZINFO(self) ||
|
|
||||||
self->tzinfo == Py_None ||
|
|
||||||
self->tzinfo == tzinfo)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
/* Get the offsets. If either object turns out to be naive, again
|
result = PyObject_CallMethod(tzinfo, "fromutc", "O", temp);
|
||||||
* there's no conversion of date or time fields.
|
Py_DECREF(temp);
|
||||||
*/
|
|
||||||
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;
|
return result;
|
||||||
|
|
||||||
mm += dst1;
|
NeedAware:
|
||||||
if ((mm < 0 || mm >= 60) &&
|
PyErr_SetString(PyExc_ValueError, "astimezone() cannot be applied to "
|
||||||
normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us) < 0)
|
"a naive datetime");
|
||||||
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue