bpo-32417: Make timedelta arithmetic respect subclasses (#10902) · python/cpython@89427cd

@@ -820,6 +820,44 @@ def as_hours(self):

820820

self.assertEqual(str(t3), str(t4))

821821

self.assertEqual(t4.as_hours(), -1)

822822823+

def test_subclass_date(self):

824+

class DateSubclass(date):

825+

pass

826+827+

d1 = DateSubclass(2018, 1, 5)

828+

td = timedelta(days=1)

829+830+

tests = [

831+

('add', lambda d, t: d + t, DateSubclass(2018, 1, 6)),

832+

('radd', lambda d, t: t + d, DateSubclass(2018, 1, 6)),

833+

('sub', lambda d, t: d - t, DateSubclass(2018, 1, 4)),

834+

]

835+836+

for name, func, expected in tests:

837+

with self.subTest(name):

838+

act = func(d1, td)

839+

self.assertEqual(act, expected)

840+

self.assertIsInstance(act, DateSubclass)

841+842+

def test_subclass_datetime(self):

843+

class DateTimeSubclass(datetime):

844+

pass

845+846+

d1 = DateTimeSubclass(2018, 1, 5, 12, 30)

847+

td = timedelta(days=1, minutes=30)

848+849+

tests = [

850+

('add', lambda d, t: d + t, DateTimeSubclass(2018, 1, 6, 13)),

851+

('radd', lambda d, t: t + d, DateTimeSubclass(2018, 1, 6, 13)),

852+

('sub', lambda d, t: d - t, DateTimeSubclass(2018, 1, 4, 12)),

853+

]

854+855+

for name, func, expected in tests:

856+

with self.subTest(name):

857+

act = func(d1, td)

858+

self.assertEqual(act, expected)

859+

self.assertIsInstance(act, DateTimeSubclass)

860+823861

def test_division(self):

824862

t = timedelta(hours=1, minutes=24, seconds=19)

825863

second = timedelta(seconds=1)

@@ -2604,33 +2642,58 @@ def __new__(cls, *args, **kwargs):

26042642

ts = base_d.timestamp()

2605264326062644

test_cases = [

2607-

('fromtimestamp', (ts,)),

2645+

('fromtimestamp', (ts,), base_d),

26082646

# See https://bugs.python.org/issue32417

2609-

# ('fromtimestamp', (ts, timezone.utc)),

2610-

('utcfromtimestamp', (utc_ts,)),

2611-

('fromisoformat', (d_isoformat,)),

2612-

('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')),

2613-

('combine', (date(*args[0:3]), time(*args[3:]))),

2647+

('fromtimestamp', (ts, timezone.utc),

2648+

base_d.astimezone(timezone.utc)),

2649+

('utcfromtimestamp', (utc_ts,), base_d),

2650+

('fromisoformat', (d_isoformat,), base_d),

2651+

('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),

2652+

('combine', (date(*args[0:3]), time(*args[3:])), base_d),

26142653

]

261526542616-

for constr_name, constr_args in test_cases:

2655+

for constr_name, constr_args, expected in test_cases:

26172656

for base_obj in (DateTimeSubclass, base_d):

26182657

# Test both the classmethod and method

26192658

with self.subTest(base_obj_type=type(base_obj),

26202659

constr_name=constr_name):

2621-

constr = getattr(base_obj, constr_name)

2660+

constructor = getattr(base_obj, constr_name)

262226612623-

dt = constr(*constr_args)

2662+

dt = constructor(*constr_args)

2624266326252664

# Test that it creates the right subclass

26262665

self.assertIsInstance(dt, DateTimeSubclass)

2627266626282667

# Test that it's equal to the base object

2629-

self.assertEqual(dt, base_d.replace(tzinfo=None))

2668+

self.assertEqual(dt, expected)

2630266926312670

# Test that it called the constructor

26322671

self.assertEqual(dt.extra, 7)

263326722673+

def test_subclass_now(self):

2674+

# Test that alternate constructors call the constructor

2675+

class DateTimeSubclass(self.theclass):

2676+

def __new__(cls, *args, **kwargs):

2677+

result = self.theclass.__new__(cls, *args, **kwargs)

2678+

result.extra = 7

2679+2680+

return result

2681+2682+

test_cases = [

2683+

('now', 'now', {}),

2684+

('utcnow', 'utcnow', {}),

2685+

('now_utc', 'now', {'tz': timezone.utc}),

2686+

('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),

2687+

]

2688+2689+

for name, meth_name, kwargs in test_cases:

2690+

with self.subTest(name):

2691+

constr = getattr(DateTimeSubclass, meth_name)

2692+

dt = constr(**kwargs)

2693+2694+

self.assertIsInstance(dt, DateTimeSubclass)

2695+

self.assertEqual(dt.extra, 7)

2696+26342697

def test_fromisoformat_datetime(self):

26352698

# Test that isoformat() is reversible

26362699

base_dates = [