bpo-32417: Make timedelta arithmetic respect subclasses (#10902) · python/cpython@89427cd
@@ -820,6 +820,44 @@ def as_hours(self):
820820self.assertEqual(str(t3), str(t4))
821821self.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+823861def test_division(self):
824862t = timedelta(hours=1, minutes=24, seconds=19)
825863second = timedelta(seconds=1)
@@ -2604,33 +2642,58 @@ def __new__(cls, *args, **kwargs):
26042642ts = base_d.timestamp()
2605264326062644test_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:
26172656for base_obj in (DateTimeSubclass, base_d):
26182657# Test both the classmethod and method
26192658with self.subTest(base_obj_type=type(base_obj),
26202659constr_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
26262665self.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
26322671self.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+26342697def test_fromisoformat_datetime(self):
26352698# Test that isoformat() is reversible
26362699base_dates = [