[Python-checkins] python/nondist/sandbox/datetime datetime.py,1.64,1.65 test_datetime.py,1.45,1.46
tim_one@users.sourceforge.net
tim_one@users.sourceforge.net
Mon, 25 Nov 2002 13:09:11 -0800
Update of /cvsroot/python/python/nondist/sandbox/datetime
In directory sc8-pr-cvs1:/tmp/cvs-serv2016
Modified Files:
datetime.py test_datetime.py
Log Message:
SF patch 641958: time and timetz for the datetime module,
from Marius Gedminas. This is subject to change, as the design is still
being discussed on the wiki:
http://www.zope.org/Members/fdrake/DateTimeWiki/TimeType
Index: datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.py,v
retrieving revision 1.64
retrieving revision 1.65
diff -C2 -d -r1.64 -r1.65
*** datetime.py 22 Nov 2002 03:34:42 -0000 1.64
--- datetime.py 25 Nov 2002 21:09:05 -0000 1.65
***************
*** 694,697 ****
--- 694,1000 ----
+ class time(object):
+ """Concrete time type.
+
+ Constructors:
+
+ __init__()
+
+ Operators:
+
+ __repr__, __str__
+ __cmp__, __hash__
+
+ Methods:
+
+ strftime()
+ isoformat()
+
+ Properties (readonly):
+ hour, minute, second, microsecond
+ """
+
+ def __init__(self, hour, minute, second=0, microsecond=0):
+ """Constructor.
+
+ Arguments:
+
+ hour, minute (required)
+ second, microsecond (default to zero)
+ """
+ if not 0 <= hour <= 23:
+ raise ValueError('hour must be in 0..23', hour)
+ if not 0 <= minute <= 59:
+ raise ValueError('minute must be in 0..59', minute)
+ if not 0 <= second <= 59:
+ raise ValueError('second must be in 0..59', second)
+ if not 0 <= microsecond <= 999999:
+ raise ValueError('microsecond must be in 0..999999', microsecond)
+ self.__hour = hour
+ self.__minute = minute
+ self.__second = second
+ self.__microsecond = microsecond
+
+ # Read-only field accessors
+ hour = property(lambda self: self.__hour, doc="hour (0-23)")
+ minute = property(lambda self: self.__minute, doc="minute (0-59)")
+ second = property(lambda self: self.__second, doc="second (0-59)")
+ microsecond = property(lambda self: self.__microsecond,
+ doc="microsecond (0-999999)")
+
+ # Standard conversions, __cmp__, __hash__ (and helpers)
+
+ def __cmp__(self, other):
+ """Three-way comparison."""
+ if isinstance(other, time):
+ return cmp((self.__hour, self.__minute, self.__second,
+ self.__microsecond),
+ (other.__hour, other.__minute, other.__second,
+ other.__microsecond))
+ raise TypeError, ("can't compare time to %s instance" %
+ type(other).__name__)
+
+ def __hash__(self):
+ """Hash."""
+ return hash((self.__hour, self.__minute, self.__second,
+ self.__microsecond))
+
+ # Conversions to string
+
+ def __repr__(self):
+ """Convert to formal string, for repr()."""
+ if self.__microsecond != 0:
+ s = ", %d, %d" % (self.__second, self.__microsecond)
+ elif self.__second != 0:
+ s = ", %d" % self.__second
+ else:
+ s = ""
+ return "%s(%d, %d%s)" % (self.__class__.__name__,
+ self.__hour, self.__minute, s)
+
+ def __str__(self):
+ """Convert to pretty string, for str()."""
+ pretty = "%d:%02d:%02d.%06d" % (
+ self.__hour, self.__minute, self.__second,
+ self.__microsecond)
+ # trim microseconds: hh:mm:ss.xxx000 -> hh:mm:ss.xxx
+ while pretty.endswith('0'):
+ pretty = pretty[:-1]
+ # trim microseconds: hh:mm:ss.000000 -> hh:mm:ss
+ if pretty.endswith('.'):
+ pretty = pretty[:-1]
+ # trim seconds: hh:mm:00 -> hh:mm
+ if pretty.endswith(':00'):
+ pretty = pretty[:-3]
+ return pretty
+
+ def isoformat(self):
+ """Return the time formatted according to ISO.
+
+ This is 'HH:MM:SS.mmmmmm'.
+ """
+ return "%02d:%02d:%02d.%06d" % (
+ self.__hour, self.__minute, self.__second,
+ self.__microsecond)
+
+ def strftime(self, fmt):
+ """Format using strftime(). The date part of the timestamp passed
+ to underlying strftime should not be used.
+ """
+ return _time.strftime(fmt, (0, 0, 0, self.__hour, self.__minute,
+ self.__second, 0, 0, -1))
+
+
+ time.min = time(0, 0, 0)
+ time.max = time(23, 59, 59, 999999)
+ time.resolution = timedelta(microseconds=1)
+
+
+ class timetz(time):
+ """Time with time zone.
+
+ Constructors:
+
+ __init__()
+
+ Operators:
+
+ __repr__, __str__
+ __cmp__, __hash__
+
+ Methods:
+
+ strftime()
+ isoformat()
+ utcoffset()
+ tzname()
+ dst()
+
+ Properties (readonly):
+ hour, minute, second, microsecond, tzinfo
+ """
+
+ def __init__(self, hour, minute, second=0, microsecond=0, tzinfo=None):
+ """Constructor.
+
+ Arguments:
+
+ hour, minute (required)
+ second, microsecond (default to zero)
+ tzinfo (default to None)
+ """
+ super(timetz, self).__init__(hour, minute, second, microsecond)
+ if tzinfo is not None:
+ # Better fail now than later
+ assert hasattr(tzinfo, 'utcoffset')
+ assert hasattr(tzinfo, 'dst')
+ assert hasattr(tzinfo, 'tzname')
+ self.__tzinfo = tzinfo
+
+ # Read-only field accessors
+ tzinfo = property(lambda self: self.__tzinfo, doc="timezone info object")
+
+ # Standard conversions, __cmp__, __hash__ (and helpers)
+
+ def __cmp__(self, other):
+ """Three-way comparison."""
+ if not isinstance(other, time):
+ raise TypeError("can't compare timetz to %s instance" %
+ type(other).__name__)
+ superself = super(timetz, self)
+ supercmp = superself.__cmp__
+ mytz = self.__tzinfo
+ ottz = None
+ if isinstance(other, timetz):
+ ottz = other.__tzinfo
+ if mytz is ottz:
+ return supercmp(other)
+ myoff = otoff = None
+ if mytz is not None:
+ myoff = mytz.utcoffset(self)
+ if ottz is not None:
+ otoff = ottz.utcoffset(other)
+ if myoff == otoff:
+ return supercmp(other)
+ if myoff is None or otoff is None:
+ raise ValueError, "cannot mix naive and timezone-aware time"
+ myhhmm = self.hour * 60 + self.minute - myoff
+ othhmm = other.hour * 60 + other.minute - otoff
+ return cmp((myhhmm, self.second, self.microsecond),
+ (othhmm, other.second, other.microsecond))
+
+ def __hash__(self):
+ """Hash."""
+ tz = self.__tzinfo
+ if tz == None:
+ return super(timetz, self).__hash__()
+ tzoff = tz.utcoffset(self)
+ if not tzoff: # zero or None!
+ return super(timetz, self).__hash__()
+ h, m = divmod(self.hour * 60 + self.minute - tzoff, 60)
+ # Unfortunately it is not possible to construct a new timetz object
+ # and use super().__hash__(), since hour may exceed the range of
+ # allowed values
+ return hash((h, m, self.second, self.microsecond))
+
+ # Conversion to string
+
+ def _tzstr(self, sep=":"):
+ """Return formatted timezone offset (+xx:xx) or None."""
+ if self.__tzinfo is not None:
+ off = self.__tzinfo.utcoffset(self)
+ if off is not None:
+ if off < 0:
+ sign = "-"
+ off = -off
+ else:
+ sign = "+"
+ hh, mm = divmod(off, 60)
+ return "%s%02d%s%02d" % (sign, hh, sep, mm)
+
+ def __repr__(self):
+ """Convert to formal string, for repr()."""
+ s = super(timetz, self).__repr__()
+ if self.__tzinfo is not None:
+ assert s[-1:] == ")"
+ s = s[:-1] + ", tzinfo=%r" % self.__tzinfo + ")"
+ return s
+
+ def __str__(self):
+ """Convert to pretty string, for str()."""
+ s = super(timetz, self).__str__()
+ tz = self._tzstr()
+ if tz: s = "%s %s" % (s, tz)
+ return s
+
+ def isoformat(self):
+ """Return the time formatted according to ISO.
+
+ This is 'HH:MM:SS.mmmmmm+zz:zz'.
+ """
+ s = super(timetz, self).isoformat()
+ tz = self._tzstr()
+ if tz: s += tz
+ return s
+
+ def strftime(self, fmt):
+ """Format using strftime(). The date part of the timestamp passed
+ to underlying strftime should not be used.
+
+ You can use %Z to refer to the timezone name and %z to refer to its
+ UTC offset (+zzzz).
+ """
+ tz = self._tzstr(sep="")
+ if tz:
+ fmt = fmt.replace("%z", tz).replace("%Z", self.tzinfo.tzname(None))
+ else:
+ fmt = fmt.replace("%z", "").replace("%Z", "")
+ return super(timetz, self).strftime(fmt)
+
+ # Timezone functions
+
+ def utcoffset(self):
+ """Return the timezone offset in minutes east of UTC (negative west of
+ UTC)."""
+ tz = self.__tzinfo
+ if tz is None:
+ return None
+ else:
+ return tz.utcoffset(self)
+
+ def tzname(self):
+ """Return the timezone name.
+
+ Note that the name is 100% informational -- there's no requirement that
+ it mean anything in particular. For example, "GMT", "UTC", "-500",
+ "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
+ """
+ tz = self.__tzinfo
+ if tz is None:
+ return None
+ else:
+ return tz.tzname(self)
+
+ def dst(self):
+ """Return 0 if DST is not in effect, or the DST offset (in minutes
+ eastward) if DST is in effect.
+
+ This is purely informational; the DST offset has already been added to
+ the UTC offset returned by utcoffset() if applicable, so there's no
+ need to consult dst() unless you're interested in displaying the DST
+ info.
+ """
+ tz = self.__tzinfo
+ if tz is None:
+ return None
+ else:
+ return tz.dst(self)
+
+
+ timetz.min = timetz(0, 0, 0)
+ timetz.max = timetz(23, 59, 59, 999999)
+ timetz.resolution = timedelta(microseconds=1)
+
+
class datetime(date):
"""Concrete date/time type, inheriting from date.
***************
*** 778,781 ****
--- 1081,1090 ----
utcnow = classmethod(utcnow)
+ def combine(cls, date, time):
+ "Construct a datetime from a given date and a given time."
+ return cls(date.year, date.month, date.day,
+ time.hour, time.minute, time.second, time.microsecond)
+ combine = classmethod(combine)
+
# Conversions to string
***************
*** 808,811 ****
--- 1117,1129 ----
self.weekday(), self._yday(), -1)
+ def date(self):
+ "Return the date part."
+ return date(self.__year, self.__month, self.__day)
+
+ def time(self):
+ "Return the time part."
+ return time(self.__hour, self.__minute, self.__second,
+ self.__microsecond)
+
def __cmp__(self, other):
"Three-way comparison."
***************
*** 942,945 ****
--- 1260,1271 ----
now = classmethod(now)
+ def combine(cls, date, time):
+ "Construct a datetime from a given date and a given time."
+ return cls(date.year, date.month, date.day,
+ time.hour, time.minute, time.second, time.microsecond,
+ getattr(time, 'tzinfo', None))
+ combine = classmethod(combine)
+
+
def utctimetuple(self):
"Return UTC time tuple compatible with time.gmtime()."
***************
*** 952,955 ****
--- 1278,1286 ----
dt = timedelta(minutes=offset)
return (ts - dt).timetuple()
+
+ def timetz(self):
+ "Return the time part."
+ return timetz(self.hour, self.minute, self.second, self.microsecond,
+ self.__tzinfo)
def isoformat(self, sep=' '):
Index: test_datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/test_datetime.py,v
retrieving revision 1.45
retrieving revision 1.46
diff -C2 -d -r1.45 -r1.46
*** test_datetime.py 20 Nov 2002 03:29:43 -0000 1.45
--- test_datetime.py 25 Nov 2002 21:09:06 -0000 1.46
***************
*** 7,11 ****
import unittest
! from datetime import date, datetime, datetimetz, timedelta, MINYEAR, MAXYEAR
--- 7,12 ----
import unittest
! from datetime import date, time, timetz, datetime, datetimetz, timedelta, \
! MINYEAR, MAXYEAR
***************
*** 327,330 ****
--- 328,490 ----
self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
+
+ class TestTime(unittest.TestCase):
+
+ theclass = time
+
+ def test_basic_attributes(self):
+ t = self.theclass(12, 0)
+ self.assertEqual(t.hour, 12)
+ self.assertEqual(t.minute, 0)
+ self.assertEqual(t.second, 0)
+ self.assertEqual(t.microsecond, 0)
+
+ def test_basic_attributes_nonzero(self):
+ # Make sure all attributes are non-zero so bugs in
+ # bit-shifting access show up.
+ t = self.theclass(12, 59, 59, 8000)
+ self.assertEqual(t.hour, 12)
+ self.assertEqual(t.minute, 59)
+ self.assertEqual(t.second, 59)
+ self.assertEqual(t.microsecond, 8000)
+
+ def test_roundtrip(self):
+ for t in (self.theclass(1, 2, 3, 4),):
+ # Verify t -> string -> time identity.
+ s = repr(t)
+ t2 = eval(s)
+ self.assertEqual(t, t2)
+
+ # Verify identity via reconstructing from pieces.
+ t2 = self.theclass(t.hour, t.minute, t.second,
+ t.microsecond)
+ self.assertEqual(t, t2)
+
+ def test_comparing(self):
+ t1 = self.theclass(9, 0, 0)
+ t2 = self.theclass(10, 0, 0)
+ t3 = self.theclass(9, 0, 0)
+ self.assertEqual(t1, t3)
+ self.assert_(t2 > t3)
+
+ def test_bad_constructor_arguments(self):
+ # bad hours
+ self.theclass(0, 0) # no exception
+ self.theclass(23, 0) # no exception
+ self.assertRaises(ValueError, self.theclass, -1, 0)
+ self.assertRaises(ValueError, self.theclass, 24, 0)
+ # bad minutes
+ self.theclass(23, 0) # no exception
+ self.theclass(23, 59) # no exception
+ self.assertRaises(ValueError, self.theclass, 23, -1)
+ self.assertRaises(ValueError, self.theclass, 23, 60)
+ # bad seconds
+ self.theclass(23, 59, 0) # no exception
+ self.theclass(23, 59, 59) # no exception
+ self.assertRaises(ValueError, self.theclass, 23, 59, -1)
+ self.assertRaises(ValueError, self.theclass, 23, 59, 60)
+ # bad microseconds
+ self.theclass(23, 59, 59, 0) # no exception
+ self.theclass(23, 59, 59, 999999) # no exception
+ self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
+ self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
+
+ def test_hash_equality(self):
+ d = self.theclass(23, 30, 17)
+ e = self.theclass(23, 30, 17)
+ self.assertEqual(d, e)
+ self.assertEqual(hash(d), hash(e))
+
+ dic = {d: 1}
+ dic[e] = 2
+ self.assertEqual(len(dic), 1)
+ self.assertEqual(dic[d], 2)
+ self.assertEqual(dic[e], 2)
+
+ d = self.theclass(0, 5, 17)
+ e = self.theclass(0, 5, 17)
+ self.assertEqual(d, e)
+ self.assertEqual(hash(d), hash(e))
+
+ dic = {d: 1}
+ dic[e] = 2
+ self.assertEqual(len(dic), 1)
+ self.assertEqual(dic[d], 2)
+ self.assertEqual(dic[e], 2)
+
+ def test_isoformat(self):
+ t = self.theclass(4, 5, 1, 123)
+ self.assertEqual(t.isoformat(), "04:05:01.000123")
+
+ def test_strftime(self):
+ t = self.theclass(1, 2, 3, 4)
+ self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
+
+ def test_str(self):
+ self.assertEqual(str(self.theclass(1, 2, 3, 4)), "1:02:03.000004")
+ self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004")
+ self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "0:02:03.4")
+ self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
+ self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15")
+
+ def test_repr(self):
+ self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
+ "%s(1, 2, 3, 4)" % self.theclass.__name__)
+ self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
+ "%s(10, 2, 3, 4000)" % self.theclass.__name__)
+ self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
+ "%s(0, 2, 3, 400000)" % self.theclass.__name__)
+ self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
+ "%s(12, 2, 3)" % self.theclass.__name__)
+ self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
+ "%s(23, 15)" % self.theclass.__name__)
+
+ def test_resolution_info(self):
+ self.assert_(isinstance(self.theclass.min, self.theclass))
+ self.assert_(isinstance(self.theclass.max, self.theclass))
+ self.assert_(isinstance(self.theclass.resolution, timedelta))
+ self.assert_(self.theclass.max > self.theclass.min)
+
+
+ class TestTimeTZ(TestTime):
+
+ theclass = timetz
+
+ def test_zones(self):
+ est = FixedOffset(-300, "EST")
+ utc = FixedOffset(0, "UTC")
+ met = FixedOffset(60, "MET")
+ t1 = timetz( 7, 47, tzinfo=est)
+ t2 = timetz(12, 47, tzinfo=utc)
+ t3 = timetz(13, 47, tzinfo=met)
+ self.assertEqual(t1.tzinfo, est)
+ self.assertEqual(t2.tzinfo, utc)
+ self.assertEqual(t3.tzinfo, met)
+ self.assertEqual(t1.utcoffset(), -300)
+ self.assertEqual(t2.utcoffset(), 0)
+ self.assertEqual(t3.utcoffset(), 60)
+ self.assertEqual(t1.tzname(), "EST")
+ self.assertEqual(t2.tzname(), "UTC")
+ self.assertEqual(t3.tzname(), "MET")
+ self.assertEqual(hash(t1), hash(t2))
+ self.assertEqual(hash(t1), hash(t3))
+ self.assertEqual(hash(t2), hash(t3))
+ self.assertEqual(t1, t2)
+ self.assertEqual(t1, t3)
+ self.assertEqual(t2, t3)
+ self.assertEqual(str(t1), "7:47 -05:00")
+ self.assertEqual(str(t2), "12:47 +00:00")
+ self.assertEqual(str(t3), "13:47 +01:00")
+ self.assertEqual(repr(t1), "timetz(7, 47, tzinfo=est)")
+ self.assertEqual(repr(t2), "timetz(12, 47, tzinfo=utc)")
+ self.assertEqual(repr(t3), "timetz(13, 47, tzinfo=met)")
+ self.assertEqual(t1.isoformat(), "07:47:00.000000-05:00")
+ self.assertEqual(t2.isoformat(), "12:47:00.000000+00:00")
+ self.assertEqual(t3.isoformat(), "13:47:00.000000+01:00")
+ self.assertEqual(t1.strftime("%H:%M:%S %Z %z"), "07:47:00 EST -0500")
+ self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
+ self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
+
+
class TestDateTime(TestDate):
***************
*** 531,534 ****
--- 691,705 ----
self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
+ def test_combine(self):
+ d = date(2002, 3, 4)
+ t = time(18, 45, 3, 1234)
+ dt = datetime.combine(d, t)
+ self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234))
+
+ def test_extract(self):
+ dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
+ self.assertEqual(dt.date(), date(2002, 3, 4))
+ self.assertEqual(dt.time(), time(18, 45, 3, 1234))
+
class FixedOffset(object):
***************
*** 582,592 ****
"datetimetz(2002, 3, 19, 13, 47, tzinfo=met)")
def test_suite():
s1 = unittest.makeSuite(TestTimeDelta, 'test')
s2 = unittest.makeSuite(TestDate, 'test')
! s3 = unittest.makeSuite(TestDateTime, 'test')
! s4 = unittest.makeSuite(TestDateTimeTZ, 'test')
! return unittest.TestSuite([s1, s2, s3, s4])
def test_main():
--- 753,780 ----
"datetimetz(2002, 3, 19, 13, 47, tzinfo=met)")
+ 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,
+ tzinfo=met))
+
+ def test_extract(self):
+ met = FixedOffset(60, "MET")
+ 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))
+
def test_suite():
s1 = unittest.makeSuite(TestTimeDelta, 'test')
s2 = unittest.makeSuite(TestDate, 'test')
! s3 = unittest.makeSuite(TestTime, 'test')
! s4 = unittest.makeSuite(TestTimeTZ, 'test')
! s5 = unittest.makeSuite(TestDateTime, 'test')
! s6 = unittest.makeSuite(TestDateTimeTZ, 'test')
! return unittest.TestSuite([s1, s2, s3, s4, s5, s6])
def test_main():