mirror of
https://github.com/python/cpython.git
synced 2025-11-01 10:45:30 +00:00
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.
This commit is contained in:
parent
a2f93885b0
commit
6c7a4182f5
3 changed files with 239 additions and 141 deletions
|
|
@ -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__)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue