diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index c3779607e57..614fed6d032 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -1,4 +1,7 @@ -"""Test date/time type.""" +"""Test date/time type. + +See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases +""" import sys import unittest @@ -8,8 +11,24 @@ from test import test_support from datetime import MINYEAR, MAXYEAR from datetime import timedelta from datetime import tzinfo -from datetime import time, timetz -from datetime import date, datetime, datetimetz +from datetime import time +from datetime import date, datetime + + +# XXX The test suite uncovered a bug in Python 2.2.2: if x and y are +# XXX instances of new-style classes (like date and time) that both +# XXX define __cmp__, and x is compared to y, and one of the __cmp__ +# XXX implementations raises an exception, the exception can get dropped +# XXX on the floor when it occurs, and pop up again at some "random" time +# XXX later (it depends on when the next opcode gets executed that +# XXX bothers to check). There isn't a workaround for this, so instead +# XXX we disable the parts of the tests that trigger it unless +# XXX CMP_BUG_FIXED is true. The bug is still there, we simply avoid +# XXX provoking it here. +# XXX Guido checked into a fix that will go into 2.2.3. The bug was +# XXX already fixed in 2.3 CVS via a different means. +CMP_BUG_FIXED = sys.version_info >= (2, 2, 3) + ############################################################################# # module tests @@ -478,27 +497,20 @@ class TestDate(unittest.TestCase): self.assertEqual(fromord.second, 0) self.assertEqual(fromord.microsecond, 0) - # Check first and last days of year across the whole range of years - # supported. - ordinal = 1 - for year in xrange(MINYEAR, MAXYEAR+1): + # Check first and last days of year spottily across the whole + # range of years supported. + for year in xrange(MINYEAR, MAXYEAR+1, 7): # Verify (year, 1, 1) -> ordinal -> y, m, d is identity. d = self.theclass(year, 1, 1) n = d.toordinal() - self.assertEqual(ordinal, n) d2 = self.theclass.fromordinal(n) self.assertEqual(d, d2) - self.assertEqual(d.timetuple().tm_yday, 1) - # Same for (year, 12, 31). - isleap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - days_in_year = 365 + isleap - d = self.theclass(year, 12, 31) - n = d.toordinal() - self.assertEqual(n, ordinal + days_in_year - 1) - self.assertEqual(d.timetuple().tm_yday, days_in_year) - d2 = self.theclass.fromordinal(n) - self.assertEqual(d, d2) - ordinal += days_in_year + # Verify that moving back a day gets to the end of year-1. + if year > 1: + d = self.theclass.fromordinal(n-1) + d2 = self.theclass(year-1, 12, 31) + self.assertEqual(d, d2) + self.assertEqual(d2.toordinal(), n-1) # Test every day in a leap-year and a non-leap year. dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] @@ -1136,7 +1148,7 @@ class TestDateTime(TestDate): args = 6, 7, 23, 20, 59, 1, 64**2 orig = self.theclass(*args) state = orig.__getstate__() - self.assertEqual(state, '\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00') + self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',)) derived = self.theclass(1, 1, 1) derived.__setstate__(state) self.assertEqual(orig, derived) @@ -1301,11 +1313,11 @@ class TestDateTime(TestDate): self.assertRaises(ValueError, base.replace, year=2001) def test_astimezone(self): - # Pretty boring for a datetime! datetimetz is more interesting here. + # Pretty boring! The TZ test is more interesting here. dt = self.theclass.now() f = FixedOffset(44, "") for dtz in dt.astimezone(f), dt.astimezone(tz=f): - self.failUnless(isinstance(dtz, datetimetz)) + self.failUnless(isinstance(dtz, datetime)) self.assertEqual(dt.date(), dtz.date()) self.assertEqual(dt.time(), dtz.time()) self.failUnless(dtz.tzinfo is f) @@ -1383,8 +1395,10 @@ class TestTime(unittest.TestCase): self.assertEqual(cmp(t1, t2), -1) self.assertEqual(cmp(t2, t1), 1) - for badarg in (10, 10L, 34.5, "abc", {}, [], (), date(1, 1, 1), - datetime(1, 1, 1, 1, 1), timedelta(9)): + badargs = (10, 10L, 34.5, "abc", {}, [], ()) + if CMP_BUG_FIXED: + badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9)) + for badarg in badargs: self.assertRaises(TypeError, lambda: t1 == badarg) self.assertRaises(TypeError, lambda: t1 != badarg) self.assertRaises(TypeError, lambda: t1 <= badarg) @@ -1513,7 +1527,7 @@ class TestTime(unittest.TestCase): args = 20, 59, 16, 64**2 orig = self.theclass(*args) state = orig.__getstate__() - self.assertEqual(state, '\x14\x3b\x10\x00\x10\x00') + self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',)) derived = self.theclass() derived.__setstate__(state) self.assertEqual(orig, derived) @@ -1559,12 +1573,12 @@ class TestTime(unittest.TestCase): # 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). +# must be legit (which is true for time and datetime). class TZInfoBase(unittest.TestCase): def test_argument_passing(self): cls = self.theclass - # A datetimetz passes itself on, a timetz passes None. + # A datetime passes itself on, a time passes None. class introspective(tzinfo): def tzname(self, dt): return dt and "real" or "none" def utcoffset(self, dt): @@ -1573,10 +1587,10 @@ class TZInfoBase(unittest.TestCase): obj = cls(1, 2, 3, tzinfo=introspective()) - expected = cls is timetz and "none" or "real" + expected = cls is time and "none" or "real" self.assertEqual(obj.tzname(), expected) - expected = timedelta(minutes=(cls is timetz and -42 or 42)) + expected = timedelta(minutes=(cls is time and -42 or 42)) self.assertEqual(obj.utcoffset(), expected) self.assertEqual(obj.dst(), expected) @@ -1608,15 +1622,17 @@ class TZInfoBase(unittest.TestCase): (-1439, True), (1439, True), (1440, False)): - if cls is timetz: + if cls is time: t = cls(1, 2, 3, tzinfo=Edgy(offset)) - elif cls is datetimetz: + elif cls is datetime: t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset)) + else: + assert 0, "impossible" if legit: aofs = abs(offset) h, m = divmod(aofs, 60) tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m) - if isinstance(t, datetimetz): + if isinstance(t, datetime): t = t.timetz() self.assertEqual(str(t), "01:02:03" + tag) else: @@ -1695,10 +1711,10 @@ class TZInfoBase(unittest.TestCase): self.assertEqual(got, expected) # However, if they're different members, uctoffset is not ignored. - # Note that a timetz can't actually have an operand-depedent offset, - # though (and timetz.utcoffset() passes None to tzinfo.utcoffset()), - # so skip this test for timetz. - if cls is not timetz: + # Note that a time can't actually have an operand-depedent offset, + # though (and time.utcoffset() passes None to tzinfo.utcoffset()), + # so skip this test for time. + if cls is not time: d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) @@ -1717,8 +1733,9 @@ class TZInfoBase(unittest.TestCase): self.assertEqual(got, expected) +# Testing time objects with a non-None tzinfo. class TestTimeTZ(TestTime, TZInfoBase): - theclass = timetz + theclass = time def test_empty(self): t = self.theclass() @@ -1732,11 +1749,11 @@ class TestTimeTZ(TestTime, TZInfoBase): est = FixedOffset(-300, "EST", 1) utc = FixedOffset(0, "UTC", -2) met = FixedOffset(60, "MET", 3) - t1 = timetz( 7, 47, tzinfo=est) - t2 = timetz(12, 47, tzinfo=utc) - t3 = timetz(13, 47, tzinfo=met) - t4 = timetz(microsecond=40) - t5 = timetz(microsecond=40, tzinfo=utc) + t1 = time( 7, 47, tzinfo=est) + t2 = time(12, 47, tzinfo=utc) + t3 = time(13, 47, tzinfo=met) + t4 = time(microsecond=40) + t5 = time(microsecond=40, tzinfo=utc) self.assertEqual(t1.tzinfo, est) self.assertEqual(t2.tzinfo, utc) @@ -1785,7 +1802,7 @@ class TestTimeTZ(TestTime, TZInfoBase): self.assertEqual(t4.isoformat(), "00:00:00.000040") self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00") - d = 'datetime.timetz' + d = 'datetime.time' self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)") self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)") self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)") @@ -1798,14 +1815,14 @@ class TestTimeTZ(TestTime, TZInfoBase): self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100") yuck = FixedOffset(-1439, "%z %Z %%z%%Z") - t1 = timetz(23, 59, tzinfo=yuck) + t1 = time(23, 59, tzinfo=yuck) self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"), "23:59 %Z='%z %Z %%z%%Z' %z='-2359'") # Check that an invalid tzname result raises an exception. class Badtzname(tzinfo): def tzname(self, dt): return 42 - t = timetz(2, 3, 4, tzinfo=Badtzname()) + t = time(2, 3, 4, tzinfo=Badtzname()) self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04") self.assertRaises(TypeError, t.strftime, "%Z") @@ -1926,16 +1943,17 @@ class TestTimeTZ(TestTime, TZInfoBase): def test_mixed_compare(self): t1 = time(1, 2, 3) - t2 = timetz(1, 2, 3) + t2 = time(1, 2, 3) self.assertEqual(t1, t2) t2 = t2.replace(tzinfo=None) self.assertEqual(t1, t2) t2 = t2.replace(tzinfo=FixedOffset(None, "")) self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertRaises(TypeError, lambda: t1 == t2) + if CMP_BUG_FIXED: + t2 = t2.replace(tzinfo=FixedOffset(0, "")) + self.assertRaises(TypeError, lambda: t1 == t2) - # In timetz w/ identical tzinfo objects, utcoffset is ignored. + # In time w/ identical tzinfo objects, utcoffset is ignored. class Varies(tzinfo): def __init__(self): self.offset = timedelta(minutes=22) @@ -1955,8 +1973,10 @@ class TestTimeTZ(TestTime, TZInfoBase): self.failUnless(t1 < t2) # t1's offset counter still going up +# Testing datetime objects with a non-None tzinfo. + class TestDateTimeTZ(TestDateTime, TZInfoBase): - theclass = datetimetz + theclass = datetime def test_trivial(self): dt = self.theclass(1, 2, 3, 4, 5, 6, 7) @@ -2091,9 +2111,9 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): est = FixedOffset(-300, "EST") utc = FixedOffset(0, "UTC") met = FixedOffset(60, "MET") - t1 = datetimetz(2002, 3, 19, 7, 47, tzinfo=est) - t2 = datetimetz(2002, 3, 19, 12, 47, tzinfo=utc) - t3 = datetimetz(2002, 3, 19, 13, 47, tzinfo=met) + t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est) + t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc) + t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met) self.assertEqual(t1.tzinfo, est) self.assertEqual(t2.tzinfo, utc) self.assertEqual(t3.tzinfo, met) @@ -2112,7 +2132,7 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00") self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00") self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00") - d = 'datetime.datetimetz(2002, 3, 19, ' + d = 'datetime.datetime(2002, 3, 19, ' self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)") self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)") self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)") @@ -2120,9 +2140,9 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): def test_combine(self): met = FixedOffset(60, "MET") d = date(2002, 3, 4) - tz = timetz(18, 45, 3, 1234, tzinfo=met) - dt = datetimetz.combine(d, tz) - self.assertEqual(dt, datetimetz(2002, 3, 4, 18, 45, 3, 1234, + tz = time(18, 45, 3, 1234, tzinfo=met) + dt = datetime.combine(d, tz) + self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)) def test_extract(self): @@ -2130,14 +2150,14 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met) self.assertEqual(dt.date(), date(2002, 3, 4)) self.assertEqual(dt.time(), time(18, 45, 3, 1234)) - self.assertEqual(dt.timetz(), timetz(18, 45, 3, 1234, tzinfo=met)) + self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met)) def test_tz_aware_arithmetic(self): import random now = self.theclass.now() tz55 = FixedOffset(-330, "west 5:30") - timeaware = now.timetz().replace(tzinfo=tz55) + timeaware = now.time().replace(tzinfo=tz55) nowaware = self.theclass.combine(now.date(), timeaware) self.failUnless(nowaware.tzinfo is tz55) self.assertEqual(nowaware.timetz(), timeaware) @@ -2146,7 +2166,7 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): self.assertRaises(TypeError, lambda: now - nowaware) self.assertRaises(TypeError, lambda: nowaware - now) - # And adding datetimetz's doesn't make sense, aware or not. + # And adding datetime's doesn't make sense, aware or not. self.assertRaises(TypeError, lambda: now + nowaware) self.assertRaises(TypeError, lambda: nowaware + now) self.assertRaises(TypeError, lambda: nowaware + nowaware) @@ -2257,7 +2277,7 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): self.assertRaises(TypeError, meth, ts, tzinfo=off42) def test_tzinfo_timetuple(self): - # TestDateTime tested most of this. datetimetz adds a twist to the + # TestDateTime tested most of this. datetime adds a twist to the # DST flag. class DST(tzinfo): def __init__(self, dstvalue): @@ -2498,16 +2518,17 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): def test_mixed_compare(self): t1 = datetime(1, 2, 3, 4, 5, 6, 7) - t2 = datetimetz(1, 2, 3, 4, 5, 6, 7) + t2 = datetime(1, 2, 3, 4, 5, 6, 7) self.assertEqual(t1, t2) t2 = t2.replace(tzinfo=None) self.assertEqual(t1, t2) t2 = t2.replace(tzinfo=FixedOffset(None, "")) self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertRaises(TypeError, lambda: t1 == t2) + if CMP_BUG_FIXED: + t2 = t2.replace(tzinfo=FixedOffset(0, "")) + self.assertRaises(TypeError, lambda: t1 == t2) - # In datetimetz w/ identical tzinfo objects, utcoffset is ignored. + # In datetime w/ identical tzinfo objects, utcoffset is ignored. class Varies(tzinfo): def __init__(self): self.offset = timedelta(minutes=22) @@ -2596,10 +2617,10 @@ utc_fake = FixedOffset(-12*60, "UTCfake", 0) class TestTimezoneConversions(unittest.TestCase): # The DST switch times for 2002, in local time. - dston = datetimetz(2002, 4, 7, 2) - dstoff = datetimetz(2002, 10, 27, 2) + dston = datetime(2002, 4, 7, 2) + dstoff = datetime(2002, 10, 27, 2) - theclass = datetimetz + theclass = datetime # Check a time that's inside DST. def checkinside(self, dt, tz, utc, dston, dstoff): diff --git a/Misc/NEWS b/Misc/NEWS index b55f8da2552..d2d4f8a99bc 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,13 @@ Extension modules - datetime changes: + The datetime and datetimetz classes have been collapsed into a single + datetime class, and likewise the time and timetz classes into a single + time class. Previously, a datetimetz object with tzinfo=None acted + exactly like a datetime object, and similarly for timetz. This wasn't + enough of a difference to justify distinct classes, and life is simpler + now. + today() and now() now round system timestamps to the closest microsecond . This repairs an irritation most likely seen on Windows systems. @@ -1202,7 +1209,7 @@ Mac - MacPython no longer maps both \r and \n to \n on input for any text file. This feature has been replaced by universal newline support (PEP278). - + - The default encoding for Python sourcefiles in MacPython-OS9 is no longer mac-roman (or whatever your local Mac encoding was but "ascii", like on other platforms. If you really need sourcefiles with Mac characters in them diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index b01f139efe7..ea8302fa9ce 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -3345,10 +3345,11 @@ datetime_getdate(PyDateTime_DateTime *self) static PyObject * datetime_gettime(PyDateTime_DateTime *self) { - return new_time(DATE_GET_HOUR(self), - DATE_GET_MINUTE(self), - DATE_GET_SECOND(self), - DATE_GET_MICROSECOND(self)); + return new_timetz(DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self), + Py_None); } /* Pickle support. Quite a maze! */ @@ -3457,8 +3458,7 @@ static PyMethodDef datetime_methods[] = { PyDoc_STR("Return date object with same year, month and day.")}, {"time", (PyCFunction)datetime_gettime, METH_NOARGS, - PyDoc_STR("Return time object with same hour, minute, second and " - "microsecond.")}, + PyDoc_STR("Return time object with same time but with tzinfo=None.")}, {"ctime", (PyCFunction)datetime_ctime, METH_NOARGS, PyDoc_STR("Return ctime() style string.")}, @@ -4403,7 +4403,7 @@ static PyNumberMethods timetz_as_number = { statichere PyTypeObject PyDateTime_TimeTZType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ - "datetime.timetz", /* tp_name */ + "datetime.time", /* tp_name */ sizeof(PyDateTime_TimeTZ), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)timetz_dealloc, /* tp_dealloc */ @@ -5119,7 +5119,7 @@ static PyNumberMethods datetimetz_as_number = { statichere PyTypeObject PyDateTime_DateTimeTZType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ - "datetime.datetimetz", /* tp_name */ + "datetime.datetime", /* tp_name */ sizeof(PyDateTime_DateTimeTZ), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)datetimetz_dealloc, /* tp_dealloc */ @@ -5424,24 +5424,17 @@ initdatetime(void) Py_INCREF(&PyDateTime_DateType); PyModule_AddObject(m, "date", (PyObject *) &PyDateTime_DateType); - Py_INCREF(&PyDateTime_DateTimeType); - PyModule_AddObject(m, "datetime", - (PyObject *) &PyDateTime_DateTimeType); - Py_INCREF(&PyDateTime_DeltaType); PyModule_AddObject(m, "timedelta", (PyObject *) &PyDateTime_DeltaType); - Py_INCREF(&PyDateTime_TimeType); - PyModule_AddObject(m, "time", (PyObject *) &PyDateTime_TimeType); - Py_INCREF(&PyDateTime_TZInfoType); PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); Py_INCREF(&PyDateTime_TimeTZType); - PyModule_AddObject(m, "timetz", (PyObject *) &PyDateTime_TimeTZType); + PyModule_AddObject(m, "time", (PyObject *) &PyDateTime_TimeTZType); Py_INCREF(&PyDateTime_DateTimeTZType); - PyModule_AddObject(m, "datetimetz", + PyModule_AddObject(m, "datetime", (PyObject *)&PyDateTime_DateTimeTZType); /* A 4-year cycle has an extra leap day over what we'd get from