diff options
-rw-r--r-- | Doc/lib/libdatetime.tex | 29 | ||||
-rw-r--r-- | Lib/test/test_datetime.py | 150 | ||||
-rw-r--r-- | Modules/datetimemodule.c | 180 |
3 files changed, 332 insertions, 27 deletions
diff --git a/Doc/lib/libdatetime.tex b/Doc/lib/libdatetime.tex index a7d9926..8bfc2bf 100644 --- a/Doc/lib/libdatetime.tex +++ b/Doc/lib/libdatetime.tex @@ -365,6 +365,12 @@ Supported operations: Instance methods: + - replace(year=, month=, day=) + Return a date with the same value, except for those fields given + new values by whichever keyword arguments are specified. For + example, if \code{d == date(2002, 12, 31)}, then + \code{d.replace(day=26) == date(2000, 12, 26)}. + - timetuple() Return a 9-element tuple of the form returned by \function{time.localtime()}. The hours, minutes and seconds are @@ -591,6 +597,10 @@ Instance methods: - time() Return time object with same hour, minute, second and microsecond. + - replace(year=, month=, day=, hour=, minute=, second=, microsecond=) + Return a datetime with the same value, except for those fields given + new values by whichever keyword arguments are specified. + - timetuple() Return a 9-element tuple of the form returned by \function{time.localtime()}. @@ -710,6 +720,10 @@ Supported operations: Instance methods: + - replace(hour=, minute=, second=, microsecond=) + Return a time with the same value, except for those fields given + new values by whichever keyword arguments are specified. + - isoformat() Return a string representing the time in ISO 8601 format, HH:MM:SS.mmmmmm @@ -857,6 +871,12 @@ Supported operations: Instance methods: + - replace(hour=, minute=, second=, microsecond=, tzinfo=) + Return a timetz with the same value, except for those fields given + new values by whichever keyword arguments are specified. Note that + \code{tzinfo=None} can be specified to create a naive timetz from an + aware timetz. + - isoformat() Return a string representing the time in ISO 8601 format, HH:MM:SS.mmmmmm @@ -1048,11 +1068,18 @@ Instance methods: Return \class{timetz} object with same hour, minute, second, microsecond, and tzinfo. + - replace(year=, month=, day=, hour=, minute=, second=, microsecond=, + tzinfo=) + Return a datetimetz with the same value, except for those fields given + new values by whichever keyword arguments are specified. Note that + \code{tzinfo=None} can be specified to create a naive datetimetz from + an aware datetimetz. + - utcoffset() If \member{tzinfo} is \code{None}, returns \code{None}, else \code{tzinfo.utcoffset(self)}. - - tzname(): + - tzname() If \member{tzinfo} is \code{None}, returns \code{None}, else \code{tzinfo.tzname(self)}. diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index 6838e47..fe52212 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -874,6 +874,28 @@ class TestDate(unittest.TestCase): self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900") for y in 1, 49, 51, 99, 100, 1000, 1899: self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y") + + def test_replace(self): + cls = self.theclass + args = [1, 2, 3] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("year", 2), + ("month", 3), + ("day", 4)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Out of bounds. + base = cls(2000, 2, 29) + self.assertRaises(ValueError, base.replace, year=2001) + ############################################################################# # datetime tests @@ -1248,6 +1270,32 @@ class TestDateTime(TestDate): self.assertRaises(TypeError, combine, d, t, 1) # too many args self.assertRaises(TypeError, combine, "date", "time") # wrong types + def test_replace(self): + cls = self.theclass + args = [1, 2, 3, 4, 5, 6, 7] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("year", 2), + ("month", 3), + ("day", 4), + ("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Out of bounds. + base = cls(2000, 2, 29) + self.assertRaises(ValueError, base.replace, year=2001) + + class TestTime(unittest.TestCase): theclass = time @@ -1464,6 +1512,31 @@ class TestTime(unittest.TestCase): self.failUnless(not cls(0)) self.failUnless(not cls()) + def test_replace(self): + cls = self.theclass + args = [1, 2, 3, 4] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Out of bounds. + base = cls(1) + self.assertRaises(ValueError, base.replace, hour=24) + self.assertRaises(ValueError, base.replace, minute=-1) + self.assertRaises(ValueError, base.replace, second=100) + self.assertRaises(ValueError, base.replace, microsecond=1000000) + # 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 timetz and datetimetz). @@ -1735,6 +1808,45 @@ class TestTimeTZ(TestTime, TZInfoBase): t = cls(0, tzinfo=FixedOffset(-24*60, "")) self.assertRaises(ValueError, lambda: bool(t)) + def test_replace(self): + cls = self.theclass + z100 = FixedOffset(100, "+100") + zm200 = FixedOffset(timedelta(minutes=-200), "-200") + args = [1, 2, 3, 4, z100] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8), + ("tzinfo", zm200)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Ensure we can get rid of a tzinfo. + self.assertEqual(base.tzname(), "+100") + base2 = base.replace(tzinfo=None) + self.failUnless(base2.tzinfo is None) + self.failUnless(base2.tzname() is None) + + # Ensure we can add one. + base3 = base2.replace(tzinfo=z100) + self.assertEqual(base, base3) + self.failUnless(base.tzinfo is base3.tzinfo) + + # Out of bounds. + base = cls(1) + self.assertRaises(ValueError, base.replace, hour=24) + self.assertRaises(ValueError, base.replace, minute=-1) + self.assertRaises(ValueError, base.replace, second=100) + self.assertRaises(ValueError, base.replace, microsecond=1000000) + class TestDateTimeTZ(TestDateTime, TZInfoBase): theclass = datetimetz @@ -2157,6 +2269,44 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr) self.assertEqual(str(d), datestr + ' ' + tailstr) + def test_replace(self): + cls = self.theclass + z100 = FixedOffset(100, "+100") + zm200 = FixedOffset(timedelta(minutes=-200), "-200") + args = [1, 2, 3, 4, 5, 6, 7, z100] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("year", 2), + ("month", 3), + ("day", 4), + ("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8), + ("tzinfo", zm200)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Ensure we can get rid of a tzinfo. + self.assertEqual(base.tzname(), "+100") + base2 = base.replace(tzinfo=None) + self.failUnless(base2.tzinfo is None) + self.failUnless(base2.tzname() is None) + + # Ensure we can add one. + base3 = base2.replace(tzinfo=z100) + self.assertEqual(base, base3) + self.failUnless(base.tzinfo is base3.tzinfo) + + # Out of bounds. + base = cls(2000, 2, 29) + self.assertRaises(ValueError, base.replace, year=2001) def test_suite(): allsuites = [unittest.makeSuite(klass, 'test') diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index 250ee23..6fd10ed 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -2143,6 +2143,8 @@ static PyGetSetDef date_getset[] = { /* Constructors. */ +static char *date_kws[] = {"year", "month", "day", NULL}; + static PyObject * date_new(PyTypeObject *type, PyObject *args, PyObject *kw) { @@ -2151,11 +2153,7 @@ date_new(PyTypeObject *type, PyObject *args, PyObject *kw) int month; int day; - static char *keywords[] = { - "year", "month", "day", NULL - }; - - if (PyArg_ParseTupleAndKeywords(args, kw, "iii", keywords, + if (PyArg_ParseTupleAndKeywords(args, kw, "iii", date_kws, &year, &month, &day)) { if (check_date_args(year, month, day) < 0) return NULL; @@ -2454,6 +2452,26 @@ date_timetuple(PyDateTime_Date *self) 0, 0, 0, -1); } +static PyObject * +date_replace(PyDateTime_Date *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int year = GET_YEAR(self); + int month = GET_MONTH(self); + int day = GET_DAY(self); + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iii:replace", date_kws, + &year, &month, &day)) + return NULL; + tuple = Py_BuildValue("iii", year, month, day); + if (tuple == NULL) + return NULL; + clone = date_new(self->ob_type, tuple, NULL); + Py_DECREF(tuple); + return clone; +} + static PyObject *date_getstate(PyDateTime_Date *self); static long @@ -2602,6 +2620,9 @@ static PyMethodDef date_methods[] = { PyDoc_STR("Return the day of the week represented by the date.\n" "Monday == 0 ... Sunday == 6")}, + {"replace", (PyCFunction)date_replace, METH_KEYWORDS, + PyDoc_STR("Return date with new specified fields.")}, + {"__setstate__", (PyCFunction)date_setstate, METH_O, PyDoc_STR("__setstate__(state)")}, @@ -2712,6 +2733,11 @@ static PyGetSetDef datetime_getset[] = { /* Constructors. */ + +static char *datetime_kws[] = {"year", "month", "day", + "hour", "minute", "second", "microsecond", + NULL}; + static PyObject * datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) { @@ -2724,12 +2750,7 @@ datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) int second = 0; int usecond = 0; - static char *keywords[] = { - "year", "month", "day", "hour", "minute", "second", - "microsecond", NULL - }; - - if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiii", keywords, + if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiii", datetime_kws, &year, &month, &day, &hour, &minute, &second, &usecond)) { if (check_date_args(year, month, day) < 0) @@ -3201,6 +3222,31 @@ datetime_hash(PyDateTime_DateTime *self) } static PyObject * +datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int y = GET_YEAR(self); + int m = GET_MONTH(self); + int d = GET_DAY(self); + int hh = DATE_GET_HOUR(self); + int mm = DATE_GET_MINUTE(self); + int ss = DATE_GET_SECOND(self); + int us = DATE_GET_MICROSECOND(self); + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiii:replace", + datetime_kws, + &y, &m, &d, &hh, &mm, &ss, &us)) + return NULL; + tuple = Py_BuildValue("iiiiiii", y, m, d, hh, mm, ss, us); + if (tuple == NULL) + return NULL; + clone = datetime_new(self->ob_type, tuple, NULL); + Py_DECREF(tuple); + return clone; +} + +static PyObject * datetime_timetuple(PyDateTime_DateTime *self) { return build_struct_time(GET_YEAR(self), @@ -3348,6 +3394,9 @@ static PyMethodDef datetime_methods[] = { "defaults\n" "to 'T'.")}, + {"replace", (PyCFunction)datetime_replace, METH_KEYWORDS, + PyDoc_STR("Return datetime with new specified fields.")}, + {"__setstate__", (PyCFunction)datetime_setstate, METH_O, PyDoc_STR("__setstate__(state)")}, @@ -3457,6 +3506,8 @@ static PyGetSetDef time_getset[] = { /* Constructors. */ +static char *time_kws[] = {"hour", "minute", "second", "microsecond", NULL}; + static PyObject * time_new(PyTypeObject *type, PyObject *args, PyObject *kw) { @@ -3466,11 +3517,8 @@ time_new(PyTypeObject *type, PyObject *args, PyObject *kw) int second = 0; int usecond = 0; - static char *keywords[] = { - "hour", "minute", "second", "microsecond", NULL - }; - if (PyArg_ParseTupleAndKeywords(args, kw, "|iiii", keywords, + if (PyArg_ParseTupleAndKeywords(args, kw, "|iiii", time_kws, &hour, &minute, &second, &usecond)) { if (check_time_args(hour, minute, second, usecond) < 0) return NULL; @@ -3669,6 +3717,28 @@ time_hash(PyDateTime_Time *self) return self->hashcode; } +static PyObject * +time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int hh = TIME_GET_HOUR(self); + int mm = TIME_GET_MINUTE(self); + int ss = TIME_GET_SECOND(self); + int us = TIME_GET_MICROSECOND(self); + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiii:replace", + time_kws, + &hh, &mm, &ss, &us)) + return NULL; + tuple = Py_BuildValue("iiii", hh, mm, ss, us); + if (tuple == NULL) + return NULL; + clone = time_new(self->ob_type, tuple, NULL); + Py_DECREF(tuple); + return clone; +} + static int time_nonzero(PyDateTime_Time *self) { @@ -3759,6 +3829,9 @@ static PyMethodDef time_methods[] = { {"strftime", (PyCFunction)time_strftime, METH_KEYWORDS, PyDoc_STR("format -> strftime() style string.")}, + {"replace", (PyCFunction)time_replace, METH_KEYWORDS, + PyDoc_STR("Return datetime with new specified fields.")}, + {"__setstate__", (PyCFunction)time_setstate, METH_O, PyDoc_STR("__setstate__(state)")}, @@ -3977,6 +4050,9 @@ static PyGetSetDef timetz_getset[] = { * Constructors. */ +static char *timetz_kws[] = {"hour", "minute", "second", "microsecond", + "tzinfo", NULL}; + static PyObject * timetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) { @@ -3987,11 +4063,7 @@ timetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) int usecond = 0; PyObject *tzinfo = Py_None; - static char *keywords[] = { - "hour", "minute", "second", "microsecond", "tzinfo", NULL - }; - - if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO", keywords, + if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO", timetz_kws, &hour, &minute, &second, &usecond, &tzinfo)) { if (check_time_args(hour, minute, second, usecond) < 0) @@ -4078,6 +4150,29 @@ timetz_isoformat(PyDateTime_TimeTZ *self) /* Note: tp_richcompare and tp_hash are inherited from time. */ +static PyObject * +timetz_replace(PyDateTime_TimeTZ *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int hh = TIME_GET_HOUR(self); + int mm = TIME_GET_MINUTE(self); + int ss = TIME_GET_SECOND(self); + int us = TIME_GET_MICROSECOND(self); + PyObject *tzinfo = self->tzinfo; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO:replace", + timetz_kws, + &hh, &mm, &ss, &us, &tzinfo)) + return NULL; + tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); + if (tuple == NULL) + return NULL; + clone = timetz_new(self->ob_type, tuple, NULL); + Py_DECREF(tuple); + return clone; +} + static int timetz_nonzero(PyDateTime_TimeTZ *self) { @@ -4204,6 +4299,9 @@ static PyMethodDef timetz_methods[] = { {"dst", (PyCFunction)timetz_dst, METH_NOARGS, PyDoc_STR("Return self.tzinfo.dst(self).")}, + {"replace", (PyCFunction)timetz_replace, METH_KEYWORDS, + PyDoc_STR("Return timetz with new specified fields.")}, + {"__setstate__", (PyCFunction)timetz_setstate, METH_O, PyDoc_STR("__setstate__(state)")}, @@ -4314,6 +4412,11 @@ replace_tzinfo(PyObject *self, PyObject *newtzinfo) ((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo; } +static char *datetimetz_kws[] = { + "year", "month", "day", "hour", "minute", "second", + "microsecond", "tzinfo", NULL +}; + static PyObject * datetimetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) { @@ -4327,12 +4430,7 @@ datetimetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) int usecond = 0; PyObject *tzinfo = Py_None; - static char *keywords[] = { - "year", "month", "day", "hour", "minute", "second", - "microsecond", "tzinfo", NULL - }; - - if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO", keywords, + if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO", datetimetz_kws, &year, &month, &day, &hour, &minute, &second, &usecond, &tzinfo)) { if (check_date_args(year, month, day) < 0) @@ -4572,6 +4670,33 @@ datetimetz_isoformat(PyDateTime_DateTimeTZ *self, /* Note: tp_richcompare and tp_hash are inherited from datetime. */ static PyObject * +datetimetz_replace(PyDateTime_DateTimeTZ *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int y = GET_YEAR(self); + int m = GET_MONTH(self); + int d = GET_DAY(self); + int hh = DATE_GET_HOUR(self); + int mm = DATE_GET_MINUTE(self); + int ss = DATE_GET_SECOND(self); + int us = DATE_GET_MICROSECOND(self); + PyObject *tzinfo = self->tzinfo; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO:replace", + datetimetz_kws, + &y, &m, &d, &hh, &mm, &ss, &us, + &tzinfo)) + return NULL; + tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo); + if (tuple == NULL) + return NULL; + clone = datetimetz_new(self->ob_type, tuple, NULL); + Py_DECREF(tuple); + return clone; +} + +static PyObject * datetimetz_timetuple(PyDateTime_DateTimeTZ *self) { int dstflag = -1; @@ -4780,6 +4905,9 @@ static PyMethodDef datetimetz_methods[] = { {"dst", (PyCFunction)datetimetz_dst, METH_NOARGS, PyDoc_STR("Return self.tzinfo.dst(self).")}, + {"replace", (PyCFunction)datetimetz_replace, METH_KEYWORDS, + PyDoc_STR("Return datetimetz with new specified fields.")}, + {"__setstate__", (PyCFunction)datetimetz_setstate, METH_O, PyDoc_STR("__setstate__(state)")}, |