diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -759,13 +759,19 @@ :attr:`tzinfo` ``None``. This may raise :exc:`OverflowError`, if the timestamp is out of the range of values supported by the platform C :c:func:`gmtime` function, and :exc:`OSError` on :c:func:`gmtime` failure. - It's common for this to be restricted to years in 1970 through 2038. See also - :meth:`fromtimestamp`. - - On the POSIX compliant platforms, ``utcfromtimestamp(timestamp)`` - is equivalent to the following expression:: - - datetime(1970, 1, 1) + timedelta(seconds=timestamp) + It's common for this to be restricted to years in 1970 through 2038. + + To get an aware :class:`.datetime` object, call :meth:`fromtimestamp`:: + + datetime.fromtimestamp(timestamp, timezone.utc) + + On the POSIX compliant platforms, it is equivalent to the following + expression:: + + datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=timestamp) + + except the latter formula always supports the full years range: between + :const:`MINYEAR` and :const:`MAXYEAR` inclusive. .. versionchanged:: 3.3 Raise :exc:`OverflowError` instead of :exc:`ValueError` if the timestamp diff --git a/Lib/datetime.py b/Lib/datetime.py --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1390,7 +1390,10 @@ @classmethod def utcfromtimestamp(cls, t): - "Construct a UTC datetime from a POSIX timestamp (like time.time())." + """Construct a naive UTC datetime from a POSIX timestamp + (like time.time()). + + """ t, frac = divmod(t, 1.0) us = int(frac * 1e6) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3429,6 +3429,66 @@ self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7) +class TestDatetimeRange(unittest.TestCase): + # test datetime <-> timestamp conversion for extreme datetime values + # - datetime + timedelta should work everywhere + # - fromtimestamp fails on Windows (and possibly on other systems) + def setUp(self): + SECOND = timedelta(seconds=1) + self.epoch = datetime(1970, 1, 1, tzinfo=timezone.utc) + self.windows_max = datetime(3000, 12, 31, 23, 59, 59, 999999, tzinfo=timezone.utc) + timestamp = lambda utc_dt: (utc_dt - self.epoch) // SECOND # integer ts + self.min_utc = datetime.min.replace(tzinfo=timezone.utc) + assert self.min_utc.microsecond == 0 + self.min_ts = timestamp(self.min_utc) + self.max_utc = datetime.max.replace(tzinfo=timezone.utc, microsecond=0) + self.max_ts = timestamp(self.max_utc) + + @unittest.skipUnless(sys.platform == "win32", "Windows doesn't accept negative timestamps") + def test_utcfromtimestamp_min(self): + self.assertLess(self.min_ts, 0) + with self.assertRaises(OSError): + self.assertEqual(self.min_utc.replace(tzinfo=None), + datetime.utcfromtimestamp(self.min_ts)) + + @unittest.skipUnless(sys.platform == "win32", "Windows doesn't accept >3000 timestamps") + def test_utcfromtimestamp_max(self): + self.assertGreater(self.max_utc, self.windows_max) + with self.assertRaises(OSError): + self.assertEqual(self.max_utc.replace(tzinfo=None), + datetime.utcfromtimestamp(self.max_ts)) + + @unittest.skipUnless(sys.platform == "win32", "Windows doesn't accept negative timestamps") + def test_fromtimestamp_tz_min(self): + self.assertLess(self.min_ts, 0) + with self.assertRaises(OSError): + self.assertEqual(self.min_utc, + datetime.fromtimestamp(self.min_ts, timezone.utc)) + + @unittest.skipUnless(sys.platform == "win32", "Windows doesn't accept >3000 timestamps") + def test_fromtimestamp_tz_max(self): + self.assertGreater(self.max_utc, self.windows_max) + with self.assertRaises(OSError): + self.assertEqual(self.max_utc, + datetime.fromtimestamp(self.max_ts, timezone.utc)) + + def test_timedelta_naive_min(self): + self.assertEqual(self.min_utc.replace(tzinfo=None), + self.epoch.replace(tzinfo=None) + timedelta(seconds=self.min_ts)) + + def test_timedelta_min(self): + self.assertEqual(self.min_utc, + self.epoch + timedelta(seconds=self.min_ts)) + + def test_timedelta_naive_max(self): + self.assertEqual(self.max_utc.replace(tzinfo=None), + self.epoch.replace(tzinfo=None) + timedelta(seconds=self.max_ts)) + + def test_timedelta_max(self): + self.assertEqual(self.max_utc, + self.epoch + timedelta(seconds=self.max_ts)) + + # Pain to set up DST-aware tzinfo classes. def first_sunday_on_or_after(dt): diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -5020,7 +5020,7 @@ {"utcfromtimestamp", (PyCFunction)datetime_utcfromtimestamp, METH_VARARGS | METH_CLASS, - PyDoc_STR("timestamp -> UTC datetime from a POSIX timestamp " + PyDoc_STR("Construct a naive UTC datetime from a POSIX timestamp " "(like time.time()).")}, {"strptime", (PyCFunction)datetime_strptime,