[Python-checkins] bpo-33812: Corrected astimezone for naive datetimes. (GH-7578) (GH-7600)

Alexander Belopolsky webhook-mailer at python.org
Sun Jun 10 18:02:27 EDT 2018


https://github.com/python/cpython/commit/037e9125527d4a55af566f161c96a61b3c3fd998
commit: 037e9125527d4a55af566f161c96a61b3c3fd998
branch: 3.7
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: Alexander Belopolsky <abalkin at users.noreply.github.com>
date: 2018年06月10日T18:02:24-04:00
summary:
bpo-33812: Corrected astimezone for naive datetimes. (GH-7578) (GH-7600)
A datetime object d is aware if d.tzinfo is not None and
d.tzinfo.utcoffset(d) does not return None. If d.tzinfo is None,
or if d.tzinfo is not None but d.tzinfo.utcoffset(d) returns None,
 d is naive.
This commit ensures that instances with non-None d.tzinfo, but
d.tzinfo.utcoffset(d) returning None are treated as naive.
In addition, C acceleration code will raise TypeError if
d.tzinfo.utcoffset(d) returns an object with the type other than
timedelta.
* Updated the documentation.
Assume that the term "naive" is defined elsewhere and remove the
not entirely correct clarification. Thanks, Tim.
(cherry picked from commit 877b23202b7e7d4f57b58504fd0eb886e8c0b377)
Co-authored-by: Alexander Belopolsky <abalkin at users.noreply.github.com>
files:
A Misc/NEWS.d/next/Library/2018-06-10-13-26-02.bpo-33812.frGAOr.rst
M Doc/library/datetime.rst
M Lib/datetime.py
M Lib/test/datetimetester.py
M Modules/_datetimemodule.c
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
index 8d91f4ef9346..1ac2570eae5a 100644
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -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
diff --git a/Lib/datetime.py b/Lib/datetime.py
index 8fa18a78932c..dd6eca907dd4 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -1780,14 +1780,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.
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index a7e5e0b42446..7d4cdac9f41a 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -2414,25 +2414,24 @@ 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)
@@ -2440,6 +2439,14 @@ 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):
diff --git a/Misc/NEWS.d/next/Library/2018-06-10-13-26-02.bpo-33812.frGAOr.rst b/Misc/NEWS.d/next/Library/2018-06-10-13-26-02.bpo-33812.frGAOr.rst
new file mode 100644
index 000000000000..0dc3df6a7953
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-06-10-13-26-02.bpo-33812.frGAOr.rst
@@ -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.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 6855903bdd57..0a3856caa88d 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -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;
@@ -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);


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /