Kaydet (Commit) 6c7a4182 authored tarafından Alexander Belopolsky's avatar Alexander Belopolsky

Closes issue #20858: Enhancements/fixes to pure-python datetime module

This patch brings the pure-python datetime more in-line with the C
module.  Patch contributed by Brian Kearns, a PyPy developer.  PyPy
project has been running these modifications in PyPy2 stdlib.

This commit includes:

- General PEP8/cleanups;
- Better testing of argument types passed to constructors;
- Removal of duplicate operations;
- Optimization of timedelta creation;
- Caching the result of __hash__ like the C accelerator;
- Enhancements/bug fixes in tests.
üst a2f93885
This diff is collapsed.
......@@ -50,6 +50,17 @@ class TestModule(unittest.TestCase):
self.assertEqual(datetime.MINYEAR, 1)
self.assertEqual(datetime.MAXYEAR, 9999)
def test_name_cleanup(self):
if '_Fast' not in str(self):
return
datetime = datetime_module
names = set(name for name in dir(datetime)
if not name.startswith('__') and not name.endswith('__'))
allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
'datetime_CAPI', 'time', 'timedelta', 'timezone',
'tzinfo'])
self.assertEqual(names - allowed, set([]))
#############################################################################
# tzinfo tests
......@@ -616,8 +627,12 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
# Single-field rounding.
eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
eq(td(milliseconds=0.5/1000), td(microseconds=0))
eq(td(milliseconds=-0.5/1000), td(microseconds=0))
eq(td(milliseconds=0.6/1000), td(microseconds=1))
eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
eq(td(seconds=0.5/10**6), td(microseconds=0))
eq(td(seconds=-0.5/10**6), td(microseconds=0))
# Rounding due to contributions from more than one field.
us_per_hour = 3600e6
......@@ -1131,11 +1146,13 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
#check that this standard extension works
t.strftime("%f")
def test_format(self):
dt = self.theclass(2007, 9, 10)
self.assertEqual(dt.__format__(''), str(dt))
with self.assertRaisesRegex(TypeError, '^must be str, not int$'):
dt.__format__(123)
# check that a derived class's __str__() gets called
class A(self.theclass):
def __str__(self):
......@@ -1391,9 +1408,10 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
for month_byte in b'9', b'\0', b'\r', b'\xff':
self.assertRaises(TypeError, self.theclass,
base[:2] + month_byte + base[3:])
# Good bytes, but bad tzinfo:
self.assertRaises(TypeError, self.theclass,
bytes([1] * len(base)), 'EST')
if issubclass(self.theclass, datetime):
# Good bytes, but bad tzinfo:
with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
self.theclass(bytes([1] * len(base)), 'EST')
for ord_byte in range(1, 13):
# This shouldn't blow up because of the month byte alone. If
......@@ -1469,6 +1487,9 @@ class TestDateTime(TestDate):
dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
self.assertEqual(dt.__format__(''), str(dt))
with self.assertRaisesRegex(TypeError, '^must be str, not int$'):
dt.__format__(123)
# check that a derived class's __str__() gets called
class A(self.theclass):
def __str__(self):
......@@ -1789,6 +1810,7 @@ class TestDateTime(TestDate):
tzinfo=timezone(timedelta(hours=-5), 'EST'))
self.assertEqual(t.timestamp(),
18000 + 3600 + 2*60 + 3 + 4*1e-6)
def test_microsecond_rounding(self):
for fts in [self.theclass.fromtimestamp,
self.theclass.utcfromtimestamp]:
......@@ -1839,6 +1861,7 @@ class TestDateTime(TestDate):
for insane in -1e200, 1e200:
self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
insane)
@unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
def test_negative_float_fromtimestamp(self):
# The result is tz-dependent; at least test that this doesn't
......@@ -2218,6 +2241,9 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
t = self.theclass(1, 2, 3, 4)
self.assertEqual(t.__format__(''), str(t))
with self.assertRaisesRegex(TypeError, '^must be str, not int$'):
t.__format__(123)
# check that a derived class's __str__() gets called
class A(self.theclass):
def __str__(self):
......@@ -2347,6 +2373,9 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
for hour_byte in ' ', '9', chr(24), '\xff':
self.assertRaises(TypeError, self.theclass,
hour_byte + base[1:])
# Good bytes, but bad tzinfo:
with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
self.theclass(bytes([1] * len(base)), 'EST')
# A mixin for classes with a tzinfo= argument. Subclasses must define
# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
......@@ -2606,7 +2635,7 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
self.assertRaises(TypeError, t.strftime, "%Z")
# Issue #6697:
if '_Fast' in str(type(self)):
if '_Fast' in str(self):
Badtzname.tz = '\ud800'
self.assertRaises(ValueError, t.strftime, "%Z")
......@@ -3768,6 +3797,61 @@ class Oddballs(unittest.TestCase):
self.assertEqual(as_datetime, datetime_sc)
self.assertEqual(datetime_sc, as_datetime)
def test_extra_attributes(self):
for x in [date.today(),
time(),
datetime.utcnow(),
timedelta(),
tzinfo(),
timezone(timedelta())]:
with self.assertRaises(AttributeError):
x.abc = 1
def test_check_arg_types(self):
import decimal
class Number:
def __init__(self, value):
self.value = value
def __int__(self):
return self.value
for xx in [decimal.Decimal(10),
decimal.Decimal('10.9'),
Number(10)]:
self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
datetime(xx, xx, xx, xx, xx, xx, xx))
with self.assertRaisesRegex(TypeError, '^an integer is required '
'\(got type str\)$'):
datetime(10, 10, '10')
f10 = Number(10.9)
with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
'\(type float\)$'):
datetime(10, 10, f10)
class Float(float):
pass
s10 = Float(10.9)
with self.assertRaisesRegex(TypeError, '^integer argument expected, '
'got float$'):
datetime(10, 10, s10)
with self.assertRaises(TypeError):
datetime(10., 10, 10)
with self.assertRaises(TypeError):
datetime(10, 10., 10)
with self.assertRaises(TypeError):
datetime(10, 10, 10.)
with self.assertRaises(TypeError):
datetime(10, 10, 10, 10.)
with self.assertRaises(TypeError):
datetime(10, 10, 10, 10, 10.)
with self.assertRaises(TypeError):
datetime(10, 10, 10, 10, 10, 10.)
with self.assertRaises(TypeError):
datetime(10, 10, 10, 10, 10, 10, 10.)
def test_main():
support.run_unittest(__name__)
......
......@@ -689,6 +689,7 @@ Per Øyvind Karlsen
Anton Kasyanov
Lou Kates
Hiroaki Kawai
Brian Kearns
Sebastien Keim
Ryan Kelly
Dan Kenigsberg
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment