Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Doc/library/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1058,8 +1058,7 @@ Instance methods:

If provided, *tz* must be an instance of a :class:`tzinfo` subclass, and its
:meth:`utcoffset` and :meth:`dst` methods must not return ``None``. If *self*
is naive (``self.tzinfo is None``), it is presumed to represent time in the
system timezone.
is naive, it is presumed to represent time in the system timezone.

If called without arguments (or with ``tz=None``) the system local
timezone is assumed for the target timezone. The ``.tzinfo`` attribute of the converted
Expand Down
9 changes: 6 additions & 3 deletions Lib/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1773,14 +1773,17 @@ def astimezone(self, tz=None):
mytz = self.tzinfo
if mytz is None:
mytz = self._local_timezone()
myoffset = mytz.utcoffset(self)
else:
myoffset = mytz.utcoffset(self)
if myoffset is None:
mytz = self.replace(tzinfo=None)._local_timezone()
myoffset = mytz.utcoffset(self)

if tz is mytz:
return self

# Convert self to UTC, and attach the new time zone object.
myoffset = mytz.utcoffset(self)
if myoffset is None:
raise ValueError("astimezone() requires an aware datetime")
utc = (self - myoffset).replace(tzinfo=tz)

# Convert from UTC to tz's local time.
Expand Down
25 changes: 16 additions & 9 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2414,32 +2414,39 @@ def test_replace(self):
base = cls(2000, 2, 29)
self.assertRaises(ValueError, base.replace, year=2001)

@support.run_with_tz('EDT4')
def test_astimezone(self):
return # The rest is no longer applicable
# Pretty boring! The TZ test is more interesting here. astimezone()
# simply can't be applied to a naive object.
dt = self.theclass.now()
f = FixedOffset(44, "")
self.assertRaises(ValueError, dt.astimezone) # naive
f = FixedOffset(44, "0044")
dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
self.assertEqual(dt.astimezone(), dt_utc) # naive
self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
self.assertRaises(ValueError, dt.astimezone, f) # naive
self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
self.assertEqual(dt.astimezone(f), dt_f) # naive
self.assertEqual(dt.astimezone(tz=f), dt_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
self.assertRaises(ValueError,
dt.replace(tzinfo=bog).astimezone, f)
self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)

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 Broken(tzinfo):
def utcoffset(self, dt): return 1
def dst(self, dt): return 1
broken = Broken()
dt_broken = dt.replace(tzinfo=broken)
with self.assertRaises(TypeError):
dt_broken.astimezone()

def test_subclass_datetime(self):

class C(self.theclass):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Datetime instance d with non-None tzinfo, but with d.tzinfo.utcoffset(d)
returning None is now treated as naive by the astimezone() method.
11 changes: 11 additions & 0 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5576,6 +5576,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
return NULL;

if (!HASTZINFO(self) || self->tzinfo == Py_None) {
naive:
self_tzinfo = local_timezone_from_local(self);
if (self_tzinfo == NULL)
return NULL;
Expand All @@ -5596,6 +5597,16 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
Py_DECREF(self_tzinfo);
if (offset == NULL)
return NULL;
else if(offset == Py_None) {
Py_DECREF(offset);
goto naive;
}
else if (!PyDelta_Check(offset)) {
Py_DECREF(offset);
PyErr_Format(PyExc_TypeError, "utcoffset() returned %.200s,"
" expected timedelta or None", Py_TYPE(offset)->tp_name);
return NULL;
}
/* result = self - offset */
result = (PyDateTime_DateTime *)add_datetime_timedelta(self,
(PyDateTime_Delta *)offset, -1);
Expand Down