diff --git a/Lib/datetime.py b/Lib/datetime.py index f506e9a..eced0b2 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1059,6 +1059,30 @@ class time: """timezone info object""" return self._tzinfo + def __add__(self, other): + "Add a time and a timedelta" + if not isinstance(other, timedelta): + return NotImplemented + + delta = timedelta(hours=self._hour, + minutes=self._minute, + seconds=self._second, + microseconds=self._microsecond) + + delta += other + hour, rem = divmod(delta.seconds, 3600) + minute, second = divmod(rem, 60) + + return time(hour, minute, second, + delta.microseconds, + tzinfo=self._tzinfo) + + def __sub__(self, other): + "Subtract a time and a timedelta." + if isinstance(other, timedelta): + return self + -other + return NotImplemented + # Standard conversions, __hash__ (and helpers) # Comparisons of time objects with other. @@ -1122,8 +1146,10 @@ class time: return 2 # arbitrary non-zero value else: raise TypeError("cannot compare naive and aware times") + myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1) othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1) + return _cmp((myhhmm, self._second, self._microsecond), (othhmm, other._second, other._microsecond)) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 931ef6f..55d199e 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2041,6 +2041,27 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase): self.assertEqual(t.second, 0) self.assertEqual(t.microsecond, 0) + def test_computations(self): + a = self.theclass(0, 0, 0) + b = self.theclass(23, 59) + + minute = timedelta(minutes=1) + hour = timedelta(hours=1) + week = timedelta(days=7) + self.assertEqual(a + minute, self.theclass(0, 1, 0)) + self.assertEqual(b + minute + minute, self.theclass(0, 1)) + self.assertEqual(b + minute, self.theclass(0, 0)) + self.assertEqual(a + week, self.theclass(0)) + self.assertEqual(a + week, self.theclass(0)) + self.assertEqual(b + hour * 12, self.theclass(11, 59)) + + # Subtracting + self.assertEqual(a - minute, self.theclass(23, 59)) + self.assertEqual(a - minute * 2, self.theclass(23, 58)) + self.assertEqual(b - hour * 24, self.theclass(23, 59)) + self.assertEqual(b - week, self.theclass(23, 59)) + self.assertEqual(b - week - hour, self.theclass(22, 59)) + def test_basic_attributes_nonzero(self): # Make sure all attributes are non-zero so bugs in # bit-shifting access show up. @@ -2330,6 +2351,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase): self.assertRaises(TypeError, self.theclass, hour_byte + base[1:]) + # A mixin for classes with a tzinfo= argument. Subclasses must define # theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever) # must be legit (which is true for time and datetime). @@ -2497,6 +2519,46 @@ class TZInfoBase: class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): theclass = time + def test_computations(self): + est = FixedOffset(-300, "EST", 1) + utc = FixedOffset(0, "UTC", -2) + met = FixedOffset(60, "MET", 3) + + minute = timedelta(minutes=1) + hour = timedelta(hours=1) + week = timedelta(days=7) + + for tz in [est, utc, met]: + a = self.theclass(0, 0, 0, tzinfo=tz) + b = self.theclass(23, 59, tzinfo=tz) + self.assertEqual(a + minute, self.theclass(0, 1, 0, tzinfo=tz)) + self.assertEqual(b + minute + minute, self.theclass(0, 1, tzinfo=tz)) + self.assertEqual(b + minute, self.theclass(0, 0, tzinfo=tz)) + self.assertEqual(a + week, self.theclass(0, tzinfo=tz)) + self.assertEqual(a + week, self.theclass(0, tzinfo=tz)) + self.assertEqual(b + hour * 12, self.theclass(11, 59, tzinfo=tz)) + + c = self.theclass(0, 0, 0, tzinfo=est) + d = self.theclass(23, 59, 0, tzinfo=met) + + self.assertEqual(c + hour * 5, self.theclass(10, 0, tzinfo=utc)) + self.assertEqual(d + hour + minute, self.theclass(0, 0, tzinfo=utc)) + + # Subtracting + for tz in [est, utc, met]: + a = self.theclass(0, 0, 0, tzinfo=tz) + b = self.theclass(23, 59, tzinfo=tz) + + self.assertEqual(a - minute, self.theclass(23, 59, tzinfo=tz)) + self.assertEqual(a - minute * 2, self.theclass(23, 58, tzinfo=tz)) + self.assertEqual(b - hour * 24, self.theclass(23, 59, tzinfo=tz)) + self.assertEqual(b - week, self.theclass(23, 59, tzinfo=tz)) + self.assertEqual(b - week - hour, self.theclass(22, 59, tzinfo=tz)) + + e = self.theclass(0, tzinfo=est) + + self.assertEqual(e - hour * 5, self.theclass(0, tzinfo=utc)) + def test_empty(self): t = self.theclass() self.assertEqual(t.hour, 0) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 7c7170d..608f4b4 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3821,6 +3821,71 @@ time_bool(PyObject *self) return (TIME_GET_MINUTE(self)*60 - offsecs + TIME_GET_HOUR(self)*3600) != 0; } +/* Force all the time fields into range + * the day argument is there just because I don't know how to normalize the + * hours without it. + */ + +static void +normalize_time(int *hour, int *minute, int *second, int *microsecond) +{ + int day = 0; /* Used to normalize the hour */ + normalize_pair(second, microsecond, 1000000); + normalize_pair(minute, second, 60); + normalize_pair(hour, minute, 60); + normalize_pair(&day, hour, 24); +} + +/* + * Time arithmetic + */ + +/* time + timedelta -> time + * +.*/ +static PyObject * +add_time_timedelta(PyDateTime_Time *time, PyDateTime_Delta *delta, + int factor) +{ + int hour = TIME_GET_HOUR(time); + int minute = TIME_GET_MINUTE(time); + int second = TIME_GET_SECOND(time) + GET_TD_SECONDS(delta) * factor; + int microsecond = TIME_GET_MICROSECOND(time) + + GET_TD_MICROSECONDS(delta) * factor; + + assert(factor == 1 || factor == -1); + + normalize_time(&hour, &minute, &second, µsecond); + + return new_time(hour, minute, second, microsecond, + HASTZINFO(time) ? time->tzinfo : Py_None); +} + + +static PyObject * +time_add(PyObject *left, PyObject *right) +{ + if (PyDelta_Check(right)) { + /* time + delta */ + return add_time_timedelta((PyDateTime_Time *) left, + (PyDateTime_Delta *) right, + 1); + } + Py_RETURN_NOTIMPLEMENTED; +} + +static PyObject * +time_subtract(PyObject *left, PyObject *right) +{ + if (PyDelta_Check(right)) { + /* time - delta */ + return add_time_timedelta((PyDateTime_Time *) left, + (PyDateTime_Delta *) right, + -1); + } + Py_RETURN_NOTIMPLEMENTED; +} + /* Pickle support, a simple use of __reduce__. */ /* Let basestate be the non-tzinfo data string. @@ -3889,8 +3954,8 @@ All arguments are optional. tzinfo may be None, or an instance of\n\ a tzinfo subclass. The remaining arguments may be ints or longs.\n"); static PyNumberMethods time_as_number = { - 0, /* nb_add */ - 0, /* nb_subtract */ + time_add, /* nb_add */ + time_subtract, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */