Utterly minimal changes to collapse datetimetz into datetime, and timetz

into time.  This is little more than *exporting* the datetimetz object
under the name "datetime", and similarly for timetz.  A good implementation
of this change requires more work, but this is fully functional if you
don't stare too hard at the internals (e.g., right now a type named
"datetime" shows up as a base class of the type named "datetime").  The
docs also need extensive revision, not part of this checkin.
This commit is contained in:
Tim Peters 2003-01-08 20:40:01 +00:00
parent 034d97605d
commit 0bf60bd67f
3 changed files with 107 additions and 86 deletions

View File

@ -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):

View File

@ -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 <http://www.python.org/sf/661086>. 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

View File

@ -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