diff --git a/Include/datetime.h b/Include/datetime.h new file mode 100644 index 00000000000..7bfbf0b3f3c --- /dev/null +++ b/Include/datetime.h @@ -0,0 +1,125 @@ +/* datetime.h + */ + +#ifndef DATETIME_H +#define DATETIME_H + +/* Fields are packed into successive bytes, each viewed as unsigned and + * big-endian, unless otherwise noted: + * + * byte offset + * 0 year 2 bytes, 1-9999 + * 2 month 1 byte, 1-12 + * 3 day 1 byte, 1-31 + * 4 hour 1 byte, 0-23 + * 5 minute 1 byte, 0-59 + * 6 second 1 byte, 0-59 + * 7 usecond 3 bytes, 0-999999 + * 10 + */ + +/* # of bytes for year, month, and day. */ +#define _PyDateTime_DATE_DATASIZE 4 + +/* # of bytes for hour, minute, second, and usecond. */ +#define _PyDateTime_TIME_DATASIZE 6 + +/* # of bytes for year, month, day, hour, minute, second, and usecond. */ +#define _PyDateTime_DATETIME_DATASIZE 10 + +typedef struct +{ + PyObject_HEAD + long hashcode; + unsigned char data[_PyDateTime_DATE_DATASIZE]; +} PyDateTime_Date; + +typedef struct +{ + PyObject_HEAD + long hashcode; + unsigned char data[_PyDateTime_DATETIME_DATASIZE]; +} PyDateTime_DateTime; + +typedef struct +{ + PyObject_HEAD + long hashcode; + unsigned char data[_PyDateTime_DATETIME_DATASIZE]; + PyObject *tzinfo; +} PyDateTime_DateTimeTZ; + +typedef struct +{ + PyObject_HEAD + long hashcode; + unsigned char data[_PyDateTime_TIME_DATASIZE]; +} PyDateTime_Time; + +typedef struct +{ + PyObject_HEAD + long hashcode; + unsigned char data[_PyDateTime_TIME_DATASIZE]; + PyObject *tzinfo; +} PyDateTime_TimeTZ; + +typedef struct +{ + PyObject_HEAD + long hashcode; /* -1 when unknown */ + int days; /* -MAX_DELTA_DAYS <= days <= MAX_DELTA_DAYS */ + int seconds; /* 0 <= seconds < 24*3600 is invariant */ + int microseconds; /* 0 <= microseconds < 1000000 is invariant */ +} PyDateTime_Delta; + +typedef struct +{ + PyObject_HEAD /* a pure abstract base clase */ +} PyDateTime_TZInfo; + +/* Apply for date, datetime, and datetimetz instances. */ +#define PyDateTime_GET_YEAR(o) ((((PyDateTime_Date*)o)->data[0] << 8) | \ + ((PyDateTime_Date*)o)->data[1]) +#define PyDateTime_GET_MONTH(o) (((PyDateTime_Date*)o)->data[2]) +#define PyDateTime_GET_DAY(o) (((PyDateTime_Date*)o)->data[3]) + +#define PyDateTime_DATE_GET_HOUR(o) (((PyDateTime_DateTime*)o)->data[4]) +#define PyDateTime_DATE_GET_MINUTE(o) (((PyDateTime_DateTime*)o)->data[5]) +#define PyDateTime_DATE_GET_SECOND(o) (((PyDateTime_DateTime*)o)->data[6]) +#define PyDateTime_DATE_GET_MICROSECOND(o) \ + ((((PyDateTime_DateTime*)o)->data[7] << 16) | \ + (((PyDateTime_DateTime*)o)->data[8] << 8) | \ + ((PyDateTime_DateTime*)o)->data[9]) + +/* Apply for time and timetz instances. */ +#define PyDateTime_TIME_GET_HOUR(o) (((PyDateTime_Time*)o)->data[0]) +#define PyDateTime_TIME_GET_MINUTE(o) (((PyDateTime_Time*)o)->data[1]) +#define PyDateTime_TIME_GET_SECOND(o) (((PyDateTime_Time*)o)->data[2]) +#define PyDateTime_TIME_GET_MICROSECOND(o) \ + ((((PyDateTime_Time*)o)->data[3] << 16) | \ + (((PyDateTime_Time*)o)->data[4] << 8) | \ + ((PyDateTime_Time*)o)->data[5]) + +#define PyDate_Check(op) PyObject_TypeCheck(op, &PyDateTime_DateType) +#define PyDate_CheckExact(op) ((op)->ob_type == &PyDateTime_DateType) + +#define PyDateTime_Check(op) PyObject_TypeCheck(op, &PyDateTime_DateTimeType) +#define PyDateTime_CheckExact(op) ((op)->ob_type == &PyDateTime_DateTimeType) + +#define PyDateTimeTZ_Check(op) PyObject_TypeCheck(op, &PyDateTime_DateTimeTZType) +#define PyDateTimeTZ_CheckExact(op) ((op)->ob_type == &PyDateTime_DateTimeTZType) + +#define PyTime_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeType) +#define PyTime_CheckExact(op) ((op)->ob_type == &PyDateTime_TimeType) + +#define PyTimeTZ_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeTZType) +#define PyTimeTZ_CheckExact(op) ((op)->ob_type == &PyDateTime_TimeTZType) + +#define PyDelta_Check(op) PyObject_TypeCheck(op, &PyDateTime_DeltaType) +#define PyDelta_CheckExact(op) ((op)->ob_type == &PyDateTime_DeltaType) + +#define PyTZInfo_Check(op) PyObject_TypeCheck(op, &PyDateTime_TZInfoType) +#define PyTZInfo_CheckExact(op) ((op)->ob_type == &PyDateTime_TZInfoType) + +#endif diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py new file mode 100644 index 00000000000..058fb900bc7 --- /dev/null +++ b/Lib/test/test_datetime.py @@ -0,0 +1,2128 @@ +"""Test date/time type.""" + +import sys +import unittest + +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 + +############################################################################# +# module tests + +class TestModule(unittest.TestCase): + + def test_constants(self): + import datetime + self.assertEqual(datetime.MINYEAR, 1) + self.assertEqual(datetime.MAXYEAR, 9999) + +############################################################################# +# tzinfo tests + +class FixedOffset(tzinfo): + def __init__(self, offset, name, dstoffset=42): + self.__offset = offset + self.__name = name + self.__dstoffset = dstoffset + def __repr__(self): + return self.__name.lower() + def utcoffset(self, dt): + return self.__offset + def tzname(self, dt): + return self.__name + def dst(self, dt): + return self.__dstoffset + +class TestTZInfo(unittest.TestCase): + + def test_non_abstractness(self): + # In order to allow subclasses to get pickled, the C implementation + # wasn't able to get away with having __init__ raise + # NotImplementedError. + useless = tzinfo() + dt = datetime.max + self.assertRaises(NotImplementedError, useless.tzname, dt) + self.assertRaises(NotImplementedError, useless.utcoffset, dt) + self.assertRaises(NotImplementedError, useless.dst, dt) + + def test_subclass_must_override(self): + class NotEnough(tzinfo): + def __init__(self, offset, name): + self.__offset = offset + self.__name = name + self.failUnless(issubclass(NotEnough, tzinfo)) + ne = NotEnough(3, "NotByALongShot") + self.failUnless(isinstance(ne, tzinfo)) + + dt = datetime.now() + self.assertRaises(NotImplementedError, ne.tzname, dt) + self.assertRaises(NotImplementedError, ne.utcoffset, dt) + self.assertRaises(NotImplementedError, ne.dst, dt) + + def test_normal(self): + fo = FixedOffset(3, "Three") + self.failUnless(isinstance(fo, tzinfo)) + for dt in datetime.now(), None: + self.assertEqual(fo.utcoffset(dt), 3) + self.assertEqual(fo.tzname(dt), "Three") + self.assertEqual(fo.dst(dt), 42) + + def test_pickling_base(self): + import pickle, cPickle + + # There's no point to pickling tzinfo objects on their own (they + # carry no data), but they need to be picklable anyway else + # concrete subclasses can't be pickled. + orig = tzinfo.__new__(tzinfo) + self.failUnless(type(orig) is tzinfo) + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.failUnless(type(derived) is tzinfo) + + def test_pickling_subclass(self): + import pickle, cPickle + + # Make sure we can pickle/unpickle an instance of a subclass. + orig = FixedOffset(-300, 'cookie') + self.failUnless(isinstance(orig, tzinfo)) + self.failUnless(type(orig) is FixedOffset) + self.assertEqual(orig.utcoffset(None), -300) + self.assertEqual(orig.tzname(None), 'cookie') + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.failUnless(isinstance(derived, tzinfo)) + self.failUnless(type(derived) is FixedOffset) + self.assertEqual(derived.utcoffset(None), -300) + self.assertEqual(derived.tzname(None), 'cookie') + +############################################################################# +# timedelta tests + +class TestTimeDelta(unittest.TestCase): + + def test_constructor(self): + eq = self.assertEqual + td = timedelta + + # Check keyword args to constructor + eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0, + milliseconds=0, microseconds=0)) + eq(td(1), td(days=1)) + eq(td(0, 1), td(seconds=1)) + eq(td(0, 0, 1), td(microseconds=1)) + eq(td(weeks=1), td(days=7)) + eq(td(days=1), td(hours=24)) + eq(td(hours=1), td(minutes=60)) + eq(td(minutes=1), td(seconds=60)) + eq(td(seconds=1), td(milliseconds=1000)) + eq(td(milliseconds=1), td(microseconds=1000)) + + # Check float args to constructor + eq(td(weeks=1.0/7), td(days=1)) + eq(td(days=1.0/24), td(hours=1)) + eq(td(hours=1.0/60), td(minutes=1)) + eq(td(minutes=1.0/60), td(seconds=1)) + eq(td(seconds=0.001), td(milliseconds=1)) + eq(td(milliseconds=0.001), td(microseconds=1)) + + def test_computations(self): + eq = self.assertEqual + td = timedelta + + a = td(7) # One week + b = td(0, 60) # One minute + c = td(0, 0, 1000) # One millisecond + eq(a+b+c, td(7, 60, 1000)) + eq(a-b, td(6, 24*3600 - 60)) + eq(-a, td(-7)) + eq(+a, td(7)) + eq(-b, td(-1, 24*3600 - 60)) + eq(-c, td(-1, 24*3600 - 1, 999000)) + eq(abs(a), a) + eq(abs(-a), a) + eq(td(6, 24*3600), a) + eq(td(0, 0, 60*1000000), b) + eq(a*10, td(70)) + eq(a*10, 10*a) + eq(a*10L, 10*a) + eq(b*10, td(0, 600)) + eq(10*b, td(0, 600)) + eq(b*10L, td(0, 600)) + eq(c*10, td(0, 0, 10000)) + eq(10*c, td(0, 0, 10000)) + eq(c*10L, td(0, 0, 10000)) + eq(a*-1, -a) + eq(b*-2, -b-b) + eq(c*-2, -c+-c) + eq(b*(60*24), (b*60)*24) + eq(b*(60*24), (60*b)*24) + eq(c*1000, td(0, 1)) + eq(1000*c, td(0, 1)) + eq(a//7, td(1)) + eq(b//10, td(0, 6)) + eq(c//1000, td(0, 0, 1)) + eq(a//10, td(0, 7*24*360)) + eq(a//3600000, td(0, 0, 7*24*1000)) + + def test_disallowed_computations(self): + a = timedelta(42) + + # Add/sub ints, longs, floats should be illegal + for i in 1, 1L, 1.0: + self.assertRaises(TypeError, lambda: a+i) + self.assertRaises(TypeError, lambda: a-i) + self.assertRaises(TypeError, lambda: i+a) + self.assertRaises(TypeError, lambda: i-a) + + # Mul/div by float isn't supported. + x = 2.3 + self.assertRaises(TypeError, lambda: a*x) + self.assertRaises(TypeError, lambda: x*a) + self.assertRaises(TypeError, lambda: a/x) + self.assertRaises(TypeError, lambda: x/a) + self.assertRaises(TypeError, lambda: a // x) + self.assertRaises(TypeError, lambda: x // a) + + # Divison of int by timedelta doesn't make sense. + # Division by zero doesn't make sense. + for zero in 0, 0L: + self.assertRaises(TypeError, lambda: zero // a) + self.assertRaises(ZeroDivisionError, lambda: a // zero) + + def test_basic_attributes(self): + days, seconds, us = 1, 7, 31 + td = timedelta(days, seconds, us) + self.assertEqual(td.days, days) + self.assertEqual(td.seconds, seconds) + self.assertEqual(td.microseconds, us) + + def test_carries(self): + t1 = timedelta(days=100, + weeks=-7, + hours=-24*(100-49), + minutes=-3, + seconds=12, + microseconds=(3*60 - 12) * 1e6 + 1) + t2 = timedelta(microseconds=1) + self.assertEqual(t1, t2) + + def test_hash_equality(self): + t1 = timedelta(days=100, + weeks=-7, + hours=-24*(100-49), + minutes=-3, + seconds=12, + microseconds=(3*60 - 12) * 1000000) + t2 = timedelta() + self.assertEqual(hash(t1), hash(t2)) + + t1 += timedelta(weeks=7) + t2 += timedelta(days=7*7) + self.assertEqual(t1, t2) + self.assertEqual(hash(t1), hash(t2)) + + d = {t1: 1} + d[t2] = 2 + self.assertEqual(len(d), 1) + self.assertEqual(d[t1], 2) + + def test_pickling(self): + import pickle, cPickle + args = 12, 34, 56 + orig = timedelta(*args) + state = orig.__getstate__() + self.assertEqual(args, state) + derived = timedelta() + derived.__setstate__(state) + self.assertEqual(orig, derived) + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + + def test_compare(self): + t1 = timedelta(2, 3, 4) + t2 = timedelta(2, 3, 4) + self.failUnless(t1 == t2) + self.failUnless(t1 <= t2) + self.failUnless(t1 >= t2) + self.failUnless(not t1 != t2) + self.failUnless(not t1 < t2) + self.failUnless(not t1 > t2) + self.assertEqual(cmp(t1, t2), 0) + self.assertEqual(cmp(t2, t1), 0) + + for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): + t2 = timedelta(*args) # this is larger than t1 + self.failUnless(t1 < t2) + self.failUnless(t2 > t1) + self.failUnless(t1 <= t2) + self.failUnless(t2 >= t1) + self.failUnless(t1 != t2) + self.failUnless(t2 != t1) + self.failUnless(not t1 == t2) + self.failUnless(not t2 == t1) + self.failUnless(not t1 > t2) + self.failUnless(not t2 < t1) + self.failUnless(not t1 >= t2) + self.failUnless(not t2 <= t1) + self.assertEqual(cmp(t1, t2), -1) + self.assertEqual(cmp(t2, t1), 1) + + for badarg in 10, 10L, 34.5, "abc", {}, [], (): + self.assertRaises(TypeError, lambda: t1 == badarg) + self.assertRaises(TypeError, lambda: t1 != badarg) + self.assertRaises(TypeError, lambda: t1 <= badarg) + self.assertRaises(TypeError, lambda: t1 < badarg) + self.assertRaises(TypeError, lambda: t1 > badarg) + self.assertRaises(TypeError, lambda: t1 >= badarg) + self.assertRaises(TypeError, lambda: badarg == t1) + self.assertRaises(TypeError, lambda: badarg != t1) + self.assertRaises(TypeError, lambda: badarg <= t1) + self.assertRaises(TypeError, lambda: badarg < t1) + self.assertRaises(TypeError, lambda: badarg > t1) + self.assertRaises(TypeError, lambda: badarg >= t1) + + def test_str(self): + td = timedelta + eq = self.assertEqual + + eq(str(td(1)), "1 day, 0:00:00") + eq(str(td(-1)), "-1 day, 0:00:00") + eq(str(td(2)), "2 days, 0:00:00") + eq(str(td(-2)), "-2 days, 0:00:00") + + eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59") + eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04") + eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)), + "-210 days, 23:12:34") + + eq(str(td(milliseconds=1)), "0:00:00.001000") + eq(str(td(microseconds=3)), "0:00:00.000003") + + eq(str(td(days=999999999, hours=23, minutes=59, seconds=59, + microseconds=999999)), + "999999999 days, 23:59:59.999999") + + def test_roundtrip(self): + for td in (timedelta(days=999999999, hours=23, minutes=59, + seconds=59, microseconds=999999), + timedelta(days=-999999999), + timedelta(days=1, seconds=2, microseconds=3)): + + # Verify td -> string -> td identity. + s = repr(td) + self.failUnless(s.startswith('datetime.')) + s = s[9:] + td2 = eval(s) + self.assertEqual(td, td2) + + # Verify identity via reconstructing from pieces. + td2 = timedelta(td.days, td.seconds, td.microseconds) + self.assertEqual(td, td2) + + def test_resolution_info(self): + self.assert_(isinstance(timedelta.min, timedelta)) + self.assert_(isinstance(timedelta.max, timedelta)) + self.assert_(isinstance(timedelta.resolution, timedelta)) + self.assert_(timedelta.max > timedelta.min) + self.assertEqual(timedelta.min, timedelta(-999999999)) + self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1)) + self.assertEqual(timedelta.resolution, timedelta(0, 0, 1)) + + def test_overflow(self): + tiny = timedelta.resolution + + td = timedelta.min + tiny + td -= tiny # no problem + self.assertRaises(OverflowError, td.__sub__, tiny) + self.assertRaises(OverflowError, td.__add__, -tiny) + + td = timedelta.max - tiny + td += tiny # no problem + self.assertRaises(OverflowError, td.__add__, tiny) + self.assertRaises(OverflowError, td.__sub__, -tiny) + + self.assertRaises(OverflowError, lambda: -timedelta.max) + + def test_microsecond_rounding(self): + td = timedelta + eq = self.assertEqual + + # Single-field rounding. + eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0 + eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0 + eq(td(milliseconds=0.6/1000), td(microseconds=1)) + eq(td(milliseconds=-0.6/1000), td(microseconds=-1)) + + # Rounding due to contributions from more than one field. + us_per_hour = 3600e6 + us_per_day = us_per_hour * 24 + eq(td(days=.4/us_per_day), td(0)) + eq(td(hours=.2/us_per_hour), td(0)) + eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1)) + + eq(td(days=-.4/us_per_day), td(0)) + eq(td(hours=-.2/us_per_hour), td(0)) + eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1)) + + def test_massive_normalization(self): + td = timedelta(microseconds=-1) + self.assertEqual((td.days, td.seconds, td.microseconds), + (-1, 24*3600-1, 999999)) + + def test_bool(self): + self.failUnless(timedelta(1)) + self.failUnless(timedelta(0, 1)) + self.failUnless(timedelta(0, 0, 1)) + self.failUnless(timedelta(microseconds=1)) + self.failUnless(not timedelta(0)) + +############################################################################# +# date tests + +class TestDateOnly(unittest.TestCase): + # Tests here won't pass if also run on datetime objects, so don't + # subclass this to test datetimes too. + + def test_delta_non_days_ignored(self): + dt = date(2000, 1, 2) + delta = timedelta(days=1, hours=2, minutes=3, seconds=4, + microseconds=5) + days = timedelta(delta.days) + self.assertEqual(days, timedelta(1)) + + dt2 = dt + delta + self.assertEqual(dt2, dt + days) + + dt2 = delta + dt + self.assertEqual(dt2, dt + days) + + dt2 = dt - delta + self.assertEqual(dt2, dt - days) + + delta = -delta + days = timedelta(delta.days) + self.assertEqual(days, timedelta(-2)) + + dt2 = dt + delta + self.assertEqual(dt2, dt + days) + + dt2 = delta + dt + self.assertEqual(dt2, dt + days) + + dt2 = dt - delta + self.assertEqual(dt2, dt - days) + +class TestDate(unittest.TestCase): + # Tests here should pass for both dates and datetimes, except for a + # few tests that TestDateTime overrides. + + theclass = date + + def test_basic_attributes(self): + dt = self.theclass(2002, 3, 1) + self.assertEqual(dt.year, 2002) + self.assertEqual(dt.month, 3) + self.assertEqual(dt.day, 1) + + def test_roundtrip(self): + for dt in (self.theclass(1, 2, 3), + self.theclass.today()): + # Verify dt -> string -> date identity. + s = repr(dt) + self.failUnless(s.startswith('datetime.')) + s = s[9:] + dt2 = eval(s) + self.assertEqual(dt, dt2) + + # Verify identity via reconstructing from pieces. + dt2 = self.theclass(dt.year, dt.month, dt.day) + self.assertEqual(dt, dt2) + + def test_ordinal_conversions(self): + # Check some fixed values. + for y, m, d, n in [(1, 1, 1, 1), # calendar origin + (1, 12, 31, 365), + (2, 1, 1, 366), + # first example from "Calendrical Calculations" + (1945, 11, 12, 710347)]: + d = self.theclass(y, m, d) + self.assertEqual(n, d.toordinal()) + fromord = self.theclass.fromordinal(n) + self.assertEqual(d, fromord) + if hasattr(fromord, "hour"): + # if we're checking something fancier than a date, verify + # the extra fields have been zeroed out + self.assertEqual(fromord.hour, 0) + self.assertEqual(fromord.minute, 0) + self.assertEqual(fromord.second, 0) + self.assertEqual(fromord.microsecond, 0) + + # 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() + d2 = self.theclass.fromordinal(n) + self.assertEqual(d, d2) + # 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] + for year, isleap in (2000, True), (2002, False): + n = self.theclass(year, 1, 1).toordinal() + for month, maxday in zip(range(1, 13), dim): + if month == 2 and isleap: + maxday += 1 + for day in range(1, maxday+1): + d = self.theclass(year, month, day) + self.assertEqual(d.toordinal(), n) + self.assertEqual(d, self.theclass.fromordinal(n)) + n += 1 + + def test_extreme_ordinals(self): + a = self.theclass.min + a = self.theclass(a.year, a.month, a.day) # get rid of time parts + aord = a.toordinal() + b = a.fromordinal(aord) + self.assertEqual(a, b) + + self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1)) + + b = a + timedelta(days=1) + self.assertEqual(b.toordinal(), aord + 1) + self.assertEqual(b, self.theclass.fromordinal(aord + 1)) + + a = self.theclass.max + a = self.theclass(a.year, a.month, a.day) # get rid of time parts + aord = a.toordinal() + b = a.fromordinal(aord) + self.assertEqual(a, b) + + self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1)) + + b = a - timedelta(days=1) + self.assertEqual(b.toordinal(), aord - 1) + self.assertEqual(b, self.theclass.fromordinal(aord - 1)) + + def test_bad_constructor_arguments(self): + # bad years + self.theclass(MINYEAR, 1, 1) # no exception + self.theclass(MAXYEAR, 1, 1) # no exception + self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1) + self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1) + # bad months + self.theclass(2000, 1, 1) # no exception + self.theclass(2000, 12, 1) # no exception + self.assertRaises(ValueError, self.theclass, 2000, 0, 1) + self.assertRaises(ValueError, self.theclass, 2000, 13, 1) + # bad days + self.theclass(2000, 2, 29) # no exception + self.theclass(2004, 2, 29) # no exception + self.theclass(2400, 2, 29) # no exception + self.assertRaises(ValueError, self.theclass, 2000, 2, 30) + self.assertRaises(ValueError, self.theclass, 2001, 2, 29) + self.assertRaises(ValueError, self.theclass, 2100, 2, 29) + self.assertRaises(ValueError, self.theclass, 1900, 2, 29) + self.assertRaises(ValueError, self.theclass, 2000, 1, 0) + self.assertRaises(ValueError, self.theclass, 2000, 1, 32) + + def test_hash_equality(self): + d = self.theclass(2000, 12, 31) + # same thing + e = self.theclass(2000, 12, 31) + 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(2001, 1, 1) + # same thing + e = self.theclass(2001, 1, 1) + 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_computations(self): + a = self.theclass(2002, 1, 31) + b = self.theclass(1956, 1, 31) + + diff = a-b + self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4))) + self.assertEqual(diff.seconds, 0) + self.assertEqual(diff.microseconds, 0) + + day = timedelta(1) + week = timedelta(7) + a = self.theclass(2002, 3, 2) + self.assertEqual(a + day, self.theclass(2002, 3, 3)) + self.assertEqual(day + a, self.theclass(2002, 3, 3)) + self.assertEqual(a - day, self.theclass(2002, 3, 1)) + self.assertEqual(-day + a, self.theclass(2002, 3, 1)) + self.assertEqual(a + week, self.theclass(2002, 3, 9)) + self.assertEqual(a - week, self.theclass(2002, 2, 23)) + self.assertEqual(a + 52*week, self.theclass(2003, 3, 1)) + self.assertEqual(a - 52*week, self.theclass(2001, 3, 3)) + self.assertEqual((a + week) - a, week) + self.assertEqual((a + day) - a, day) + self.assertEqual((a - week) - a, -week) + self.assertEqual((a - day) - a, -day) + self.assertEqual(a - (a + week), -week) + self.assertEqual(a - (a + day), -day) + self.assertEqual(a - (a - week), week) + self.assertEqual(a - (a - day), day) + + # Add/sub ints, longs, floats should be illegal + for i in 1, 1L, 1.0: + self.assertRaises(TypeError, lambda: a+i) + self.assertRaises(TypeError, lambda: a-i) + self.assertRaises(TypeError, lambda: i+a) + self.assertRaises(TypeError, lambda: i-a) + + # delta - date is senseless. + self.assertRaises(TypeError, lambda: day - a) + # mixing date and (delta or date) via * or // is senseless + self.assertRaises(TypeError, lambda: day * a) + self.assertRaises(TypeError, lambda: a * day) + self.assertRaises(TypeError, lambda: day // a) + self.assertRaises(TypeError, lambda: a // day) + self.assertRaises(TypeError, lambda: a * a) + self.assertRaises(TypeError, lambda: a // a) + # date + date is senseless + self.assertRaises(TypeError, lambda: a + a) + + def test_overflow(self): + tiny = self.theclass.resolution + + dt = self.theclass.min + tiny + dt -= tiny # no problem + self.assertRaises(OverflowError, dt.__sub__, tiny) + self.assertRaises(OverflowError, dt.__add__, -tiny) + + dt = self.theclass.max - tiny + dt += tiny # no problem + self.assertRaises(OverflowError, dt.__add__, tiny) + self.assertRaises(OverflowError, dt.__sub__, -tiny) + + def test_fromtimestamp(self): + import time + + # Try an arbitrary fixed value. + year, month, day = 1999, 9, 19 + ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1)) + d = self.theclass.fromtimestamp(ts) + self.assertEqual(d.year, year) + self.assertEqual(d.month, month) + self.assertEqual(d.day, day) + + def test_today(self): + import time + + # We claim that today() is like fromtimestamp(time.time()), so + # prove it. + for dummy in range(3): + today = self.theclass.today() + ts = time.time() + todayagain = self.theclass.fromtimestamp(ts) + if today == todayagain: + break + # There are several legit reasons that could fail: + # 1. It recently became midnight, between the today() and the + # time() calls. + # 2. The platform time() has such fine resolution that we'll + # never get the same value twice. + # 3. The platform time() has poor resolution, and we just + # happened to call today() right before a resolution quantum + # boundary. + # 4. The system clock got fiddled between calls. + # In any case, wait a little while and try again. + time.sleep(0.1) + + # It worked or it didn't. If it didn't, assume it's reason #2, and + # let the test pass if they're within half a second of each other. + self.failUnless(today == todayagain or + abs(todayagain - today) < timedelta(seconds=0.5)) + + def test_weekday(self): + for i in range(7): + # March 4, 2002 is a Monday + self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i) + self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1) + # January 2, 1956 is a Monday + self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i) + self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1) + + def test_isocalendar(self): + # Check examples from + # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm + for i in range(7): + d = self.theclass(2003, 12, 22+i) + self.assertEqual(d.isocalendar(), (2003, 52, i+1)) + d = self.theclass(2003, 12, 29) + timedelta(i) + self.assertEqual(d.isocalendar(), (2004, 1, i+1)) + d = self.theclass(2004, 1, 5+i) + self.assertEqual(d.isocalendar(), (2004, 2, i+1)) + d = self.theclass(2009, 12, 21+i) + self.assertEqual(d.isocalendar(), (2009, 52, i+1)) + d = self.theclass(2009, 12, 28) + timedelta(i) + self.assertEqual(d.isocalendar(), (2009, 53, i+1)) + d = self.theclass(2010, 1, 4+i) + self.assertEqual(d.isocalendar(), (2010, 1, i+1)) + + def test_iso_long_years(self): + # Calculate long ISO years and compare to table from + # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm + ISO_LONG_YEARS_TABLE = """ + 4 32 60 88 + 9 37 65 93 + 15 43 71 99 + 20 48 76 + 26 54 82 + + 105 133 161 189 + 111 139 167 195 + 116 144 172 + 122 150 178 + 128 156 184 + + 201 229 257 285 + 207 235 263 291 + 212 240 268 296 + 218 246 274 + 224 252 280 + + 303 331 359 387 + 308 336 364 392 + 314 342 370 398 + 320 348 376 + 325 353 381 + """ + iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split()) + iso_long_years.sort() + L = [] + for i in range(400): + d = self.theclass(2000+i, 12, 31) + d1 = self.theclass(1600+i, 12, 31) + self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:]) + if d.isocalendar()[1] == 53: + L.append(i) + self.assertEqual(L, iso_long_years) + + def test_isoformat(self): + t = self.theclass(2, 3, 2) + self.assertEqual(t.isoformat(), "0002-03-02") + + def test_ctime(self): + t = self.theclass(2002, 3, 2) + self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002") + + def test_strftime(self): + t = self.theclass(2005, 3, 2) + self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05") + + self.assertRaises(TypeError, t.strftime) # needs an arg + self.assertRaises(TypeError, t.strftime, "one", "two") # too many args + self.assertRaises(TypeError, t.strftime, 42) # arg wrong type + + # A naive object replaces %z and %Z w/ empty strings. + self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") + + 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) + + def test_extreme_timedelta(self): + big = self.theclass.max - self.theclass.min + # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds + n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds + # n == 315537897599999999 ~= 2**58.13 + justasbig = timedelta(0, 0, n) + self.assertEqual(big, justasbig) + self.assertEqual(self.theclass.min + big, self.theclass.max) + self.assertEqual(self.theclass.max - big, self.theclass.min) + + def test_timetuple(self): + for i in range(7): + # January 2, 1956 is a Monday (0) + d = self.theclass(1956, 1, 2+i) + t = d.timetuple() + self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1)) + # February 1, 1956 is a Wednesday (2) + d = self.theclass(1956, 2, 1+i) + t = d.timetuple() + self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1)) + # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day + # of the year. + d = self.theclass(1956, 3, 1+i) + t = d.timetuple() + self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1)) + self.assertEqual(t.tm_year, 1956) + self.assertEqual(t.tm_mon, 3) + self.assertEqual(t.tm_mday, 1+i) + self.assertEqual(t.tm_hour, 0) + self.assertEqual(t.tm_min, 0) + self.assertEqual(t.tm_sec, 0) + self.assertEqual(t.tm_wday, (3+i)%7) + self.assertEqual(t.tm_yday, 61+i) + self.assertEqual(t.tm_isdst, -1) + + def test_pickling(self): + import pickle, cPickle + args = 6, 7, 23 + orig = self.theclass(*args) + state = orig.__getstate__() + self.assertEqual(state, '\x00\x06\x07\x17') + derived = self.theclass(1, 1, 1) + derived.__setstate__(state) + self.assertEqual(orig, derived) + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + + def test_compare(self): + t1 = self.theclass(2, 3, 4) + t2 = self.theclass(2, 3, 4) + self.failUnless(t1 == t2) + self.failUnless(t1 <= t2) + self.failUnless(t1 >= t2) + self.failUnless(not t1 != t2) + self.failUnless(not t1 < t2) + self.failUnless(not t1 > t2) + self.assertEqual(cmp(t1, t2), 0) + self.assertEqual(cmp(t2, t1), 0) + + for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): + t2 = self.theclass(*args) # this is larger than t1 + self.failUnless(t1 < t2) + self.failUnless(t2 > t1) + self.failUnless(t1 <= t2) + self.failUnless(t2 >= t1) + self.failUnless(t1 != t2) + self.failUnless(t2 != t1) + self.failUnless(not t1 == t2) + self.failUnless(not t2 == t1) + self.failUnless(not t1 > t2) + self.failUnless(not t2 < t1) + self.failUnless(not t1 >= t2) + self.failUnless(not t2 <= t1) + self.assertEqual(cmp(t1, t2), -1) + self.assertEqual(cmp(t2, t1), 1) + + for badarg in 10, 10L, 34.5, "abc", {}, [], (): + self.assertRaises(TypeError, lambda: t1 == badarg) + self.assertRaises(TypeError, lambda: t1 != badarg) + self.assertRaises(TypeError, lambda: t1 <= badarg) + self.assertRaises(TypeError, lambda: t1 < badarg) + self.assertRaises(TypeError, lambda: t1 > badarg) + self.assertRaises(TypeError, lambda: t1 >= badarg) + self.assertRaises(TypeError, lambda: badarg == t1) + self.assertRaises(TypeError, lambda: badarg != t1) + self.assertRaises(TypeError, lambda: badarg <= t1) + self.assertRaises(TypeError, lambda: badarg < t1) + self.assertRaises(TypeError, lambda: badarg > t1) + self.assertRaises(TypeError, lambda: badarg >= t1) + + def test_bool(self): + # All dates are considered true. + self.failUnless(self.theclass.min) + self.failUnless(self.theclass.max) + +############################################################################# +# datetime tests + +class TestDateTime(TestDate): + + theclass = datetime + + def test_basic_attributes(self): + dt = self.theclass(2002, 3, 1, 12, 0) + self.assertEqual(dt.year, 2002) + self.assertEqual(dt.month, 3) + self.assertEqual(dt.day, 1) + self.assertEqual(dt.hour, 12) + self.assertEqual(dt.minute, 0) + self.assertEqual(dt.second, 0) + self.assertEqual(dt.microsecond, 0) + + def test_basic_attributes_nonzero(self): + # Make sure all attributes are non-zero so bugs in + # bit-shifting access show up. + dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000) + self.assertEqual(dt.year, 2002) + self.assertEqual(dt.month, 3) + self.assertEqual(dt.day, 1) + self.assertEqual(dt.hour, 12) + self.assertEqual(dt.minute, 59) + self.assertEqual(dt.second, 59) + self.assertEqual(dt.microsecond, 8000) + + def test_roundtrip(self): + for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7), + self.theclass.now()): + # Verify dt -> string -> datetime identity. + s = repr(dt) + self.failUnless(s.startswith('datetime.')) + s = s[9:] + dt2 = eval(s) + self.assertEqual(dt, dt2) + + # Verify identity via reconstructing from pieces. + dt2 = self.theclass(dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.microsecond) + self.assertEqual(dt, dt2) + + def test_isoformat(self): + t = self.theclass(2, 3, 2, 4, 5, 1, 123) + self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123") + self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123") + self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123") + # str is ISO format with the separator forced to a blank. + self.assertEqual(str(t), "0002-03-02 04:05:01.000123") + + t = self.theclass(2, 3, 2) + self.assertEqual(t.isoformat(), "0002-03-02T00:00:00") + self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00") + self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00") + # str is ISO format with the separator forced to a blank. + self.assertEqual(str(t), "0002-03-02 00:00:00") + + def test_more_ctime(self): + # Test fields that TestDate doesn't touch. + import time + + t = self.theclass(2002, 3, 2, 18, 3, 5, 123) + self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002") + # Oops! The next line fails on Win2K under MSVC 6, so it's commented + # out. The difference is that t.ctime() produces " 2" for the day, + # but platform ctime() produces "02" for the day. According to + # C99, t.ctime() is correct here. + # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) + + # So test a case where that difference doesn't matter. + t = self.theclass(2002, 3, 22, 18, 3, 5, 123) + self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) + + def test_tz_independent_comparing(self): + dt1 = self.theclass(2002, 3, 1, 9, 0, 0) + dt2 = self.theclass(2002, 3, 1, 10, 0, 0) + dt3 = self.theclass(2002, 3, 1, 9, 0, 0) + self.assertEqual(dt1, dt3) + self.assert_(dt2 > dt3) + + # Make sure comparison doesn't forget microseconds, and isn't done + # via comparing a float timestamp (an IEEE double doesn't have enough + # precision to span microsecond resolution across years 1 thru 9999, + # so comparing via timestamp necessarily calls some distinct values + # equal). + dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998) + us = timedelta(microseconds=1) + dt2 = dt1 + us + self.assertEqual(dt2 - dt1, us) + self.assert_(dt1 < dt2) + + def test_bad_constructor_arguments(self): + # bad years + self.theclass(MINYEAR, 1, 1) # no exception + self.theclass(MAXYEAR, 1, 1) # no exception + self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1) + self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1) + # bad months + self.theclass(2000, 1, 1) # no exception + self.theclass(2000, 12, 1) # no exception + self.assertRaises(ValueError, self.theclass, 2000, 0, 1) + self.assertRaises(ValueError, self.theclass, 2000, 13, 1) + # bad days + self.theclass(2000, 2, 29) # no exception + self.theclass(2004, 2, 29) # no exception + self.theclass(2400, 2, 29) # no exception + self.assertRaises(ValueError, self.theclass, 2000, 2, 30) + self.assertRaises(ValueError, self.theclass, 2001, 2, 29) + self.assertRaises(ValueError, self.theclass, 2100, 2, 29) + self.assertRaises(ValueError, self.theclass, 1900, 2, 29) + self.assertRaises(ValueError, self.theclass, 2000, 1, 0) + self.assertRaises(ValueError, self.theclass, 2000, 1, 32) + # bad hours + self.theclass(2000, 1, 31, 0) # no exception + self.theclass(2000, 1, 31, 23) # no exception + self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1) + self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24) + # bad minutes + self.theclass(2000, 1, 31, 23, 0) # no exception + self.theclass(2000, 1, 31, 23, 59) # no exception + self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1) + self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60) + # bad seconds + self.theclass(2000, 1, 31, 23, 59, 0) # no exception + self.theclass(2000, 1, 31, 23, 59, 59) # no exception + self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1) + self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60) + # bad microseconds + self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception + self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception + self.assertRaises(ValueError, self.theclass, + 2000, 1, 31, 23, 59, 59, -1) + self.assertRaises(ValueError, self.theclass, + 2000, 1, 31, 23, 59, 59, + 1000000) + + def test_hash_equality(self): + d = self.theclass(2000, 12, 31, 23, 30, 17) + e = self.theclass(2000, 12, 31, 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(2001, 1, 1, 0, 5, 17) + e = self.theclass(2001, 1, 1, 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_computations(self): + a = self.theclass(2002, 1, 31) + b = self.theclass(1956, 1, 31) + diff = a-b + self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4))) + self.assertEqual(diff.seconds, 0) + self.assertEqual(diff.microseconds, 0) + a = self.theclass(2002, 3, 2, 17, 6) + millisec = timedelta(0, 0, 1000) + hour = timedelta(0, 3600) + day = timedelta(1) + week = timedelta(7) + self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6)) + self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6)) + self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6)) + self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6)) + self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6)) + self.assertEqual(a - hour, a + -hour) + self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6)) + self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6)) + self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6)) + self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6)) + self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6)) + self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6)) + self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6)) + self.assertEqual((a + week) - a, week) + self.assertEqual((a + day) - a, day) + self.assertEqual((a + hour) - a, hour) + self.assertEqual((a + millisec) - a, millisec) + self.assertEqual((a - week) - a, -week) + self.assertEqual((a - day) - a, -day) + self.assertEqual((a - hour) - a, -hour) + self.assertEqual((a - millisec) - a, -millisec) + self.assertEqual(a - (a + week), -week) + self.assertEqual(a - (a + day), -day) + self.assertEqual(a - (a + hour), -hour) + self.assertEqual(a - (a + millisec), -millisec) + self.assertEqual(a - (a - week), week) + self.assertEqual(a - (a - day), day) + self.assertEqual(a - (a - hour), hour) + self.assertEqual(a - (a - millisec), millisec) + self.assertEqual(a + (week + day + hour + millisec), + self.theclass(2002, 3, 10, 18, 6, 0, 1000)) + self.assertEqual(a + (week + day + hour + millisec), + (((a + week) + day) + hour) + millisec) + self.assertEqual(a - (week + day + hour + millisec), + self.theclass(2002, 2, 22, 16, 5, 59, 999000)) + self.assertEqual(a - (week + day + hour + millisec), + (((a - week) - day) - hour) - millisec) + # Add/sub ints, longs, floats should be illegal + for i in 1, 1L, 1.0: + self.assertRaises(TypeError, lambda: a+i) + self.assertRaises(TypeError, lambda: a-i) + self.assertRaises(TypeError, lambda: i+a) + self.assertRaises(TypeError, lambda: i-a) + + # delta - datetime is senseless. + self.assertRaises(TypeError, lambda: day - a) + # mixing datetime and (delta or datetime) via * or // is senseless + self.assertRaises(TypeError, lambda: day * a) + self.assertRaises(TypeError, lambda: a * day) + self.assertRaises(TypeError, lambda: day // a) + self.assertRaises(TypeError, lambda: a // day) + self.assertRaises(TypeError, lambda: a * a) + self.assertRaises(TypeError, lambda: a // a) + # datetime + datetime is senseless + self.assertRaises(TypeError, lambda: a + a) + + def test_pickling(self): + import pickle, cPickle + 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') + derived = self.theclass(1, 1, 1) + derived.__setstate__(state) + self.assertEqual(orig, derived) + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + + def test_more_compare(self): + # The test_compare() inherited from TestDate covers the error cases. + # We just want to test lexicographic ordering on the members datetime + # has that date lacks. + args = [2000, 11, 29, 20, 58, 16, 999998] + t1 = self.theclass(*args) + t2 = self.theclass(*args) + self.failUnless(t1 == t2) + self.failUnless(t1 <= t2) + self.failUnless(t1 >= t2) + self.failUnless(not t1 != t2) + self.failUnless(not t1 < t2) + self.failUnless(not t1 > t2) + self.assertEqual(cmp(t1, t2), 0) + self.assertEqual(cmp(t2, t1), 0) + + for i in range(len(args)): + newargs = args[:] + newargs[i] = args[i] + 1 + t2 = self.theclass(*newargs) # this is larger than t1 + self.failUnless(t1 < t2) + self.failUnless(t2 > t1) + self.failUnless(t1 <= t2) + self.failUnless(t2 >= t1) + self.failUnless(t1 != t2) + self.failUnless(t2 != t1) + self.failUnless(not t1 == t2) + self.failUnless(not t2 == t1) + self.failUnless(not t1 > t2) + self.failUnless(not t2 < t1) + self.failUnless(not t1 >= t2) + self.failUnless(not t2 <= t1) + self.assertEqual(cmp(t1, t2), -1) + self.assertEqual(cmp(t2, t1), 1) + + + # A helper for timestamp constructor tests. + def verify_field_equality(self, expected, got): + self.assertEqual(expected.tm_year, got.year) + self.assertEqual(expected.tm_mon, got.month) + self.assertEqual(expected.tm_mday, got.day) + self.assertEqual(expected.tm_hour, got.hour) + self.assertEqual(expected.tm_min, got.minute) + self.assertEqual(expected.tm_sec, got.second) + + def test_fromtimestamp(self): + import time + + ts = time.time() + expected = time.localtime(ts) + got = self.theclass.fromtimestamp(ts) + self.verify_field_equality(expected, got) + + def test_utcfromtimestamp(self): + import time + + ts = time.time() + expected = time.gmtime(ts) + got = self.theclass.utcfromtimestamp(ts) + self.verify_field_equality(expected, got) + + def test_utcnow(self): + import time + + # Call it a success if utcnow() and utcfromtimestamp() are within + # a second of each other. + tolerance = timedelta(seconds=1) + for dummy in range(3): + from_now = self.theclass.utcnow() + from_timestamp = self.theclass.utcfromtimestamp(time.time()) + if abs(from_timestamp - from_now) <= tolerance: + break + # Else try again a few times. + self.failUnless(abs(from_timestamp - from_now) <= tolerance) + + def test_more_timetuple(self): + # This tests fields beyond those tested by the TestDate.test_timetuple. + t = self.theclass(2004, 12, 31, 6, 22, 33) + self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1)) + self.assertEqual(t.timetuple(), + (t.year, t.month, t.day, + t.hour, t.minute, t.second, + t.weekday(), + t.toordinal() - date(t.year, 1, 1).toordinal() + 1, + -1)) + tt = t.timetuple() + self.assertEqual(tt.tm_year, t.year) + self.assertEqual(tt.tm_mon, t.month) + self.assertEqual(tt.tm_mday, t.day) + self.assertEqual(tt.tm_hour, t.hour) + self.assertEqual(tt.tm_min, t.minute) + self.assertEqual(tt.tm_sec, t.second) + self.assertEqual(tt.tm_wday, t.weekday()) + self.assertEqual(tt.tm_yday, t.toordinal() - + date(t.year, 1, 1).toordinal() + 1) + self.assertEqual(tt.tm_isdst, -1) + + def test_more_strftime(self): + # This tests fields beyond those tested by the TestDate.test_strftime. + t = self.theclass(2004, 12, 31, 6, 22, 33) + self.assertEqual(t.strftime("%m %d %y %S %M %H %j"), + "12 31 04 33 22 06 366") + + 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)) + + def test_combine(self): + d = date(2002, 3, 4) + t = time(18, 45, 3, 1234) + expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234) + combine = self.theclass.combine + dt = combine(d, t) + self.assertEqual(dt, expected) + + dt = combine(time=t, date=d) + self.assertEqual(dt, expected) + + self.assertEqual(d, dt.date()) + self.assertEqual(t, dt.time()) + self.assertEqual(dt, combine(dt.date(), dt.time())) + + self.assertRaises(TypeError, combine) # need an arg + self.assertRaises(TypeError, combine, d) # need two args + self.assertRaises(TypeError, combine, t, d) # args reversed + self.assertRaises(TypeError, combine, d, t, 1) # too many args + self.assertRaises(TypeError, combine, "date", "time") # wrong types + +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): + t = self.theclass(1, 2, 3, 4) + + # Verify t -> string -> time identity. + s = repr(t) + self.failUnless(s.startswith('datetime.')) + s = s[9:] + 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): + args = [1, 2, 3, 4] + t1 = self.theclass(*args) + t2 = self.theclass(*args) + self.failUnless(t1 == t2) + self.failUnless(t1 <= t2) + self.failUnless(t1 >= t2) + self.failUnless(not t1 != t2) + self.failUnless(not t1 < t2) + self.failUnless(not t1 > t2) + self.assertEqual(cmp(t1, t2), 0) + self.assertEqual(cmp(t2, t1), 0) + + for i in range(len(args)): + newargs = args[:] + newargs[i] = args[i] + 1 + t2 = self.theclass(*newargs) # this is larger than t1 + self.failUnless(t1 < t2) + self.failUnless(t2 > t1) + self.failUnless(t1 <= t2) + self.failUnless(t2 >= t1) + self.failUnless(t1 != t2) + self.failUnless(t2 != t1) + self.failUnless(not t1 == t2) + self.failUnless(not t2 == t1) + self.failUnless(not t1 > t2) + self.failUnless(not t2 < t1) + self.failUnless(not t1 >= t2) + self.failUnless(not t2 <= t1) + 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)): + self.assertRaises(TypeError, lambda: t1 == badarg) + self.assertRaises(TypeError, lambda: t1 != badarg) + self.assertRaises(TypeError, lambda: t1 <= badarg) + self.assertRaises(TypeError, lambda: t1 < badarg) + self.assertRaises(TypeError, lambda: t1 > badarg) + self.assertRaises(TypeError, lambda: t1 >= badarg) + self.assertRaises(TypeError, lambda: badarg == t1) + self.assertRaises(TypeError, lambda: badarg != t1) + self.assertRaises(TypeError, lambda: badarg <= t1) + self.assertRaises(TypeError, lambda: badarg < t1) + self.assertRaises(TypeError, lambda: badarg > t1) + self.assertRaises(TypeError, lambda: badarg >= t1) + + 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") + self.assertEqual(t.isoformat(), str(t)) + + t = self.theclass() + self.assertEqual(t.isoformat(), "00:00:00") + self.assertEqual(t.isoformat(), str(t)) + + t = self.theclass(microsecond=1) + self.assertEqual(t.isoformat(), "00:00:00.000001") + self.assertEqual(t.isoformat(), str(t)) + + t = self.theclass(microsecond=10) + self.assertEqual(t.isoformat(), "00:00:00.000010") + self.assertEqual(t.isoformat(), str(t)) + + t = self.theclass(microsecond=100) + self.assertEqual(t.isoformat(), "00:00:00.000100") + self.assertEqual(t.isoformat(), str(t)) + + t = self.theclass(microsecond=1000) + self.assertEqual(t.isoformat(), "00:00:00.001000") + self.assertEqual(t.isoformat(), str(t)) + + t = self.theclass(microsecond=10000) + self.assertEqual(t.isoformat(), "00:00:00.010000") + self.assertEqual(t.isoformat(), str(t)) + + t = self.theclass(microsecond=100000) + self.assertEqual(t.isoformat(), "00:00:00.100000") + self.assertEqual(t.isoformat(), str(t)) + + def test_strftime(self): + t = self.theclass(1, 2, 3, 4) + self.assertEqual(t.strftime('%H %M %S'), "01 02 03") + # A naive object replaces %z and %Z with empty strings. + self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") + + def test_str(self): + self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004") + self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000") + self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000") + self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03") + self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00") + + def test_repr(self): + name = 'datetime.' + self.theclass.__name__ + self.assertEqual(repr(self.theclass(1, 2, 3, 4)), + "%s(1, 2, 3, 4)" % name) + self.assertEqual(repr(self.theclass(10, 2, 3, 4000)), + "%s(10, 2, 3, 4000)" % name) + self.assertEqual(repr(self.theclass(0, 2, 3, 400000)), + "%s(0, 2, 3, 400000)" % name) + self.assertEqual(repr(self.theclass(12, 2, 3, 0)), + "%s(12, 2, 3)" % name) + self.assertEqual(repr(self.theclass(23, 15, 0, 0)), + "%s(23, 15)" % 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) + + def test_pickling(self): + import pickle, cPickle + args = 20, 59, 16, 64**2 + orig = self.theclass(*args) + state = orig.__getstate__() + self.assertEqual(state, '\x14\x3b\x10\x00\x10\x00') + derived = self.theclass() + derived.__setstate__(state) + self.assertEqual(orig, derived) + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + + def test_bool(self): + cls = self.theclass + self.failUnless(cls(1)) + self.failUnless(cls(0, 1)) + self.failUnless(cls(0, 0, 1)) + self.failUnless(cls(0, 0, 0, 1)) + self.failUnless(not cls(0)) + self.failUnless(not cls()) + + +class TestTimeTZ(TestTime): + + theclass = timetz + + def test_empty(self): + t = self.theclass() + self.assertEqual(t.hour, 0) + self.assertEqual(t.minute, 0) + self.assertEqual(t.second, 0) + self.assertEqual(t.microsecond, 0) + self.failUnless(t.tzinfo is None) + + def test_bad_tzinfo_classes(self): + tz = self.theclass + self.assertRaises(TypeError, tz, tzinfo=12) + + class NiceTry(object): + def __init__(self): pass + def utcoffset(self, dt): pass + self.assertRaises(TypeError, tz, tzinfo=NiceTry) + + class BetterTry(tzinfo): + def __init__(self): pass + def utcoffset(self, dt): pass + b = BetterTry() + t = tz(tzinfo=b) + self.failUnless(t.tzinfo is b) + + def test_zones(self): + 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) + + self.assertEqual(t1.tzinfo, est) + self.assertEqual(t2.tzinfo, utc) + self.assertEqual(t3.tzinfo, met) + self.failUnless(t4.tzinfo is None) + self.assertEqual(t5.tzinfo, utc) + + self.assertEqual(t1.utcoffset(), -300) + self.assertEqual(t2.utcoffset(), 0) + self.assertEqual(t3.utcoffset(), 60) + self.failUnless(t4.utcoffset() is None) + self.assertRaises(TypeError, t1.utcoffset, "no args") + + self.assertEqual(t1.tzname(), "EST") + self.assertEqual(t2.tzname(), "UTC") + self.assertEqual(t3.tzname(), "MET") + self.failUnless(t4.tzname() is None) + self.assertRaises(TypeError, t1.tzname, "no args") + + self.assertEqual(t1.dst(), 1) + self.assertEqual(t2.dst(), -2) + self.assertEqual(t3.dst(), 3) + self.failUnless(t4.dst() is None) + self.assertRaises(TypeError, t1.dst, "no args") + + 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.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive + self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive + self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive + + self.assertEqual(str(t1), "07:47:00-05:00") + self.assertEqual(str(t2), "12:47:00+00:00") + self.assertEqual(str(t3), "13:47:00+01:00") + self.assertEqual(str(t4), "00:00:00.000040") + self.assertEqual(str(t5), "00:00:00.000040+00:00") + + self.assertEqual(t1.isoformat(), "07:47:00-05:00") + self.assertEqual(t2.isoformat(), "12:47:00+00:00") + self.assertEqual(t3.isoformat(), "13:47:00+01:00") + self.assertEqual(t4.isoformat(), "00:00:00.000040") + self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00") + + d = 'datetime.timetz' + 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)") + self.assertEqual(repr(t4), d + "(0, 0, 0, 40)") + self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)") + + self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"), + "07:47:00 %Z=EST %z=-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") + + yuck = FixedOffset(-1439, "%z %Z %%z%%Z") + t1 = timetz(23, 59, tzinfo=yuck) + self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"), + "23:59 %Z='%z %Z %%z%%Z' %z='-2359'") + + + def test_hash_edge_cases(self): + # Offsets that overflow a basic time. + t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, "")) + t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, "")) + self.assertEqual(hash(t1), hash(t2)) + + t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, "")) + t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, "")) + self.assertEqual(hash(t1), hash(t2)) + + def test_utc_offset_out_of_bounds(self): + class Edgy(tzinfo): + def __init__(self, offset): + self.offset = offset + def utcoffset(self, dt): + return self.offset + + for offset, legit in ((-1440, False), + (-1439, True), + (1439, True), + (1440, False)): + t = timetz(1, 2, 3, tzinfo=Edgy(offset)) + if legit: + aofs = abs(offset) + h, m = divmod(aofs, 60) + tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m) + self.assertEqual(str(t), "01:02:03" + tag) + else: + self.assertRaises(ValueError, str, t) + + def test_pickling(self): + import pickle, cPickle + + # Try one without a tzinfo. + args = 20, 59, 16, 64**2 + orig = self.theclass(*args) + state = orig.__getstate__() + self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',)) + derived = self.theclass() + derived.__setstate__(state) + self.assertEqual(orig, derived) + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + + # Try one with a tzinfo. + tinfo = FixedOffset(-300, 'cookie') + orig = self.theclass(5, 6, 7, tzinfo=tinfo) + state = orig.__getstate__() + derived = self.theclass() + derived.__setstate__(state) + self.assertEqual(orig, derived) + self.failUnless(isinstance(derived.tzinfo, FixedOffset)) + self.assertEqual(derived.utcoffset(), -300) + self.assertEqual(derived.tzname(), 'cookie') + + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + self.failUnless(isinstance(derived.tzinfo, FixedOffset)) + self.assertEqual(derived.utcoffset(), -300) + self.assertEqual(derived.tzname(), 'cookie') + + def test_more_bool(self): + # Test cases with non-None tzinfo. + cls = self.theclass + + t = cls(0, tzinfo=FixedOffset(-300, "")) + self.failUnless(t) + + t = cls(5, tzinfo=FixedOffset(-300, "")) + self.failUnless(t) + + t = cls(5, tzinfo=FixedOffset(300, "")) + self.failUnless(not t) + + t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, "")) + self.failUnless(not t) + + # Mostly ensuring this doesn't overflow internally. + t = cls(0, tzinfo=FixedOffset(23*60 + 59, "")) + self.failUnless(t) + + # But this should yield a value error -- the utcoffset is bogus. + t = cls(0, tzinfo=FixedOffset(24*60, "")) + self.assertRaises(ValueError, lambda: bool(t)) + + # Likewise. + t = cls(0, tzinfo=FixedOffset(-24*60, "")) + self.assertRaises(ValueError, lambda: bool(t)) + +class TestDateTimeTZ(TestDateTime): + + theclass = datetimetz + + def test_trivial(self): + dt = self.theclass(1, 2, 3, 4, 5, 6, 7) + self.assertEqual(dt.year, 1) + self.assertEqual(dt.month, 2) + self.assertEqual(dt.day, 3) + self.assertEqual(dt.hour, 4) + self.assertEqual(dt.minute, 5) + self.assertEqual(dt.second, 6) + self.assertEqual(dt.microsecond, 7) + self.assertEqual(dt.tzinfo, None) + + def test_even_more_compare(self): + # The test_compare() and test_more_compare() inherited from TestDate + # and TestDateTime covered non-tzinfo cases. + + # Smallest possible after UTC adjustment. + t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) + # Largest possible after UTC adjustment. + t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, + tzinfo=FixedOffset(-1439, "")) + + # Make sure those compare correctly, and w/o overflow. + self.failUnless(t1 < t2) + self.failUnless(t1 != t2) + self.failUnless(t2 > t1) + + self.failUnless(t1 == t1) + self.failUnless(t2 == t2) + + # Equal afer adjustment. + t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, "")) + t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, "")) + self.assertEqual(t1, t2) + + # Change t1 not to subtract a minute, and t1 should be larger. + t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, "")) + self.failUnless(t1 > t2) + + # Change t1 to subtract 2 minutes, and t1 should be smaller. + t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, "")) + self.failUnless(t1 < t2) + + # Back to the original t1, but make seconds resolve it. + t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), + second=1) + self.failUnless(t1 > t2) + + # Likewise, but make microseconds resolve it. + t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), + microsecond=1) + self.failUnless(t1 > t2) + + # Make t2 naive and it should fail. + t2 = self.theclass.min + self.assertRaises(TypeError, lambda: t1 == t2) + self.assertEqual(t2, t2) + + # It's also naive if it has tzinfo but tzinfo.utcoffset() is None. + class Naive(tzinfo): + def utcoffset(self, dt): return None + t2 = self.theclass(5, 6, 7, tzinfo=Naive()) + self.assertRaises(TypeError, lambda: t1 == t2) + self.assertEqual(t2, t2) + + # OTOH, it's OK to compare two of these mixing the two ways of being + # naive. + t1 = self.theclass(5, 6, 7) + self.assertEqual(t1, t2) + + # Try a bogus uctoffset. + class Bogus(tzinfo): + def utcoffset(self, dt): return 1440 # out of bounds + t1 = self.theclass(2, 2, 2, tzinfo=Bogus()) + t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, "")) + self.assertRaises(ValueError, lambda: t1 == t1) + + def test_bad_tzinfo_classes(self): + tz = self.theclass + self.assertRaises(TypeError, tz, 1, 2, 3, tzinfo=12) + + class NiceTry(object): + def __init__(self): pass + def utcoffset(self, dt): pass + self.assertRaises(TypeError, tz, 1, 2, 3, tzinfo=NiceTry) + + class BetterTry(tzinfo): + def __init__(self): pass + def utcoffset(self, dt): pass + b = BetterTry() + t = tz(1, 2, 3, tzinfo=b) + self.failUnless(t.tzinfo is b) + + def test_pickling(self): + import pickle, cPickle + + # Try one without a tzinfo. + 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',)) + derived = self.theclass(1, 1, 1) + derived.__setstate__(state) + self.assertEqual(orig, derived) + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + + # Try one with a tzinfo. + tinfo = FixedOffset(-300, 'cookie') + orig = self.theclass(*args, **{'tzinfo': tinfo}) + state = orig.__getstate__() + derived = self.theclass(1, 1, 1) + derived.__setstate__(state) + self.assertEqual(orig, derived) + self.failUnless(isinstance(derived.tzinfo, FixedOffset)) + self.assertEqual(derived.utcoffset(), -300) + self.assertEqual(derived.tzname(), 'cookie') + + for pickler in pickle, cPickle: + for binary in 0, 1: + green = pickler.dumps(orig, binary) + derived = pickler.loads(green) + self.assertEqual(orig, derived) + self.failUnless(isinstance(derived.tzinfo, FixedOffset)) + self.assertEqual(derived.utcoffset(), -300) + self.assertEqual(derived.tzname(), 'cookie') + + def test_extreme_hashes(self): + # If an attempt is made to hash these via subtracting the offset + # then hashing a datetime object, OverflowError results. The + # Python implementation used to blow up here. + t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) + hash(t) + t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, + tzinfo=FixedOffset(-1439, "")) + hash(t) + + # OTOH, an OOB offset should blow up. + t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, "")) + self.assertRaises(ValueError, hash, t) + + def test_zones(self): + 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) + 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), "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, ' + 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)") + + 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_tz_aware_arithmetic(self): + import random + + now = self.theclass.now() + tz55 = FixedOffset(-330, "west 5:30") + timeaware = timetz(now.hour, now.minute, now.second, + now.microsecond, tzinfo=tz55) + nowaware = self.theclass.combine(now.date(), timeaware) + self.failUnless(nowaware.tzinfo is tz55) + self.assertEqual(nowaware.timetz(), timeaware) + + # Can't mix aware and non-aware. + self.assertRaises(TypeError, lambda: now - nowaware) + self.assertRaises(TypeError, lambda: nowaware - now) + + # And adding datetimetz'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) + + # Subtracting should yield 0. + self.assertEqual(now - now, timedelta(0)) + self.assertEqual(nowaware - nowaware, timedelta(0)) + + # Adding a delta should preserve tzinfo. + delta = timedelta(weeks=1, minutes=12, microseconds=5678) + nowawareplus = nowaware + delta + self.failUnless(nowaware.tzinfo is tz55) + nowawareplus2 = delta + nowaware + self.failUnless(nowawareplus2.tzinfo is tz55) + self.assertEqual(nowawareplus, nowawareplus2) + + # that - delta should be what we started with, and that - what we + # started with should be delta. + diff = nowawareplus - delta + self.failUnless(diff.tzinfo is tz55) + self.assertEqual(nowaware, diff) + self.assertRaises(TypeError, lambda: delta - nowawareplus) + self.assertEqual(nowawareplus - nowaware, delta) + + # Make up a random timezone. + tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone") + # Attach it to nowawareplus -- this is clumsy. + nowawareplus = self.theclass.combine(nowawareplus.date(), + timetz(nowawareplus.hour, + nowawareplus.minute, + nowawareplus.second, + nowawareplus.microsecond, + tzinfo=tzr)) + self.failUnless(nowawareplus.tzinfo is tzr) + # Make sure the difference takes the timezone adjustments into account. + got = nowaware - nowawareplus + # Expected: (nowaware base - nowaware offset) - + # (nowawareplus base - nowawareplus offset) = + # (nowaware base - nowawareplus base) + + # (nowawareplus offset - nowaware offset) = + # -delta + nowawareplus offset - nowaware offset + expected = timedelta(minutes=nowawareplus.utcoffset() - + nowaware.utcoffset()) - delta + self.assertEqual(got, expected) + + # Try max possible difference. + min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min")) + max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, + tzinfo=FixedOffset(-1439, "max")) + maxdiff = max - min + self.assertEqual(maxdiff, self.theclass.max - self.theclass.min + + timedelta(minutes=2*1439)) + + def test_tzinfo_now(self): + meth = self.theclass.now + # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). + base = meth() + # Try with and without naming the keyword. + off42 = FixedOffset(42, "42") + another = meth(off42) + again = meth(tzinfo=off42) + self.failUnless(another.tzinfo is again.tzinfo) + self.assertEqual(another.utcoffset(), 42) + # Bad argument with and w/o naming the keyword. + self.assertRaises(TypeError, meth, 16) + self.assertRaises(TypeError, meth, tzinfo=16) + # Bad keyword name. + self.assertRaises(TypeError, meth, tinfo=off42) + # Too many args. + self.assertRaises(TypeError, meth, off42, off42) + + def test_tzinfo_fromtimestamp(self): + import time + meth = self.theclass.fromtimestamp + ts = time.time() + # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). + base = meth(ts) + # Try with and without naming the keyword. + off42 = FixedOffset(42, "42") + another = meth(ts, off42) + again = meth(ts, tzinfo=off42) + self.failUnless(another.tzinfo is again.tzinfo) + self.assertEqual(another.utcoffset(), 42) + # Bad argument with and w/o naming the keyword. + self.assertRaises(TypeError, meth, ts, 16) + self.assertRaises(TypeError, meth, ts, tzinfo=16) + # Bad keyword name. + self.assertRaises(TypeError, meth, ts, tinfo=off42) + # Too many args. + self.assertRaises(TypeError, meth, ts, off42, off42) + # Too few args. + self.assertRaises(TypeError, meth) + + def test_tzinfo_utcnow(self): + meth = self.theclass.utcnow + # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). + base = meth() + # Try with and without naming the keyword; for whatever reason, + # utcnow() doesn't accept a tzinfo argument. + off42 = FixedOffset(42, "42") + self.assertRaises(TypeError, meth, off42) + self.assertRaises(TypeError, meth, tzinfo=off42) + + def test_tzinfo_utcfromtimestamp(self): + import time + meth = self.theclass.utcfromtimestamp + ts = time.time() + # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). + base = meth(ts) + # Try with and without naming the keyword; for whatever reason, + # utcfromtimestamp() doesn't accept a tzinfo argument. + off42 = FixedOffset(42, "42") + self.assertRaises(TypeError, meth, ts, off42) + self.assertRaises(TypeError, meth, ts, tzinfo=off42) + + def test_tzinfo_timetuple(self): + # TestDateTime tested most of this. datetimetz adds a twist to the + # DST flag. + class DST(tzinfo): + def __init__(self, dstvalue): + self.dstvalue = dstvalue + def dst(self, dt): + return self.dstvalue + + cls = self.theclass + for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1): + d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue)) + t = d.timetuple() + self.assertEqual(1, t.tm_year) + self.assertEqual(1, t.tm_mon) + self.assertEqual(1, t.tm_mday) + self.assertEqual(10, t.tm_hour) + self.assertEqual(20, t.tm_min) + self.assertEqual(30, t.tm_sec) + self.assertEqual(0, t.tm_wday) + self.assertEqual(1, t.tm_yday) + self.assertEqual(flag, t.tm_isdst) + + # dst() returns wrong type. + self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple) + + # dst() at the edge. + self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1) + self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1) + + # dst() out of range. + self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple) + self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple) + + def test_utctimetuple(self): + class DST(tzinfo): + def __init__(self, dstvalue): + self.dstvalue = dstvalue + def dst(self, dt): + return self.dstvalue + + cls = self.theclass + # This can't work: DST didn't implement utcoffset. + self.assertRaises(NotImplementedError, + cls(1, 1, 1, tzinfo=DST(0)).utcoffset) + + class UOFS(DST): + def __init__(self, uofs, dofs=None): + DST.__init__(self, dofs) + self.uofs = uofs + def utcoffset(self, dt): + return self.uofs + + # Ensure tm_isdst is 0 regardless of what dst() says: DST is never + # in effect for a UTC time. + for dstvalue in -33, 33, 0, None: + d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue)) + t = d.utctimetuple() + self.assertEqual(d.year, t.tm_year) + self.assertEqual(d.month, t.tm_mon) + self.assertEqual(d.day, t.tm_mday) + self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm + self.assertEqual(13, t.tm_min) + self.assertEqual(d.second, t.tm_sec) + self.assertEqual(d.weekday(), t.tm_wday) + self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1, + t.tm_yday) + self.assertEqual(0, t.tm_isdst) + + # At the edges, UTC adjustment can normalize into years out-of-range + # for a datetime object. Ensure that a correct timetuple is + # created anyway. + tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439)) + # That goes back 1 minute less than a full day. + t = tiny.utctimetuple() + self.assertEqual(t.tm_year, MINYEAR-1) + self.assertEqual(t.tm_mon, 12) + self.assertEqual(t.tm_mday, 31) + self.assertEqual(t.tm_hour, 0) + self.assertEqual(t.tm_min, 1) + self.assertEqual(t.tm_sec, 37) + self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year + self.assertEqual(t.tm_isdst, 0) + + huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439)) + # That goes forward 1 minute less than a full day. + t = huge.utctimetuple() + self.assertEqual(t.tm_year, MAXYEAR+1) + self.assertEqual(t.tm_mon, 1) + self.assertEqual(t.tm_mday, 1) + self.assertEqual(t.tm_hour, 23) + self.assertEqual(t.tm_min, 58) + self.assertEqual(t.tm_sec, 37) + self.assertEqual(t.tm_yday, 1) + self.assertEqual(t.tm_isdst, 0) + + def test_tzinfo_isoformat(self): + zero = FixedOffset(0, "+00:00") + plus = FixedOffset(220, "+03:40") + minus = FixedOffset(-231, "-03:51") + unknown = FixedOffset(None, "") + + cls = self.theclass + datestr = '0001-02-03' + for ofs in None, zero, plus, minus, unknown: + for us in 0, 987001: + d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs) + timestr = '04:05:59' + (us and '.987001' or '') + ofsstr = ofs is not None and d.tzname() or '' + tailstr = timestr + ofsstr + iso = d.isoformat() + self.assertEqual(iso, datestr + 'T' + tailstr) + self.assertEqual(iso, d.isoformat('T')) + self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr) + self.assertEqual(str(d), datestr + ' ' + tailstr) + + +def suite(): + allsuites = [unittest.makeSuite(klass, 'test') + for klass in (TestModule, + TestTZInfo, + TestTimeDelta, + TestDateOnly, + TestDate, + TestDateTime, + TestTime, + TestTimeTZ, + TestDateTimeTZ, + ) + ] + return unittest.TestSuite(allsuites) + +def test_main(): + import gc + import sys + + thesuite = suite() + lastrc = None + while True: + test_support.run_suite(thesuite) + if 1: # change to 0, under a debug build, for some leak detection + break + gc.collect() + if gc.garbage: + raise SystemError("gc.garbage not empty after test run: %r" % + gc.garbage) + if hasattr(sys, 'gettotalrefcount'): + thisrc = sys.gettotalrefcount() + print >> sys.stderr, '*' * 10, 'total refs:', thisrc, + if lastrc: + print >> sys.stderr, 'delta:', thisrc - lastrc + else: + print >> sys.stderr + lastrc = thisrc + +if __name__ == "__main__": + test_main() diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c new file mode 100644 index 00000000000..58af972a654 --- /dev/null +++ b/Modules/datetimemodule.c @@ -0,0 +1,5075 @@ +/* C implementation for the date/time type documented at + * http://www.zope.org/Members/fdrake/DateTimeWiki/FrontPage + */ + +#include "Python.h" +#include "modsupport.h" +#include "structmember.h" + +#include + +#include "datetime.h" + +/* We require that C int be at least 32 bits, and use int virtually + * everywhere. In just a few cases we use a temp long, where a Python + * API returns a C long. In such cases, we have to ensure that the + * final result fits in a C int (this can be an issue on 64-bit boxes). + */ +#if SIZEOF_INT < 4 +# error "datetime.c requires that C int have at least 32 bits" +#endif + +#define MINYEAR 1 +#define MAXYEAR 9999 + +/* Nine decimal digits is easy to communicate, and leaves enough room + * so that two delta days can be added w/o fear of overflowing a signed + * 32-bit int, and with plenty of room left over to absorb any possible + * carries from adding seconds. + */ +#define MAX_DELTA_DAYS 999999999 + +/* Rename the long macros in datetime.h to more reasonable short names. */ +#define GET_YEAR PyDateTime_GET_YEAR +#define GET_MONTH PyDateTime_GET_MONTH +#define GET_DAY PyDateTime_GET_DAY +#define DATE_GET_HOUR PyDateTime_DATE_GET_HOUR +#define DATE_GET_MINUTE PyDateTime_DATE_GET_MINUTE +#define DATE_GET_SECOND PyDateTime_DATE_GET_SECOND +#define DATE_GET_MICROSECOND PyDateTime_DATE_GET_MICROSECOND + +/* Date accessors for date and datetime. */ +#define SET_YEAR(o, v) (((o)->data[0] = ((v) & 0xff00) >> 8), \ + ((o)->data[1] = ((v) & 0x00ff))) +#define SET_MONTH(o, v) (PyDateTime_GET_MONTH(o) = (v)) +#define SET_DAY(o, v) (PyDateTime_GET_DAY(o) = (v)) + +/* Date/Time accessors for datetime. */ +#define DATE_SET_HOUR(o, v) (PyDateTime_DATE_GET_HOUR(o) = (v)) +#define DATE_SET_MINUTE(o, v) (PyDateTime_DATE_GET_MINUTE(o) = (v)) +#define DATE_SET_SECOND(o, v) (PyDateTime_DATE_GET_SECOND(o) = (v)) +#define DATE_SET_MICROSECOND(o, v) \ + (((o)->data[7] = ((v) & 0xff0000) >> 16), \ + ((o)->data[8] = ((v) & 0x00ff00) >> 8), \ + ((o)->data[9] = ((v) & 0x0000ff))) + +/* Time accessors for time. */ +#define TIME_GET_HOUR PyDateTime_TIME_GET_HOUR +#define TIME_GET_MINUTE PyDateTime_TIME_GET_MINUTE +#define TIME_GET_SECOND PyDateTime_TIME_GET_SECOND +#define TIME_GET_MICROSECOND PyDateTime_TIME_GET_MICROSECOND +#define TIME_SET_HOUR(o, v) (PyDateTime_TIME_GET_HOUR(o) = (v)) +#define TIME_SET_MINUTE(o, v) (PyDateTime_TIME_GET_MINUTE(o) = (v)) +#define TIME_SET_SECOND(o, v) (PyDateTime_TIME_GET_SECOND(o) = (v)) +#define TIME_SET_MICROSECOND(o, v) \ + (((o)->data[3] = ((v) & 0xff0000) >> 16), \ + ((o)->data[4] = ((v) & 0x00ff00) >> 8), \ + ((o)->data[5] = ((v) & 0x0000ff))) + +/* Delta accessors for timedelta. */ +#define GET_TD_DAYS(o) (((PyDateTime_Delta *)(o))->days) +#define GET_TD_SECONDS(o) (((PyDateTime_Delta *)(o))->seconds) +#define GET_TD_MICROSECONDS(o) (((PyDateTime_Delta *)(o))->microseconds) + +#define SET_TD_DAYS(o, v) ((o)->days = (v)) +#define SET_TD_SECONDS(o, v) ((o)->seconds = (v)) +#define SET_TD_MICROSECONDS(o, v) ((o)->microseconds = (v)) + +/* Forward declarations. */ +static PyTypeObject PyDateTime_DateType; +static PyTypeObject PyDateTime_DateTimeType; +static PyTypeObject PyDateTime_DateTimeTZType; +static PyTypeObject PyDateTime_DeltaType; +static PyTypeObject PyDateTime_TimeType; +static PyTypeObject PyDateTime_TZInfoType; +static PyTypeObject PyDateTime_TimeTZType; + +/* --------------------------------------------------------------------------- + * Math utilities. + */ + +/* k = i+j overflows iff k differs in sign from both inputs, + * iff k^i has sign bit set and k^j has sign bit set, + * iff (k^i)&(k^j) has sign bit set. + */ +#define SIGNED_ADD_OVERFLOWED(RESULT, I, J) \ + ((((RESULT) ^ (I)) & ((RESULT) ^ (J))) < 0) + +/* Compute Python divmod(x, y), returning the quotient and storing the + * remainder into *r. The quotient is the floor of x/y, and that's + * the real point of this. C will probably truncate instead (C99 + * requires truncation; C89 left it implementation-defined). + * Simplification: we *require* that y > 0 here. That's appropriate + * for all the uses made of it. This simplifies the code and makes + * the overflow case impossible (divmod(LONG_MIN, -1) is the only + * overflow case). + */ +static int +divmod(int x, int y, int *r) +{ + int quo; + + assert(y > 0); + quo = x / y; + *r = x - quo * y; + if (*r < 0) { + --quo; + *r += y; + } + assert(0 <= *r && *r < y); + return quo; +} + +/* --------------------------------------------------------------------------- + * General calendrical helper functions + */ + +/* For each month ordinal in 1..12, the number of days in that month, + * and the number of days before that month in the same year. These + * are correct for non-leap years only. + */ +static int _days_in_month[] = { + 0, /* unused; this vector uses 1-based indexing */ + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static int _days_before_month[] = { + 0, /* unused; this vector uses 1-based indexing */ + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 +}; + +/* year -> 1 if leap year, else 0. */ +static int +is_leap(int year) +{ + /* Cast year to unsigned. The result is the same either way, but + * C can generate faster code for unsigned mod than for signed + * mod (especially for % 4 -- a good compiler should just grab + * the last 2 bits when the LHS is unsigned). + */ + const unsigned int ayear = (unsigned int)year; + return ayear % 4 == 0 && (ayear % 100 != 0 || ayear % 400 == 0); +} + +/* year, month -> number of days in that month in that year */ +static int +days_in_month(int year, int month) +{ + assert(month >= 1); + assert(month <= 12); + if (month == 2 && is_leap(year)) + return 29; + else + return _days_in_month[month]; +} + +/* year, month -> number of days in year preceeding first day of month */ +static int +days_before_month(int year, int month) +{ + int days; + + assert(month >= 1); + assert(month <= 12); + days = _days_before_month[month]; + if (month > 2 && is_leap(year)) + ++days; + return days; +} + +/* year -> number of days before January 1st of year. Remember that we + * start with year 1, so days_before_year(1) == 0. + */ +static int +days_before_year(int year) +{ + int y = year - 1; + /* This is incorrect if year <= 0; we really want the floor + * here. But so long as MINYEAR is 1, the smallest year this + * can see is 0 (this can happen in some normalization endcases), + * so we'll just special-case that. + */ + assert (year >= 0); + if (y >= 0) + return y*365 + y/4 - y/100 + y/400; + else { + assert(y == -1); + return -366; + } +} + +/* Number of days in 4, 100, and 400 year cycles. That these have + * the correct values is asserted in the module init function. + */ +#define DI4Y 1461 /* days_before_year(5); days in 4 years */ +#define DI100Y 36524 /* days_before_year(101); days in 100 years */ +#define DI400Y 146097 /* days_before_year(401); days in 400 years */ + +/* ordinal -> year, month, day, considering 01-Jan-0001 as day 1. */ +static void +ord_to_ymd(int ordinal, int *year, int *month, int *day) +{ + int n, n1, n4, n100, n400, leapyear, preceding; + + /* ordinal is a 1-based index, starting at 1-Jan-1. The pattern of + * leap years repeats exactly every 400 years. The basic strategy is + * to find the closest 400-year boundary at or before ordinal, then + * work with the offset from that boundary to ordinal. Life is much + * clearer if we subtract 1 from ordinal first -- then the values + * of ordinal at 400-year boundaries are exactly those divisible + * by DI400Y: + * + * D M Y n n-1 + * -- --- ---- ---------- ---------------- + * 31 Dec -400 -DI400Y -DI400Y -1 + * 1 Jan -399 -DI400Y +1 -DI400Y 400-year boundary + * ... + * 30 Dec 000 -1 -2 + * 31 Dec 000 0 -1 + * 1 Jan 001 1 0 400-year boundary + * 2 Jan 001 2 1 + * 3 Jan 001 3 2 + * ... + * 31 Dec 400 DI400Y DI400Y -1 + * 1 Jan 401 DI400Y +1 DI400Y 400-year boundary + */ + assert(ordinal >= 1); + --ordinal; + n400 = ordinal / DI400Y; + n = ordinal % DI400Y; + *year = n400 * 400 + 1; + + /* Now n is the (non-negative) offset, in days, from January 1 of + * year, to the desired date. Now compute how many 100-year cycles + * precede n. + * Note that it's possible for n100 to equal 4! In that case 4 full + * 100-year cycles precede the desired day, which implies the + * desired day is December 31 at the end of a 400-year cycle. + */ + n100 = n / DI100Y; + n = n % DI100Y; + + /* Now compute how many 4-year cycles precede it. */ + n4 = n / DI4Y; + n = n % DI4Y; + + /* And now how many single years. Again n1 can be 4, and again + * meaning that the desired day is December 31 at the end of the + * 4-year cycle. + */ + n1 = n / 365; + n = n % 365; + + *year += n100 * 100 + n4 * 4 + n1; + if (n1 == 4 || n100 == 4) { + assert(n == 0); + *year -= 1; + *month = 12; + *day = 31; + return; + } + + /* Now the year is correct, and n is the offset from January 1. We + * find the month via an estimate that's either exact or one too + * large. + */ + leapyear = n1 == 3 && (n4 != 24 || n100 == 3); + assert(leapyear == is_leap(*year)); + *month = (n + 50) >> 5; + preceding = (_days_before_month[*month] + (*month > 2 && leapyear)); + if (preceding > n) { + /* estimate is too large */ + *month -= 1; + preceding -= days_in_month(*year, *month); + } + n -= preceding; + assert(0 <= n); + assert(n < days_in_month(*year, *month)); + + *day = n + 1; +} + +/* year, month, day -> ordinal, considering 01-Jan-0001 as day 1. */ +static int +ymd_to_ord(int year, int month, int day) +{ + return days_before_year(year) + days_before_month(year, month) + day; +} + +/* Day of week, where Monday==0, ..., Sunday==6. 1/1/1 was a Monday. */ +static int +weekday(int year, int month, int day) +{ + return (ymd_to_ord(year, month, day) + 6) % 7; +} + +/* Ordinal of the Monday starting week 1 of the ISO year. Week 1 is the + * first calendar week containing a Thursday. + */ +static int +iso_week1_monday(int year) +{ + int first_day = ymd_to_ord(year, 1, 1); /* ord of 1/1 */ + /* 0 if 1/1 is a Monday, 1 if a Tue, etc. */ + int first_weekday = (first_day + 6) % 7; + /* ordinal of closest Monday at or before 1/1 */ + int week1_monday = first_day - first_weekday; + + if (first_weekday > 3) /* if 1/1 was Fri, Sat, Sun */ + week1_monday += 7; + return week1_monday; +} + +/* --------------------------------------------------------------------------- + * Range checkers. + */ + +/* Check that -MAX_DELTA_DAYS <= days <= MAX_DELTA_DAYS. If so, return 0. + * If not, raise OverflowError and return -1. + */ +static int +check_delta_day_range(int days) +{ + if (-MAX_DELTA_DAYS <= days && days <= MAX_DELTA_DAYS) + return 0; + PyErr_Format(PyExc_OverflowError, + "days=%d; must have magnitude <= %d", + days); + return -1; +} + +/* Check that date arguments are in range. Return 0 if they are. If they + * aren't, raise ValueError and return -1. + */ +static int +check_date_args(int year, int month, int day) +{ + + if (year < MINYEAR || year > MAXYEAR) { + PyErr_SetString(PyExc_ValueError, + "year is out of range"); + return -1; + } + if (month < 1 || month > 12) { + PyErr_SetString(PyExc_ValueError, + "month must be in 1..12"); + return -1; + } + if (day < 1 || day > days_in_month(year, month)) { + PyErr_SetString(PyExc_ValueError, + "day is out of range for month"); + return -1; + } + return 0; +} + +/* Check that time arguments are in range. Return 0 if they are. If they + * aren't, raise ValueError and return -1. + */ +static int +check_time_args(int h, int m, int s, int us) +{ + if (h < 0 || h > 23) { + PyErr_SetString(PyExc_ValueError, + "hour must be in 0..23"); + return -1; + } + if (m < 0 || m > 59) { + PyErr_SetString(PyExc_ValueError, + "minute must be in 0..59"); + return -1; + } + if (s < 0 || s > 59) { + PyErr_SetString(PyExc_ValueError, + "second must be in 0..59"); + return -1; + } + if (us < 0 || us > 999999) { + PyErr_SetString(PyExc_ValueError, + "microsecond must be in 0..999999"); + return -1; + } + return 0; +} + +/* --------------------------------------------------------------------------- + * Normalization utilities. + */ + +/* One step of a mixed-radix conversion. A "hi" unit is equivalent to + * factor "lo" units. factor must be > 0. If *lo is less than 0, or + * at least factor, enough of *lo is converted into "hi" units so that + * 0 <= *lo < factor. The input values must be such that int overflow + * is impossible. + */ +static void +normalize_pair(int *hi, int *lo, int factor) +{ + assert(factor > 0); + assert(lo != hi); + if (*lo < 0 || *lo >= factor) { + const int num_hi = divmod(*lo, factor, lo); + const int new_hi = *hi + num_hi; + assert(! SIGNED_ADD_OVERFLOWED(new_hi, *hi, num_hi)); + *hi = new_hi; + } + assert(0 <= *lo && *lo < factor); +} + +/* Fiddle days (d), seconds (s), and microseconds (us) so that + * 0 <= *s < 24*3600 + * 0 <= *us < 1000000 + * The input values must be such that the internals don't overflow. + * The way this routine is used, we don't get close. + */ +static void +normalize_d_s_us(int *d, int *s, int *us) +{ + if (*us < 0 || *us >= 1000000) { + normalize_pair(s, us, 1000000); + /* |s| can't be bigger than about + * |original s| + |original us|/1000000 now. + */ + + } + if (*s < 0 || *s >= 24*3600) { + normalize_pair(d, s, 24*3600); + /* |d| can't be bigger than about + * |original d| + + * (|original s| + |original us|/1000000) / (24*3600) now. + */ + } + assert(0 <= *s && *s < 24*3600); + assert(0 <= *us && *us < 1000000); +} + +/* Fiddle years (y), months (m), and days (d) so that + * 1 <= *m <= 12 + * 1 <= *d <= days_in_month(*y, *m) + * The input values must be such that the internals don't overflow. + * The way this routine is used, we don't get close. + */ +static void +normalize_y_m_d(int *y, int *m, int *d) +{ + int dim; /* # of days in month */ + + /* This gets muddy: the proper range for day can't be determined + * without knowing the correct month and year, but if day is, e.g., + * plus or minus a million, the current month and year values make + * no sense (and may also be out of bounds themselves). + * Saying 12 months == 1 year should be non-controversial. + */ + if (*m < 1 || *m > 12) { + --*m; + normalize_pair(y, m, 12); + ++*m; + /* |y| can't be bigger than about + * |original y| + |original m|/12 now. + */ + } + assert(1 <= *m && *m <= 12); + + /* Now only day can be out of bounds (year may also be out of bounds + * for a datetime object, but we don't care about that here). + * If day is out of bounds, what to do is arguable, but at least the + * method here is principled and explainable. + */ + dim = days_in_month(*y, *m); + if (*d < 1 || *d > dim) { + /* Move day-1 days from the first of the month. First try to + * get off cheap if we're only one day out of range + * (adjustments for timezone alone can't be worse than that). + */ + if (*d == 0) { + --*m; + if (*m > 0) + *d = days_in_month(*y, *m); + else { + --*y; + *m = 12; + *d = 31; + } + } + else if (*d == dim + 1) { + /* move forward a day */ + ++*m; + *d = 1; + if (*m > 12) { + *m = 1; + ++*y; + } + } + else { + int ordinal = ymd_to_ord(*y, *m, 1) + + *d - 1; + ord_to_ymd(ordinal, y, m, d); + } + } + assert(*m > 0); + assert(*d > 0); +} + +/* Fiddle out-of-bounds months and days so that the result makes some kind + * of sense. The parameters are both inputs and outputs. Returns < 0 on + * failure, where failure means the adjusted year is out of bounds. + */ +static int +normalize_date(int *year, int *month, int *day) +{ + int result; + + normalize_y_m_d(year, month, day); + if (MINYEAR <= *year && *year <= MAXYEAR) + result = 0; + else { + PyErr_SetString(PyExc_OverflowError, + "date value out of range"); + result = -1; + } + return result; +} + +/* Force all the datetime fields into range. The parameters are both + * inputs and outputs. Returns < 0 on error. + */ +static int +normalize_datetime(int *year, int *month, int *day, + int *hour, int *minute, int *second, + int *microsecond) +{ + normalize_pair(second, microsecond, 1000000); + normalize_pair(minute, second, 60); + normalize_pair(hour, minute, 60); + normalize_pair(day, hour, 24); + return normalize_date(year, month, day); +} + +/* --------------------------------------------------------------------------- + * tzinfo helpers. + */ + +/* If self has a tzinfo member, return a BORROWED reference to it. Else + * return NULL, which is NOT AN ERROR. There are no error returns here, + * and the caller must not decref the result. + */ +static PyObject * +get_tzinfo_member(PyObject *self) +{ + PyObject *tzinfo = NULL; + + if (PyDateTimeTZ_Check(self)) + tzinfo = ((PyDateTime_DateTimeTZ *)self)->tzinfo; + else if (PyTimeTZ_Check(self)) + tzinfo = ((PyDateTime_TimeTZ *)self)->tzinfo; + + return tzinfo; +} + +/* Ensure that p is None or of a tzinfo subclass. Return 0 if OK; if not + * raise TypeError and return -1. + */ +static int +check_tzinfo_subclass(PyObject *p) +{ + if (p == Py_None || PyTZInfo_Check(p)) + return 0; + PyErr_Format(PyExc_TypeError, + "tzinfo argument must be None or of a tzinfo subclass, " + "not type '%s'", + p->ob_type->tp_name); + return -1; +} + +/* Internal helper. + * Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the + * result. tzinfo must be an instance of the tzinfo class. If the method + * returns None, this returns 0 and sets *none to 1. If the method doesn't + * return a Python int or long, TypeError is raised and this returns -1. + * If it does return an int or long, but is outside the valid range for + * a UTC minute offset, ValueError is raised and this returns -1. + * Else *none is set to 0 and the integer method result is returned. + */ +static int +call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg, + int *none) +{ + PyObject *u; + long result = -1; /* Py{Int,Long}_AsLong return long */ + + assert(tzinfo != NULL); + assert(PyTZInfo_Check(tzinfo)); + assert(tzinfoarg != NULL); + + *none = 0; + u = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg); + if (u == NULL) + return -1; + + if (u == Py_None) { + result = 0; + *none = 1; + goto Done; + } + + if (PyInt_Check(u)) + result = PyInt_AS_LONG(u); + else if (PyLong_Check(u)) + result = PyLong_AsLong(u); + else { + PyErr_Format(PyExc_TypeError, + "tzinfo.%s() must return None or int or long", + name); + goto Done; + } + +Done: + Py_DECREF(u); + if (result < -1439 || result > 1439) { + PyErr_Format(PyExc_ValueError, + "tzinfo.%s() returned %ld; must be in " + "-1439 .. 1439", + name, result); + result = -1; + } + return (int)result; +} + +/* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the + * result. tzinfo must be an instance of the tzinfo class. If utcoffset() + * returns None, call_utcoffset returns 0 and sets *none to 1. If uctoffset() + & doesn't return a Python int or long, TypeError is raised and this + * returns -1. If utcoffset() returns an int outside the legitimate range + * for a UTC offset, ValueError is raised and this returns -1. Else + * *none is set to 0 and the offset is returned. + */ +static int +call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none) +{ + return call_utc_tzinfo_method(tzinfo, "utcoffset", tzinfoarg, none); +} + +/* Call tzinfo.dst(tzinfoarg), and extract an integer from the + * result. tzinfo must be an instance of the tzinfo class. If dst() + * returns None, call_dst returns 0 and sets *none to 1. If dst() + & doesn't return a Python int or long, TypeError is raised and this + * returns -1. If dst() returns an int outside the legitimate range + * for a UTC offset, ValueError is raised and this returns -1. Else + * *none is set to 0 and the offset is returned. + */ +static int +call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none) +{ + return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none); +} + +/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be + * an instance of the tzinfo class. If tzname() doesn't return None or + * a string, TypeError is raised and this returns NULL. + */ +static PyObject * +call_tzname(PyObject *tzinfo, PyObject *tzinfoarg) +{ + PyObject *result; + + assert(tzinfo != NULL); + assert(PyTZInfo_Check(tzinfo)); + assert(tzinfoarg != NULL); + + result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg); + if (result != NULL && result != Py_None && !PyString_Check(result)) { + PyErr_Format(PyExc_TypeError, ".tzinfo.tzname() must " + "return None or a string, not '%s'", + result->ob_type->tp_name); + Py_DECREF(result); + result = NULL; + } + return result; +} + +typedef enum { + /* an exception has been set; the caller should pass it on */ + OFFSET_ERROR, + + /* type isn't date, datetime, datetimetz subclass, time, or + * timetz subclass + */ + OFFSET_UNKNOWN, + + /* date, + * datetime, + * datetimetz with None tzinfo, + * datetimetz where utcoffset() return None + * time, + * timetz with None tzinfo, + * timetz where utcoffset() returns None + */ + OFFSET_NAIVE, + + /* timetz where utcoffset() doesn't return None, + * datetimetz where utcoffset() doesn't return None + */ + OFFSET_AWARE, +} naivety; + +/* Classify a datetime object as to whether it's naive or offset-aware. See + * the "naivety" typedef for details. If the type is aware, *offset is set + * to minutes east of UTC (as returned by the tzinfo.utcoffset() method). + * If the type is offset-naive, *offset is set to 0. + */ +static naivety +classify_object(PyObject *op, int *offset) +{ + int none; + PyObject *tzinfo; + + *offset = 0; + if (PyDateTime_CheckExact(op) || + PyTime_CheckExact(op) || + PyDate_CheckExact(op)) + return OFFSET_NAIVE; + + tzinfo = get_tzinfo_member(op); /* NULL means none, not error */ + if (tzinfo == Py_None) + return OFFSET_NAIVE; + if (tzinfo == NULL) + return OFFSET_UNKNOWN; + + *offset = call_utcoffset(tzinfo, op, &none); + if (*offset == -1 && PyErr_Occurred()) + return OFFSET_ERROR; + return none ? OFFSET_NAIVE : OFFSET_AWARE; +} + +/* repr is like "someclass(arg1, arg2)". If tzinfo isn't None, + * stuff + * ", tzinfo=" + repr(tzinfo) + * before the closing ")". + */ +static PyObject * +append_keyword_tzinfo(PyObject *repr, PyObject *tzinfo) +{ + PyObject *temp; + + assert(PyString_Check(repr)); + assert(tzinfo); + if (tzinfo == Py_None) + return repr; + /* Get rid of the trailing ')'. */ + assert(PyString_AsString(repr)[PyString_Size(repr)-1] == ')'); + temp = PyString_FromStringAndSize(PyString_AsString(repr), + PyString_Size(repr) - 1); + Py_DECREF(repr); + if (temp == NULL) + return NULL; + repr = temp; + + /* Append ", tzinfo=". */ + PyString_ConcatAndDel(&repr, PyString_FromString(", tzinfo=")); + + /* Append repr(tzinfo). */ + PyString_ConcatAndDel(&repr, PyObject_Repr(tzinfo)); + + /* Add a closing paren. */ + PyString_ConcatAndDel(&repr, PyString_FromString(")")); + return repr; +} + +/* --------------------------------------------------------------------------- + * String format helpers. + */ + +static PyObject * +format_ctime(PyDateTime_Date *date, + int hours, int minutes, int seconds) +{ + static char *DayNames[] = { + "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" + }; + static char *MonthNames[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + char buffer[128]; + int wday = weekday(GET_YEAR(date), GET_MONTH(date), GET_DAY(date)); + + PyOS_snprintf(buffer, sizeof(buffer), "%s %s %2d %02d:%02d:%02d %04d", + DayNames[wday], MonthNames[GET_MONTH(date) - 1], + GET_DAY(date), hours, minutes, seconds, + GET_YEAR(date)); + return PyString_FromString(buffer); +} + +/* Add an hours & minutes UTC offset string to buf. buf has no more than + * buflen bytes remaining. The UTC offset is gotten by calling + * tzinfo.uctoffset(tzinfoarg). If that returns None, \0 is stored into + * *buf, and that's all. Else the returned value is checked for sanity (an + * integer in range), and if that's OK it's converted to an hours & minutes + * string of the form + * sign HH sep MM + * Returns 0 if everything is OK. If the return value from utcoffset() is + * bogus, an appropriate exception is set and -1 is returned. + */ +static int +format_utcoffset(char *buf, int buflen, const char *sep, + PyObject *tzinfo, PyObject *tzinfoarg) +{ + int offset; + int hours; + int minutes; + char sign; + int none; + + offset = call_utcoffset(tzinfo, tzinfoarg, &none); + if (offset == -1 && PyErr_Occurred()) + return -1; + if (none) { + *buf = '\0'; + return 0; + } + sign = '+'; + if (offset < 0) { + sign = '-'; + offset = - offset; + } + hours = divmod(offset, 60, &minutes); + PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes); + return 0; +} + +/* I sure don't want to reproduce the strftime code from the time module, + * so this imports the module and calls it. All the hair is due to + * giving special meanings to the %z and %Z format codes via a preprocessing + * step on the format string. + */ +static PyObject * +wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple) +{ + PyObject *result = NULL; /* guilty until proved innocent */ + + PyObject *zreplacement = NULL; /* py string, replacement for %z */ + PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ + + char *pin; /* pointer to next char in input format */ + char ch; /* next char in input format */ + + PyObject *newfmt = NULL; /* py string, the output format */ + char *pnew; /* pointer to available byte in output format */ + char totalnew; /* number bytes total in output format buffer, + exclusive of trailing \0 */ + char usednew; /* number bytes used so far in output format buffer */ + + char *ptoappend; /* pointer to string to append to output buffer */ + int ntoappend; /* # of bytes to append to output buffer */ + + char buf[100]; /* scratch buffer */ + + assert(object && format && timetuple); + assert(PyString_Check(format)); + + /* Scan the input format, looking for %z and %Z escapes, building + * a new format. + */ + totalnew = PyString_Size(format); /* realistic if no %z/%Z */ + newfmt = PyString_FromStringAndSize(NULL, totalnew); + if (newfmt == NULL) goto Done; + pnew = PyString_AsString(newfmt); + usednew = 0; + + pin = PyString_AsString(format); + while ((ch = *pin++) != '\0') { + if (ch != '%') { + buf[0] = ch; + ptoappend = buf; + ntoappend = 1; + } + else if ((ch = *pin++) == '\0') { + /* There's a lone trailing %; doesn't make sense. */ + PyErr_SetString(PyExc_ValueError, "strftime format " + "ends with raw %"); + goto Done; + } + /* A % has been seen and ch is the character after it. */ + else if (ch == 'z') { + if (zreplacement == NULL) { + /* format utcoffset */ + PyObject *tzinfo = get_tzinfo_member(object); + zreplacement = PyString_FromString(""); + if (zreplacement == NULL) goto Done; + if (tzinfo != Py_None && tzinfo != NULL) { + if (format_utcoffset(buf, + (int)sizeof(buf), + "", + tzinfo, + object) < 0) + goto Done; + Py_DECREF(zreplacement); + zreplacement = PyString_FromString(buf); + if (zreplacement == NULL) goto Done; + } + } + assert(zreplacement != NULL); + ptoappend = PyString_AsString(zreplacement); + ntoappend = PyString_Size(zreplacement); + } + else if (ch == 'Z') { + /* format tzname */ + if (Zreplacement == NULL) { + PyObject *tzinfo = get_tzinfo_member(object); + Zreplacement = PyString_FromString(""); + if (Zreplacement == NULL) goto Done; + if (tzinfo != Py_None && tzinfo != NULL) { + PyObject *temp = call_tzname(tzinfo, + object); + if (temp == NULL) goto Done; + if (temp != Py_None) { + assert(PyString_Check(temp)); + /* Since the tzname is getting + * stuffed into the format, we + * have to double any % signs + * so that strftime doesn't + * treat them as format codes. + */ + Py_DECREF(Zreplacement); + Zreplacement = PyObject_CallMethod( + temp, "replace", + "ss", "%", "%%"); + Py_DECREF(temp); + if (Zreplacement == NULL) + goto Done; + } + else + Py_DECREF(temp); + } + } + assert(Zreplacement != NULL); + ptoappend = PyString_AsString(Zreplacement); + ntoappend = PyString_Size(Zreplacement); + } + else { + buf[0] = '%'; + buf[1] = ch; + ptoappend = buf; + ntoappend = 2; + } + + /* Append the ntoappend chars starting at ptoappend to + * the new format. + */ + assert(ntoappend >= 0); + if (ntoappend == 0) + continue; + while (usednew + ntoappend > totalnew) { + int bigger = totalnew << 1; + if ((bigger >> 1) != totalnew) { /* overflow */ + PyErr_NoMemory(); + goto Done; + } + if (_PyString_Resize(&newfmt, bigger) < 0) + goto Done; + totalnew = bigger; + pnew = PyString_AsString(newfmt) + usednew; + } + memcpy(pnew, ptoappend, ntoappend); + pnew += ntoappend; + usednew += ntoappend; + assert(usednew <= totalnew); + } /* end while() */ + + if (_PyString_Resize(&newfmt, usednew) < 0) + goto Done; + { + PyObject *time = PyImport_ImportModule("time"); + if (time == NULL) + goto Done; + result = PyObject_CallMethod(time, "strftime", "OO", + newfmt, timetuple); + Py_DECREF(time); + } + Done: + Py_XDECREF(zreplacement); + Py_XDECREF(Zreplacement); + Py_XDECREF(newfmt); + return result; +} + +static char * +isoformat_date(PyDateTime_Date *dt, char buffer[], int bufflen) +{ + int x; + x = PyOS_snprintf(buffer, bufflen, + "%04d-%02d-%02d", + GET_YEAR(dt), GET_MONTH(dt), GET_DAY(dt)); + return buffer + x; +} + +static void +isoformat_time(PyDateTime_DateTime *dt, char buffer[], int bufflen) +{ + int us = DATE_GET_MICROSECOND(dt); + + PyOS_snprintf(buffer, bufflen, + "%02d:%02d:%02d", /* 8 characters */ + DATE_GET_HOUR(dt), + DATE_GET_MINUTE(dt), + DATE_GET_SECOND(dt)); + if (us) + PyOS_snprintf(buffer + 8, bufflen - 8, ".%06d", us); +} + +/* --------------------------------------------------------------------------- + * Wrap functions from the time module. These aren't directly available + * from C. Perhaps they should be. + */ + +/* Call time.time() and return its result (a Python float). */ +static PyObject * +time_time() +{ + PyObject *result = NULL; + PyObject *time = PyImport_ImportModule("time"); + + if (time != NULL) { + result = PyObject_CallMethod(time, "time", "()"); + Py_DECREF(time); + } + return result; +} + +/* Build a time.struct_time. The weekday and day number are automatically + * computed from the y,m,d args. + */ +static PyObject * +build_struct_time(int y, int m, int d, int hh, int mm, int ss, int dstflag) +{ + PyObject *time; + PyObject *result = NULL; + + time = PyImport_ImportModule("time"); + if (time != NULL) { + result = PyObject_CallMethod(time, "struct_time", + "((iiiiiiiii))", + y, m, d, + hh, mm, ss, + weekday(y, m, d), + days_before_month(y, m) + d, + dstflag); + Py_DECREF(time); + } + return result; +} + +/* --------------------------------------------------------------------------- + * Miscellaneous helpers. + */ + +/* For obscure reasons, we need to use tp_richcompare instead of tp_compare. + * The comparisons here all most naturally compute a cmp()-like result. + * This little helper turns that into a bool result for rich comparisons. + */ +static PyObject * +diff_to_bool(int diff, int op) +{ + PyObject *result; + int istrue; + + switch (op) { + case Py_EQ: istrue = diff == 0; break; + case Py_NE: istrue = diff != 0; break; + case Py_LE: istrue = diff <= 0; break; + case Py_GE: istrue = diff >= 0; break; + case Py_LT: istrue = diff < 0; break; + case Py_GT: istrue = diff > 0; break; + default: + assert(! "op unknown"); + istrue = 0; /* To shut up compiler */ + } + result = istrue ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +/* --------------------------------------------------------------------------- + * Helpers for setting object fields. These work on pointers to the + * appropriate base class. + */ + +/* For date, datetime and datetimetz. */ +static void +set_date_fields(PyDateTime_Date *self, int y, int m, int d) +{ + self->hashcode = -1; + SET_YEAR(self, y); + SET_MONTH(self, m); + SET_DAY(self, d); +} + +/* For datetime and datetimetz. */ +static void +set_datetime_time_fields(PyDateTime_Date *self, int h, int m, int s, int us) +{ + DATE_SET_HOUR(self, h); + DATE_SET_MINUTE(self, m); + DATE_SET_SECOND(self, s); + DATE_SET_MICROSECOND(self, us); +} + +/* For time and timetz. */ +static void +set_time_fields(PyDateTime_Time *self, int h, int m, int s, int us) +{ + self->hashcode = -1; + TIME_SET_HOUR(self, h); + TIME_SET_MINUTE(self, m); + TIME_SET_SECOND(self, s); + TIME_SET_MICROSECOND(self, us); +} + +/* --------------------------------------------------------------------------- + * Create various objects, mostly without range checking. + */ + +/* Create a date instance with no range checking. */ +static PyObject * +new_date(int year, int month, int day) +{ + PyDateTime_Date *self; + + self = PyObject_New(PyDateTime_Date, &PyDateTime_DateType); + if (self != NULL) + set_date_fields(self, year, month, day); + return (PyObject *) self; +} + +/* Create a datetime instance with no range checking. */ +static PyObject * +new_datetime(int year, int month, int day, int hour, int minute, + int second, int usecond) +{ + PyDateTime_DateTime *self; + + self = PyObject_New(PyDateTime_DateTime, &PyDateTime_DateTimeType); + if (self != NULL) { + set_date_fields((PyDateTime_Date *)self, year, month, day); + set_datetime_time_fields((PyDateTime_Date *)self, + hour, minute, second, usecond); + } + return (PyObject *) self; +} + +/* Create a datetimetz instance with no range checking. */ +static PyObject * +new_datetimetz(int year, int month, int day, int hour, int minute, + int second, int usecond, PyObject *tzinfo) +{ + PyDateTime_DateTimeTZ *self; + + self = PyObject_New(PyDateTime_DateTimeTZ, &PyDateTime_DateTimeTZType); + if (self != NULL) { + set_date_fields((PyDateTime_Date *)self, year, month, day); + set_datetime_time_fields((PyDateTime_Date *)self, + hour, minute, second, usecond); + Py_INCREF(tzinfo); + self->tzinfo = tzinfo; + } + return (PyObject *) self; +} + +/* Create a time instance with no range checking. */ +static PyObject * +new_time(int hour, int minute, int second, int usecond) +{ + PyDateTime_Time *self; + + self = PyObject_New(PyDateTime_Time, &PyDateTime_TimeType); + if (self != NULL) + set_time_fields(self, hour, minute, second, usecond); + return (PyObject *) self; +} + +/* Create a timetz instance with no range checking. */ +static PyObject * +new_timetz(int hour, int minute, int second, int usecond, PyObject *tzinfo) +{ + PyDateTime_TimeTZ *self; + + self = PyObject_New(PyDateTime_TimeTZ, &PyDateTime_TimeTZType); + if (self != NULL) { + set_time_fields((PyDateTime_Time *)self, + hour, minute, second, usecond); + Py_INCREF(tzinfo); + self->tzinfo = tzinfo; + } + return (PyObject *) self; +} + +/* Create a timedelta instance. Normalize the members iff normalize is + * true. Passing false is a speed optimization, if you know for sure + * that seconds and microseconds are already in their proper ranges. In any + * case, raises OverflowError and returns NULL if the normalized days is out + * of range). + */ +static PyObject * +new_delta(int days, int seconds, int microseconds, int normalize) +{ + PyDateTime_Delta *self; + + if (normalize) + normalize_d_s_us(&days, &seconds, µseconds); + assert(0 <= seconds && seconds < 24*3600); + assert(0 <= microseconds && microseconds < 1000000); + + if (check_delta_day_range(days) < 0) + return NULL; + + self = PyObject_New(PyDateTime_Delta, &PyDateTime_DeltaType); + if (self != NULL) { + self->hashcode = -1; + SET_TD_DAYS(self, days); + SET_TD_SECONDS(self, seconds); + SET_TD_MICROSECONDS(self, microseconds); + } + return (PyObject *) self; +} + + +/* --------------------------------------------------------------------------- + * Cached Python objects; these are set by the module init function. + */ + +/* Conversion factors. */ +static PyObject *us_per_us = NULL; /* 1 */ +static PyObject *us_per_ms = NULL; /* 1000 */ +static PyObject *us_per_second = NULL; /* 1000000 */ +static PyObject *us_per_minute = NULL; /* 1e6 * 60 as Python int */ +static PyObject *us_per_hour = NULL; /* 1e6 * 3600 as Python long */ +static PyObject *us_per_day = NULL; /* 1e6 * 3600 * 24 as Python long */ +static PyObject *us_per_week = NULL; /* 1e6*3600*24*7 as Python long */ +static PyObject *seconds_per_day = NULL; /* 3600*24 as Python int */ + +/* Callables to support unpickling. */ +static PyObject *date_unpickler_object = NULL; +static PyObject *datetime_unpickler_object = NULL; +static PyObject *datetimetz_unpickler_object = NULL; +static PyObject *tzinfo_unpickler_object = NULL; +static PyObject *time_unpickler_object = NULL; +static PyObject *timetz_unpickler_object = NULL; + +/* --------------------------------------------------------------------------- + * Class implementations. + */ + +/* + * PyDateTime_Delta implementation. + */ + +/* Convert a timedelta to a number of us, + * (24*3600*self.days + self.seconds)*1000000 + self.microseconds + * as a Python int or long. + * Doing mixed-radix arithmetic by hand instead is excruciating in C, + * due to ubiquitous overflow possibilities. + */ +static PyObject * +delta_to_microseconds(PyDateTime_Delta *self) +{ + PyObject *x1 = NULL; + PyObject *x2 = NULL; + PyObject *x3 = NULL; + PyObject *result = NULL; + + x1 = PyInt_FromLong(GET_TD_DAYS(self)); + if (x1 == NULL) + goto Done; + x2 = PyNumber_Multiply(x1, seconds_per_day); /* days in seconds */ + if (x2 == NULL) + goto Done; + Py_DECREF(x1); + x1 = NULL; + + /* x2 has days in seconds */ + x1 = PyInt_FromLong(GET_TD_SECONDS(self)); /* seconds */ + if (x1 == NULL) + goto Done; + x3 = PyNumber_Add(x1, x2); /* days and seconds in seconds */ + if (x3 == NULL) + goto Done; + Py_DECREF(x1); + Py_DECREF(x2); + x1 = x2 = NULL; + + /* x3 has days+seconds in seconds */ + x1 = PyNumber_Multiply(x3, us_per_second); /* us */ + if (x1 == NULL) + goto Done; + Py_DECREF(x3); + x3 = NULL; + + /* x1 has days+seconds in us */ + x2 = PyInt_FromLong(GET_TD_MICROSECONDS(self)); + if (x2 == NULL) + goto Done; + result = PyNumber_Add(x1, x2); + +Done: + Py_XDECREF(x1); + Py_XDECREF(x2); + Py_XDECREF(x3); + return result; +} + +/* Convert a number of us (as a Python int or long) to a timedelta. + */ +static PyObject * +microseconds_to_delta(PyObject *pyus) +{ + int us; + int s; + int d; + + PyObject *tuple = NULL; + PyObject *num = NULL; + PyObject *result = NULL; + + tuple = PyNumber_Divmod(pyus, us_per_second); + if (tuple == NULL) + goto Done; + + num = PyTuple_GetItem(tuple, 1); /* us */ + if (num == NULL) + goto Done; + us = PyLong_AsLong(num); + num = NULL; + if (us < 0) { + /* The divisor was positive, so this must be an error. */ + assert(PyErr_Occurred()); + goto Done; + } + + num = PyTuple_GetItem(tuple, 0); /* leftover seconds */ + if (num == NULL) + goto Done; + Py_INCREF(num); + Py_DECREF(tuple); + + tuple = PyNumber_Divmod(num, seconds_per_day); + if (tuple == NULL) + goto Done; + Py_DECREF(num); + + num = PyTuple_GetItem(tuple, 1); /* seconds */ + if (num == NULL) + goto Done; + s = PyLong_AsLong(num); + num = NULL; + if (s < 0) { + /* The divisor was positive, so this must be an error. */ + assert(PyErr_Occurred()); + goto Done; + } + + num = PyTuple_GetItem(tuple, 0); /* leftover days */ + if (num == NULL) + goto Done; + Py_INCREF(num); + + d = PyLong_AsLong(num); + if (d == -1 && PyErr_Occurred()) + goto Done; + result = new_delta(d, s, us, 0); + +Done: + Py_XDECREF(tuple); + Py_XDECREF(num); + return result; +} + +static PyObject * +multiply_int_timedelta(PyObject *intobj, PyDateTime_Delta *delta) +{ + PyObject *pyus_in; + PyObject *pyus_out; + PyObject *result; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + + pyus_out = PyNumber_Multiply(pyus_in, intobj); + Py_DECREF(pyus_in); + if (pyus_out == NULL) + return NULL; + + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + return result; +} + +static PyObject * +divide_timedelta_int(PyDateTime_Delta *delta, PyObject *intobj) +{ + PyObject *pyus_in; + PyObject *pyus_out; + PyObject *result; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + + pyus_out = PyNumber_FloorDivide(pyus_in, intobj); + Py_DECREF(pyus_in); + if (pyus_out == NULL) + return NULL; + + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + return result; +} + +static PyObject * +delta_add(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left) && PyDelta_Check(right)) { + /* delta + delta */ + /* The C-level additions can't overflow because of the + * invariant bounds. + */ + int days = GET_TD_DAYS(left) + GET_TD_DAYS(right); + int seconds = GET_TD_SECONDS(left) + GET_TD_SECONDS(right); + int microseconds = GET_TD_MICROSECONDS(left) + + GET_TD_MICROSECONDS(right); + result = new_delta(days, seconds, microseconds, 1); + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +static PyObject * +delta_negative(PyDateTime_Delta *self) +{ + return new_delta(-GET_TD_DAYS(self), + -GET_TD_SECONDS(self), + -GET_TD_MICROSECONDS(self), + 1); +} + +static PyObject * +delta_positive(PyDateTime_Delta *self) +{ + /* Could optimize this (by returning self) if this isn't a + * subclass -- but who uses unary + ? Approximately nobody. + */ + return new_delta(GET_TD_DAYS(self), + GET_TD_SECONDS(self), + GET_TD_MICROSECONDS(self), + 0); +} + +static PyObject * +delta_abs(PyDateTime_Delta *self) +{ + PyObject *result; + + assert(GET_TD_MICROSECONDS(self) >= 0); + assert(GET_TD_SECONDS(self) >= 0); + + if (GET_TD_DAYS(self) < 0) + result = delta_negative(self); + else + result = delta_positive(self); + + return result; +} + +static PyObject * +delta_subtract(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left) && PyDelta_Check(right)) { + /* delta - delta */ + PyObject *minus_right = PyNumber_Negative(right); + if (minus_right) { + result = delta_add(left, minus_right); + Py_DECREF(minus_right); + } + else + result = NULL; + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + */ +static PyObject * +delta_richcompare(PyDateTime_Delta *self, PyObject *other, int op) +{ + int diff; + + if (! PyDelta_CheckExact(other)) { + PyErr_Format(PyExc_TypeError, + "can't compare %s to %s instance", + self->ob_type->tp_name, other->ob_type->tp_name); + return NULL; + } + diff = GET_TD_DAYS(self) - GET_TD_DAYS(other); + if (diff == 0) { + diff = GET_TD_SECONDS(self) - GET_TD_SECONDS(other); + if (diff == 0) + diff = GET_TD_MICROSECONDS(self) - + GET_TD_MICROSECONDS(other); + } + return diff_to_bool(diff, op); +} + +static PyObject *delta_getstate(PyDateTime_Delta *self); + +static long +delta_hash(PyDateTime_Delta *self) +{ + if (self->hashcode == -1) { + PyObject *temp = delta_getstate(self); + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static PyObject * +delta_multiply(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left)) { + /* delta * ??? */ + if (PyInt_Check(right) || PyLong_Check(right)) + result = multiply_int_timedelta(right, + (PyDateTime_Delta *) left); + } + else if (PyInt_Check(left) || PyLong_Check(left)) + result = multiply_int_timedelta(left, + (PyDateTime_Delta *) right); + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +static PyObject * +delta_divide(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left)) { + /* delta * ??? */ + if (PyInt_Check(right) || PyLong_Check(right)) + result = divide_timedelta_int( + (PyDateTime_Delta *)left, + right); + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* Fold in the value of the tag ("seconds", "weeks", etc) component of a + * timedelta constructor. sofar is the # of microseconds accounted for + * so far, and there are factor microseconds per current unit, the number + * of which is given by num. num * factor is added to sofar in a + * numerically careful way, and that's the result. Any fractional + * microseconds left over (this can happen if num is a float type) are + * added into *leftover. + * Note that there are many ways this can give an error (NULL) return. + */ +static PyObject * +accum(const char* tag, PyObject *sofar, PyObject *num, PyObject *factor, + double *leftover) +{ + PyObject *prod; + PyObject *sum; + + assert(num != NULL); + + if (PyInt_Check(num) || PyLong_Check(num)) { + prod = PyNumber_Multiply(num, factor); + if (prod == NULL) + return NULL; + sum = PyNumber_Add(sofar, prod); + Py_DECREF(prod); + return sum; + } + + if (PyFloat_Check(num)) { + double dnum; + double fracpart; + double intpart; + PyObject *x; + PyObject *y; + + /* The Plan: decompose num into an integer part and a + * fractional part, num = intpart + fracpart. + * Then num * factor == + * intpart * factor + fracpart * factor + * and the LHS can be computed exactly in long arithmetic. + * The RHS is again broken into an int part and frac part. + * and the frac part is added into *leftover. + */ + dnum = PyFloat_AsDouble(num); + if (dnum == -1.0 && PyErr_Occurred()) + return NULL; + fracpart = modf(dnum, &intpart); + x = PyLong_FromDouble(intpart); + if (x == NULL) + return NULL; + + prod = PyNumber_Multiply(x, factor); + Py_DECREF(x); + if (prod == NULL) + return NULL; + + sum = PyNumber_Add(sofar, prod); + Py_DECREF(prod); + if (sum == NULL) + return NULL; + + if (fracpart == 0.0) + return sum; + /* So far we've lost no information. Dealing with the + * fractional part requires float arithmetic, and may + * lose a little info. + */ + assert(PyInt_Check(factor) || PyLong_Check(factor)); + if (PyInt_Check(factor)) + dnum = (double)PyInt_AsLong(factor); + else + dnum = PyLong_AsDouble(factor); + + dnum *= fracpart; + fracpart = modf(dnum, &intpart); + x = PyLong_FromDouble(intpart); + if (x == NULL) { + Py_DECREF(sum); + return NULL; + } + + y = PyNumber_Add(sum, x); + Py_DECREF(sum); + Py_DECREF(x); + *leftover += fracpart; + return y; + } + + PyErr_Format(PyExc_TypeError, + "unsupported type for timedelta %s component: %s", + tag, num->ob_type->tp_name); + return NULL; +} + +static PyObject * +delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + + /* Argument objects. */ + PyObject *day = NULL; + PyObject *second = NULL; + PyObject *us = NULL; + PyObject *ms = NULL; + PyObject *minute = NULL; + PyObject *hour = NULL; + PyObject *week = NULL; + + PyObject *x = NULL; /* running sum of microseconds */ + PyObject *y = NULL; /* temp sum of microseconds */ + double leftover_us = 0.0; + + static char *keywords[] = { + "days", "seconds", "microseconds", "milliseconds", + "minutes", "hours", "weeks", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|OOOOOOO:__new__", + keywords, + &day, &second, &us, + &ms, &minute, &hour, &week) == 0) + goto Done; + + x = PyInt_FromLong(0); + if (x == NULL) + goto Done; + +#define CLEANUP \ + Py_DECREF(x); \ + x = y; \ + if (x == NULL) \ + goto Done + + if (us) { + y = accum("microseconds", x, us, us_per_us, &leftover_us); + CLEANUP; + } + if (ms) { + y = accum("milliseconds", x, ms, us_per_ms, &leftover_us); + CLEANUP; + } + if (second) { + y = accum("seconds", x, second, us_per_second, &leftover_us); + CLEANUP; + } + if (minute) { + y = accum("minutes", x, minute, us_per_minute, &leftover_us); + CLEANUP; + } + if (hour) { + y = accum("hours", x, hour, us_per_hour, &leftover_us); + CLEANUP; + } + if (day) { + y = accum("days", x, day, us_per_day, &leftover_us); + CLEANUP; + } + if (week) { + y = accum("weeks", x, week, us_per_week, &leftover_us); + CLEANUP; + } + if (leftover_us) { + /* Round to nearest whole # of us, and add into x. */ + PyObject *temp; + if (leftover_us >= 0.0) + leftover_us = floor(leftover_us + 0.5); + else + leftover_us = ceil(leftover_us - 0.5); + temp = PyLong_FromDouble(leftover_us); + if (temp == NULL) { + Py_DECREF(x); + goto Done; + } + y = PyNumber_Add(x, temp); + Py_DECREF(temp); + CLEANUP; + } + + self = microseconds_to_delta(x); + Py_DECREF(x); +Done: + return self; + +#undef CLEANUP +} + +static int +delta_nonzero(PyDateTime_Delta *self) +{ + return (GET_TD_DAYS(self) != 0 + || GET_TD_SECONDS(self) != 0 + || GET_TD_MICROSECONDS(self) != 0); +} + +static PyObject * +delta_repr(PyDateTime_Delta *self) +{ + if (GET_TD_MICROSECONDS(self) != 0) + return PyString_FromFormat("%s(%d, %d, %d)", + self->ob_type->tp_name, + GET_TD_DAYS(self), + GET_TD_SECONDS(self), + GET_TD_MICROSECONDS(self)); + if (GET_TD_SECONDS(self) != 0) + return PyString_FromFormat("%s(%d, %d)", + self->ob_type->tp_name, + GET_TD_DAYS(self), + GET_TD_SECONDS(self)); + + return PyString_FromFormat("%s(%d)", + self->ob_type->tp_name, + GET_TD_DAYS(self)); +} + +static PyObject * +delta_str(PyDateTime_Delta *self) +{ + int days = GET_TD_DAYS(self); + int seconds = GET_TD_SECONDS(self); + int us = GET_TD_MICROSECONDS(self); + int hours; + int minutes; + char buf[500]; + int i = 0; + + minutes = divmod(seconds, 60, &seconds); + hours = divmod(minutes, 60, &minutes); + + if (days) { + i += sprintf(buf + i, "%d day%s, ", days, + (days == 1 || days == -1) ? "" : "s"); + assert(i < sizeof(buf)); + } + + i += sprintf(buf + i, "%d:%02d:%02d", hours, minutes, seconds); + assert(i < sizeof(buf)); + + if (us) { + i += sprintf(buf + i, ".%06d", us); + assert(i < sizeof(buf)); + } + + return PyString_FromStringAndSize(buf, i); +} + +/* Pickle support. Quite a maze! While __getstate__/__setstate__ sufficed + * in the Python implementation, the C implementation also requires + * __reduce__, and a __safe_for_unpickling__ attr in the type object. + */ +static PyObject * +delta_getstate(PyDateTime_Delta *self) +{ + return Py_BuildValue("iii", GET_TD_DAYS(self), + GET_TD_SECONDS(self), + GET_TD_MICROSECONDS(self)); +} + +static PyObject * +delta_setstate(PyDateTime_Delta *self, PyObject *state) +{ + int day; + int second; + int us; + + if (!PyArg_ParseTuple(state, "iii:__setstate__", &day, &second, &us)) + return NULL; + + self->hashcode = -1; + SET_TD_DAYS(self, day); + SET_TD_SECONDS(self, second); + SET_TD_MICROSECONDS(self, us); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +delta_reduce(PyDateTime_Delta* self) +{ + PyObject* result = NULL; + PyObject* state = delta_getstate(self); + + if (state != NULL) { + /* The funky "()" in the format string creates an empty + * tuple as the 2nd component of the result 3-tuple. + */ + result = Py_BuildValue("O()O", self->ob_type, state); + Py_DECREF(state); + } + return result; +} + +#define OFFSET(field) offsetof(PyDateTime_Delta, field) + +static PyMemberDef delta_members[] = { + {"days", T_LONG, OFFSET(days), READONLY, + PyDoc_STR("Number of days.")}, + + {"seconds", T_LONG, OFFSET(seconds), READONLY, + PyDoc_STR("Number of seconds (>= 0 and less than 1 day).")}, + + {"microseconds", T_LONG, OFFSET(microseconds), READONLY, + PyDoc_STR("Number of microseconds (>= 0 and less than 1 second).")}, + {NULL} +}; + +static PyMethodDef delta_methods[] = { + {"__setstate__", (PyCFunction)delta_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__reduce__", (PyCFunction)delta_reduce, METH_NOARGS, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)delta_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL}, +}; + +static char delta_doc[] = +PyDoc_STR("Difference between two datetime values."); + +static PyNumberMethods delta_as_number = { + delta_add, /* nb_add */ + delta_subtract, /* nb_subtract */ + delta_multiply, /* nb_multiply */ + delta_divide, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + (unaryfunc)delta_negative, /* nb_negative */ + (unaryfunc)delta_positive, /* nb_positive */ + (unaryfunc)delta_abs, /* nb_absolute */ + (inquiry)delta_nonzero, /* nb_nonzero */ + 0, /*nb_invert*/ + 0, /*nb_lshift*/ + 0, /*nb_rshift*/ + 0, /*nb_and*/ + 0, /*nb_xor*/ + 0, /*nb_or*/ + 0, /*nb_coerce*/ + 0, /*nb_int*/ + 0, /*nb_long*/ + 0, /*nb_float*/ + 0, /*nb_oct*/ + 0, /*nb_hex*/ + 0, /*nb_inplace_add*/ + 0, /*nb_inplace_subtract*/ + 0, /*nb_inplace_multiply*/ + 0, /*nb_inplace_divide*/ + 0, /*nb_inplace_remainder*/ + 0, /*nb_inplace_power*/ + 0, /*nb_inplace_lshift*/ + 0, /*nb_inplace_rshift*/ + 0, /*nb_inplace_and*/ + 0, /*nb_inplace_xor*/ + 0, /*nb_inplace_or*/ + delta_divide, /* nb_floor_divide */ + 0, /* nb_true_divide */ + 0, /* nb_inplace_floor_divide */ + 0, /* nb_inplace_true_divide */ +}; + +static PyTypeObject PyDateTime_DeltaType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.timedelta", /* tp_name */ + sizeof(PyDateTime_Delta), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)delta_repr, /* tp_repr */ + &delta_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)delta_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)delta_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */ + delta_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)delta_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + delta_methods, /* tp_methods */ + delta_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + delta_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_Date implementation. + */ + +/* Accessor properties. */ + +static PyObject * +date_year(PyDateTime_Date *self, void *unused) +{ + return PyInt_FromLong(GET_YEAR(self)); +} + +static PyObject * +date_month(PyDateTime_Date *self, void *unused) +{ + return PyInt_FromLong(GET_MONTH(self)); +} + +static PyObject * +date_day(PyDateTime_Date *self, void *unused) +{ + return PyInt_FromLong(GET_DAY(self)); +} + +static PyGetSetDef date_getset[] = { + {"year", (getter)date_year}, + {"month", (getter)date_month}, + {"day", (getter)date_day}, + {NULL} +}; + +/* Constructors. */ + +static PyObject * +date_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int year; + int month; + int day; + + static char *keywords[] = { + "year", "month", "day", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "iii", keywords, + &year, &month, &day)) { + if (check_date_args(year, month, day) < 0) + return NULL; + self = new_date(year, month, day); + } + return self; +} + +/* Return new date from localtime(t). */ +static PyObject * +date_local_from_time_t(PyObject *cls, time_t t) +{ + struct tm *tm; + PyObject *result = NULL; + + tm = localtime(&t); + if (tm) + result = PyObject_CallFunction(cls, "iii", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday); + else + PyErr_SetString(PyExc_ValueError, + "timestamp out of range for " + "platform localtime() function"); + return result; +} + +/* Return new date from current time. + * We say this is equivalent to fromtimestamp(time.time()), and the + * only way to be sure of that is to *call* time.time(). That's not + * generally the same as calling C's time. + */ +static PyObject * +date_today(PyObject *cls, PyObject *dummy) +{ + PyObject *time; + PyObject *result; + + time = time_time(); + if (time == NULL) + return NULL; + + /* Note well: today() is a class method, so this may not call + * date.fromtimestamp. For example, it may call + * datetime.fromtimestamp. That's why we need all the accuracy + * time.time() delivers; if someone were gonzo about optimization, + * date.today() could get away with plain C time(). + */ + result = PyObject_CallMethod(cls, "fromtimestamp", "O", time); + Py_DECREF(time); + return result; +} + +/* Return new date from given timestamp (Python timestamp -- a double). */ +static PyObject * +date_fromtimestamp(PyObject *cls, PyObject *args) +{ + double timestamp; + PyObject *result = NULL; + + if (PyArg_ParseTuple(args, "d:fromtimestamp", ×tamp)) + result = date_local_from_time_t(cls, (time_t)timestamp); + return result; +} + +/* Return new date from proleptic Gregorian ordinal. Raises ValueError if + * the ordinal is out of range. + */ +static PyObject * +date_fromordinal(PyObject *cls, PyObject *args) +{ + PyObject *result = NULL; + int ordinal; + + if (PyArg_ParseTuple(args, "i:fromordinal", &ordinal)) { + int year; + int month; + int day; + + if (ordinal < 1) + PyErr_SetString(PyExc_ValueError, "ordinal must be " + ">= 1"); + else { + ord_to_ymd(ordinal, &year, &month, &day); + result = PyObject_CallFunction(cls, "iii", + year, month, day); + } + } + return result; +} + +/* + * Date arithmetic. + */ + +/* date + timedelta -> date. If arg negate is true, subtract the timedelta + * instead. + */ +static PyObject * +add_date_timedelta(PyDateTime_Date *date, PyDateTime_Delta *delta, int negate) +{ + PyObject *result = NULL; + int year = GET_YEAR(date); + int month = GET_MONTH(date); + int deltadays = GET_TD_DAYS(delta); + /* C-level overflow is impossible because |deltadays| < 1e9. */ + int day = GET_DAY(date) + (negate ? -deltadays : deltadays); + + if (normalize_date(&year, &month, &day) >= 0) + result = new_date(year, month, day); + return result; +} + +static PyObject * +date_add(PyObject *left, PyObject *right) +{ + if (PyDateTime_Check(left) || PyDateTime_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + if (PyDate_CheckExact(left)) { + /* date + ??? */ + if (PyDelta_Check(right)) + /* date + delta */ + return add_date_timedelta((PyDateTime_Date *) left, + (PyDateTime_Delta *) right, + 0); + } + else { + /* ??? + date + * 'right' must be one of us, or we wouldn't have been called + */ + if (PyDelta_Check(left)) + /* delta + date */ + return add_date_timedelta((PyDateTime_Date *) right, + (PyDateTime_Delta *) left, + 0); + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyObject * +date_subtract(PyObject *left, PyObject *right) +{ + if (PyDateTime_Check(left) || PyDateTime_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + if (PyDate_CheckExact(left)) { + if (PyDate_CheckExact(right)) { + /* date - date */ + int left_ord = ymd_to_ord(GET_YEAR(left), + GET_MONTH(left), + GET_DAY(left)); + int right_ord = ymd_to_ord(GET_YEAR(right), + GET_MONTH(right), + GET_DAY(right)); + return new_delta(left_ord - right_ord, 0, 0, 0); + } + if (PyDelta_Check(right)) { + /* date - delta */ + return add_date_timedelta((PyDateTime_Date *) left, + (PyDateTime_Delta *) right, + 1); + } + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + + +/* Various ways to turn a date into a string. */ + +static PyObject * +date_repr(PyDateTime_Date *self) +{ + char buffer[1028]; + char *typename; + + typename = self->ob_type->tp_name; + PyOS_snprintf(buffer, sizeof(buffer), "%s(%d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); + + return PyString_FromString(buffer); +} + +static PyObject * +date_isoformat(PyDateTime_Date *self) +{ + char buffer[128]; + + isoformat_date(self, buffer, sizeof(buffer)); + return PyString_FromString(buffer); +} + +/* str() calls the appropriate isofomat() method. */ +static PyObject * +date_str(PyDateTime_Date *self) +{ + return PyObject_CallMethod((PyObject *)self, "isoformat", "()"); +} + + +static PyObject * +date_ctime(PyDateTime_Date *self) +{ + return format_ctime(self, 0, 0, 0); +} + +static PyObject * +date_strftime(PyDateTime_Date *self, PyObject *args, PyObject *kw) +{ + /* This method can be inherited, and needs to call the + * timetuple() method appropriate to self's class. + */ + PyObject *result; + PyObject *format; + PyObject *tuple; + static char *keywords[] = {"format", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:strftime", keywords, + &PyString_Type, &format)) + return NULL; + + tuple = PyObject_CallMethod((PyObject *)self, "timetuple", "()"); + if (tuple == NULL) + return NULL; + result = wrap_strftime((PyObject *)self, format, tuple); + Py_DECREF(tuple); + return result; +} + +/* ISO methods. */ + +static PyObject * +date_isoweekday(PyDateTime_Date *self) +{ + int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); + + return PyInt_FromLong(dow + 1); +} + +static PyObject * +date_isocalendar(PyDateTime_Date *self) +{ + int year = GET_YEAR(self); + int week1_monday = iso_week1_monday(year); + int today = ymd_to_ord(year, GET_MONTH(self), GET_DAY(self)); + int week; + int day; + + week = divmod(today - week1_monday, 7, &day); + if (week < 0) { + --year; + week1_monday = iso_week1_monday(year); + week = divmod(today - week1_monday, 7, &day); + } + else if (week >= 52 && today >= iso_week1_monday(year + 1)) { + ++year; + week = 0; + } + return Py_BuildValue("iii", year, week + 1, day + 1); +} + +/* Miscellaneous methods. */ + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + */ +static PyObject * +date_richcompare(PyDateTime_Date *self, PyObject *other, int op) +{ + int diff; + + if (! PyDate_Check(other)) { + PyErr_Format(PyExc_TypeError, + "can't compare date to %s instance", + other->ob_type->tp_name); + return NULL; + } + diff = memcmp(self->data, ((PyDateTime_Date *)other)->data, + _PyDateTime_DATE_DATASIZE); + return diff_to_bool(diff, op); +} + +static PyObject * +date_timetuple(PyDateTime_Date *self) +{ + return build_struct_time(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self), + 0, 0, 0, -1); +} + +static PyObject *date_getstate(PyDateTime_Date *self); + +static long +date_hash(PyDateTime_Date *self) +{ + if (self->hashcode == -1) { + PyObject *temp = date_getstate(self); + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static PyObject * +date_toordinal(PyDateTime_Date *self) +{ + return PyInt_FromLong(ymd_to_ord(GET_YEAR(self), GET_MONTH(self), + GET_DAY(self))); +} + +static PyObject * +date_weekday(PyDateTime_Date *self) +{ + int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); + + return PyInt_FromLong(dow); +} + +/* Pickle support. Quite a maze! */ + +static PyObject * +date_getstate(PyDateTime_Date *self) +{ + return PyString_FromStringAndSize(self->data, + _PyDateTime_DATE_DATASIZE); +} + +static PyObject * +date_setstate(PyDateTime_Date *self, PyObject *state) +{ + const int len = PyString_Size(state); + unsigned char *pdata = (unsigned char*)PyString_AsString(state); + + if (! PyString_Check(state) || + len != _PyDateTime_DATE_DATASIZE) { + PyErr_SetString(PyExc_TypeError, + "bad argument to date.__setstate__"); + return NULL; + } + memcpy(self->data, pdata, _PyDateTime_DATE_DATASIZE); + self->hashcode = -1; + + Py_INCREF(Py_None); + return Py_None; +} + +/* XXX This seems a ridiculously inefficient way to pickle a short string. */ +static PyObject * +date_pickler(PyObject *module, PyDateTime_Date *date) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyDate_CheckExact(date)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to date pickler: %s", + date->ob_type->tp_name); + return NULL; + } + state = date_getstate(date); + if (state) { + result = Py_BuildValue("O(O)", date_unpickler_object, state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +date_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_Date *self; + + if (! PyString_CheckExact(arg)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to date unpickler: %s", + arg->ob_type->tp_name); + return NULL; + } + self = PyObject_New(PyDateTime_Date, &PyDateTime_DateType); + if (self != NULL) { + PyObject *res = date_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef date_methods[] = { + /* Class methods: */ + {"fromtimestamp", (PyCFunction)date_fromtimestamp, METH_VARARGS | + METH_CLASS, + PyDoc_STR("timestamp -> local date from a POSIX timestamp (like " + "time.time()).")}, + + {"fromordinal", (PyCFunction)date_fromordinal, METH_VARARGS | + METH_CLASS, + PyDoc_STR("int -> date corresponding to a proleptic Gregorian " + "ordinal.")}, + + {"today", (PyCFunction)date_today, METH_NOARGS | METH_CLASS, + PyDoc_STR("Current date or datetime: same as " + "self.__class__.fromtimestamp(time.time()).")}, + + /* Instance methods: */ + + {"ctime", (PyCFunction)date_ctime, METH_NOARGS, + PyDoc_STR("Return ctime() style string.")}, + + {"strftime", (PyCFunction)date_strftime, METH_KEYWORDS, + PyDoc_STR("format -> strftime() style string.")}, + + {"timetuple", (PyCFunction)date_timetuple, METH_NOARGS, + PyDoc_STR("Return time tuple, compatible with time.localtime().")}, + + {"isocalendar", (PyCFunction)date_isocalendar, METH_NOARGS, + PyDoc_STR("Return a 3-tuple containing ISO year, week number, and " + "weekday.")}, + + {"isoformat", (PyCFunction)date_isoformat, METH_NOARGS, + PyDoc_STR("Return string in ISO 8601 format, YYYY-MM-DD.")}, + + {"isoweekday", (PyCFunction)date_isoweekday, METH_NOARGS, + PyDoc_STR("Return the day of the week represented by the date.\n" + "Monday == 1 ... Sunday == 7")}, + + {"toordinal", (PyCFunction)date_toordinal, METH_NOARGS, + PyDoc_STR("Return proleptic Gregorian ordinal. January 1 of year " + "1 is day 1.")}, + + {"weekday", (PyCFunction)date_weekday, METH_NOARGS, + PyDoc_STR("Return the day of the week represented by the date.\n" + "Monday == 0 ... Sunday == 6")}, + + {"__setstate__", (PyCFunction)date_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)date_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + + {NULL, NULL} +}; + +static char date_doc[] = +PyDoc_STR("Basic date type."); + +static PyNumberMethods date_as_number = { + date_add, /* nb_add */ + date_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_nonzero */ +}; + +static PyTypeObject PyDateTime_DateType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.date", /* tp_name */ + sizeof(PyDateTime_Date), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)date_repr, /* tp_repr */ + &date_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)date_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)date_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + date_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)date_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + date_methods, /* tp_methods */ + 0, /* tp_members */ + date_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + date_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_DateTime implementation. + */ + +/* Accessor properties. */ + +static PyObject * +datetime_hour(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_HOUR(self)); +} + +static PyObject * +datetime_minute(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_MINUTE(self)); +} + +static PyObject * +datetime_second(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_SECOND(self)); +} + +static PyObject * +datetime_microsecond(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_MICROSECOND(self)); +} + +static PyGetSetDef datetime_getset[] = { + {"hour", (getter)datetime_hour}, + {"minute", (getter)datetime_minute}, + {"second", (getter)datetime_second}, + {"microsecond", (getter)datetime_microsecond}, + {NULL} +}; + +/* Constructors. */ + +static PyObject * +datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int year; + int month; + int day; + int hour = 0; + int minute = 0; + 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, + &year, &month, &day, &hour, &minute, + &second, &usecond)) { + if (check_date_args(year, month, day) < 0) + return NULL; + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + self = new_datetime(year, month, day, + hour, minute, second, usecond); + } + return self; +} + + +/* TM_FUNC is the shared type of localtime() and gmtime(). */ +typedef struct tm *(*TM_FUNC)(const time_t *timer); + +/* Internal helper. + * Build datetime from a time_t and a distinct count of microseconds. + * Pass localtime or gmtime for f, to control the interpretation of timet. + */ +static PyObject * +datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us) +{ + struct tm *tm; + PyObject *result = NULL; + + tm = f(&timet); + if (tm) + result = PyObject_CallFunction(cls, "iiiiiii", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec, + us); + else + PyErr_SetString(PyExc_ValueError, + "timestamp out of range for " + "platform localtime()/gmtime() function"); + return result; +} + +/* Internal helper. + * Build datetime from a Python timestamp. Pass localtime or gmtime for f, + * to control the interpretation of the timestamp. Since a double doesn't + * have enough bits to cover a datetime's full range of precision, it's + * better to call datetime_from_timet_and_us provided you have a way + * to get that much precision (e.g., C time() isn't good enough). + */ +static PyObject * +datetime_from_timestamp(PyObject *cls, TM_FUNC f, double timestamp) +{ + time_t timet = (time_t)timestamp; + int us = (int)((timestamp - (double)timet) * 1e6); + + return datetime_from_timet_and_us(cls, f, timet, us); +} + +/* Internal helper. + * Build most accurate possible datetime for current time. Pass localtime or + * gmtime for f as appropriate. + */ +static PyObject * +datetime_best_possible(PyObject *cls, TM_FUNC f) +{ +#ifdef HAVE_GETTIMEOFDAY + struct timeval t; + +#ifdef GETTIMEOFDAY_NO_TZ + gettimeofday(&t); +#else + gettimeofday(&t, (struct timezone *)NULL); +#endif + return datetime_from_timet_and_us(cls, f, t.tv_sec, (int)t.tv_usec); + +#else /* ! HAVE_GETTIMEOFDAY */ + /* No flavor of gettimeofday exists on this platform. Python's + * time.time() does a lot of other platform tricks to get the + * best time it can on the platform, and we're not going to do + * better than that (if we could, the better code would belong + * in time.time()!) We're limited by the precision of a double, + * though. + */ + PyObject *time; + double dtime; + + time = time_time(); + if (time == NULL) + return NULL; + dtime = PyFloat_AsDouble(time); + Py_DECREF(time); + if (dtime == -1.0 && PyErr_Occurred()) + return NULL; + return datetime_from_timestamp(cls, f, dtime); +#endif /* ! HAVE_GETTIMEOFDAY */ +} + +/* Return new local datetime from timestamp (Python timestamp -- a double). */ +static PyObject * +datetime_fromtimestamp(PyObject *cls, PyObject *args) +{ + double timestamp; + PyObject *result = NULL; + + if (PyArg_ParseTuple(args, "d:fromtimestamp", ×tamp)) + result = datetime_from_timestamp(cls, localtime, timestamp); + return result; +} + +/* Return new UTC datetime from timestamp (Python timestamp -- a double). */ +static PyObject * +datetime_utcfromtimestamp(PyObject *cls, PyObject *args) +{ + double timestamp; + PyObject *result = NULL; + + if (PyArg_ParseTuple(args, "d:utcfromtimestamp", ×tamp)) + result = datetime_from_timestamp(cls, gmtime, timestamp); + return result; +} + +/* Return best possible local time -- this isn't constrained by the + * precision of a timestamp. + */ +static PyObject * +datetime_now(PyObject *cls, PyObject *dummy) +{ + return datetime_best_possible(cls, localtime); +} + +/* Return best possible UTC time -- this isn't constrained by the + * precision of a timestamp. + */ +static PyObject * +datetime_utcnow(PyObject *cls, PyObject *dummy) +{ + return datetime_best_possible(cls, gmtime); +} + +/* Return new datetime or datetimetz from date/datetime/datetimetz and + * time/timetz arguments. + */ +static PyObject * +datetime_combine(PyObject *cls, PyObject *args, PyObject *kw) +{ + static char *keywords[] = {"date", "time", NULL}; + PyObject *date; + PyObject *time; + PyObject *result = NULL; + + if (PyArg_ParseTupleAndKeywords(args, kw, "O!O!:combine", keywords, + &PyDateTime_DateType, &date, + &PyDateTime_TimeType, &time)) + result = PyObject_CallFunction(cls, "iiiiiii", + GET_YEAR(date), + GET_MONTH(date), + GET_DAY(date), + TIME_GET_HOUR(time), + TIME_GET_MINUTE(time), + TIME_GET_SECOND(time), + TIME_GET_MICROSECOND(time)); + if (result && PyTimeTZ_Check(time) && PyDateTimeTZ_Check(result)) { + /* Copy the tzinfo field. */ + PyObject *tzinfo = ((PyDateTime_TimeTZ *)time)->tzinfo; + Py_INCREF(tzinfo); + Py_DECREF(((PyDateTime_DateTimeTZ *)result)->tzinfo); + ((PyDateTime_DateTimeTZ *)result)->tzinfo = tzinfo; + } + return result; +} + +/* datetime arithmetic. */ + +static PyObject * +add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta) +{ + /* Note that the C-level additions can't overflow, because of + * invariant bounds on the member values. + */ + int year = GET_YEAR(date); + int month = GET_MONTH(date); + int day = GET_DAY(date) + GET_TD_DAYS(delta); + int hour = DATE_GET_HOUR(date); + int minute = DATE_GET_MINUTE(date); + int second = DATE_GET_SECOND(date) + GET_TD_SECONDS(delta); + int microsecond = DATE_GET_MICROSECOND(date) + + GET_TD_MICROSECONDS(delta); + + if (normalize_datetime(&year, &month, &day, + &hour, &minute, &second, µsecond) < 0) + return NULL; + else + return new_datetime(year, month, day, + hour, minute, second, microsecond); +} + +static PyObject * +sub_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta) +{ + /* Note that the C-level subtractions can't overflow, because of + * invariant bounds on the member values. + */ + int year = GET_YEAR(date); + int month = GET_MONTH(date); + int day = GET_DAY(date) - GET_TD_DAYS(delta); + int hour = DATE_GET_HOUR(date); + int minute = DATE_GET_MINUTE(date); + int second = DATE_GET_SECOND(date) - GET_TD_SECONDS(delta); + int microsecond = DATE_GET_MICROSECOND(date) - + GET_TD_MICROSECONDS(delta); + + if (normalize_datetime(&year, &month, &day, + &hour, &minute, &second, µsecond) < 0) + return NULL; + else + return new_datetime(year, month, day, + hour, minute, second, microsecond); +} + +static PyObject * +sub_datetime_datetime(PyDateTime_DateTime *left, PyDateTime_DateTime *right) +{ + int days1 = ymd_to_ord(GET_YEAR(left), GET_MONTH(left), GET_DAY(left)); + int days2 = ymd_to_ord(GET_YEAR(right), + GET_MONTH(right), + GET_DAY(right)); + /* These can't overflow, since the values are normalized. At most + * this gives the number of seconds in one day. + */ + int delta_s = (DATE_GET_HOUR(left) - DATE_GET_HOUR(right)) * 3600 + + (DATE_GET_MINUTE(left) - DATE_GET_MINUTE(right)) * 60 + + DATE_GET_SECOND(left) - DATE_GET_SECOND(right); + int delta_us = DATE_GET_MICROSECOND(left) - + DATE_GET_MICROSECOND(right); + + return new_delta(days1 - days2, delta_s, delta_us, 1); +} + +static PyObject * +datetime_add(PyObject *left, PyObject *right) +{ + if (PyDateTime_Check(left)) { + /* datetime + ??? */ + if (PyDelta_Check(right)) + /* datetime + delta */ + return add_datetime_timedelta( + (PyDateTime_DateTime *)left, + (PyDateTime_Delta *)right); + } + else if (PyDelta_Check(left)) { + /* delta + datetime */ + return add_datetime_timedelta((PyDateTime_DateTime *) right, + (PyDateTime_Delta *) left); + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyObject * +datetime_subtract(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDateTime_Check(left)) { + /* datetime - ??? */ + if (PyDateTime_Check(right)) { + /* datetime - datetime */ + result = sub_datetime_datetime( + (PyDateTime_DateTime *)left, + (PyDateTime_DateTime *)right); + } + else if (PyDelta_Check(right)) { + /* datetime - delta */ + result = sub_datetime_timedelta( + (PyDateTime_DateTime *)left, + (PyDateTime_Delta *)right); + } + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* Various ways to turn a datetime into a string. */ + +static PyObject * +datetime_repr(PyDateTime_DateTime *self) +{ + char buffer[1000]; + char *typename = self->ob_type->tp_name; + + if (DATE_GET_MICROSECOND(self)) { + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d, %d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self)); + } + else if (DATE_GET_SECOND(self)) { + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); + } + else { + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self)); + } + return PyString_FromString(buffer); +} + +static PyObject * +datetime_str(PyDateTime_DateTime *self) +{ + return PyObject_CallMethod((PyObject *)self, "isoformat", "(s)", " "); +} + +static PyObject * +datetime_isoformat(PyDateTime_DateTime *self, + PyObject *args, PyObject *kw) +{ + char sep = 'T'; + static char *keywords[] = {"sep", NULL}; + char buffer[100]; + char *cp; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|c:isoformat", keywords, + &sep)) + return NULL; + cp = isoformat_date((PyDateTime_Date *)self, buffer, sizeof(buffer)); + assert(cp != NULL); + *cp++ = sep; + isoformat_time(self, cp, sizeof(buffer) - (cp - buffer)); + return PyString_FromString(buffer); +} + +static PyObject * +datetime_ctime(PyDateTime_DateTime *self) +{ + return format_ctime((PyDateTime_Date *)self, + DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); +} + +/* Miscellaneous methods. */ + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + * Note that this routine handles all comparisons for datetime and datetimetz. + */ +static PyObject * +datetime_richcompare(PyDateTime_DateTime *self, PyObject *other, int op) +{ + int diff; + naivety n1, n2; + int offset1, offset2; + + if (! PyDateTime_Check(other)) { + /* Stop this from falling back to address comparison. */ + PyErr_Format(PyExc_TypeError, + "can't compare '%s' to '%s'", + self->ob_type->tp_name, + other->ob_type->tp_name); + return NULL; + } + n1 = classify_object((PyObject *)self, &offset1); + assert(n1 != OFFSET_UNKNOWN); + if (n1 == OFFSET_ERROR) + return NULL; + + n2 = classify_object(other, &offset2); + assert(n2 != OFFSET_UNKNOWN); + if (n2 == OFFSET_ERROR) + return NULL; + + /* If they're both naive, or both aware and have the same offsets, + * we get off cheap. Note that if they're both naive, offset1 == + * offset2 == 0 at this point. + */ + if (n1 == n2 && offset1 == offset2) { + diff = memcmp(self->data, ((PyDateTime_DateTime *)other)->data, + _PyDateTime_DATETIME_DATASIZE); + return diff_to_bool(diff, op); + } + + if (n1 == OFFSET_AWARE && n2 == OFFSET_AWARE) { + /* We want the sign of + * (self - offset1 minutes) - (other - offset2 minutes) = + * (self - other) + (offset2 - offset1) minutes. + */ + PyDateTime_Delta *delta; + int days, seconds, us; + + assert(offset1 != offset2); /* else last "if" handled it */ + delta = (PyDateTime_Delta *)sub_datetime_datetime(self, + (PyDateTime_DateTime *)other); + if (delta == NULL) + return NULL; + days = delta->days; + seconds = delta->seconds + (offset2 - offset1) * 60; + us = delta->microseconds; + Py_DECREF(delta); + normalize_d_s_us(&days, &seconds, &us); + diff = days; + if (diff == 0) + diff = seconds | us; + return diff_to_bool(diff, op); + } + + assert(n1 != n2); + PyErr_SetString(PyExc_TypeError, + "can't compare offset-naive and " + "offset-aware datetimes"); + return NULL; +} + +static PyObject *datetime_getstate(PyDateTime_DateTime *self); + +static long +datetime_hash(PyDateTime_DateTime *self) +{ + if (self->hashcode == -1) { + naivety n; + int offset; + PyObject *temp; + + n = classify_object((PyObject *)self, &offset); + assert(n != OFFSET_UNKNOWN); + if (n == OFFSET_ERROR) + return -1; + + /* Reduce this to a hash of another object. */ + if (n == OFFSET_NAIVE) + temp = datetime_getstate(self); + else { + int days; + int seconds; + + assert(n == OFFSET_AWARE); + assert(PyDateTimeTZ_Check(self)); + days = ymd_to_ord(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self)); + seconds = DATE_GET_HOUR(self) * 3600 + + (DATE_GET_MINUTE(self) - offset) * 60 + + DATE_GET_SECOND(self); + temp = new_delta(days, + seconds, + DATE_GET_MICROSECOND(self), + 1); + } + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static PyObject * +datetime_timetuple(PyDateTime_DateTime *self) +{ + return build_struct_time(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self), + DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + -1); +} + +static PyObject * +datetime_getdate(PyDateTime_DateTime *self) +{ + return new_date(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(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)); +} + +/* Pickle support. Quite a maze! */ + +static PyObject * +datetime_getstate(PyDateTime_DateTime *self) +{ + return PyString_FromStringAndSize(self->data, + _PyDateTime_DATETIME_DATASIZE); +} + +static PyObject * +datetime_setstate(PyDateTime_DateTime *self, PyObject *state) +{ + const int len = PyString_Size(state); + unsigned char *pdata = (unsigned char*)PyString_AsString(state); + + if (! PyString_Check(state) || + len != _PyDateTime_DATETIME_DATASIZE) { + PyErr_SetString(PyExc_TypeError, + "bad argument to datetime.__setstate__"); + return NULL; + } + memcpy(self->data, pdata, _PyDateTime_DATETIME_DATASIZE); + self->hashcode = -1; + + Py_INCREF(Py_None); + return Py_None; +} + +/* XXX This seems a ridiculously inefficient way to pickle a short string. */ +static PyObject * +datetime_pickler(PyObject *module, PyDateTime_DateTime *datetime) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyDateTime_CheckExact(datetime)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to datetime pickler: %s", + datetime->ob_type->tp_name); + return NULL; + } + state = datetime_getstate(datetime); + if (state) { + result = Py_BuildValue("O(O)", + datetime_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +datetime_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_DateTime *self; + + if (! PyString_CheckExact(arg)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to datetime unpickler: %s", + arg->ob_type->tp_name); + return NULL; + } + self = PyObject_New(PyDateTime_DateTime, &PyDateTime_DateTimeType); + if (self != NULL) { + PyObject *res = datetime_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef datetime_methods[] = { + /* Class methods: */ + {"now", (PyCFunction)datetime_now, + METH_NOARGS | METH_CLASS, + PyDoc_STR("Return a new datetime representing local day and time.")}, + + {"utcnow", (PyCFunction)datetime_utcnow, + METH_NOARGS | METH_CLASS, + PyDoc_STR("Return a new datetime representing UTC day and time.")}, + + {"fromtimestamp", (PyCFunction)datetime_fromtimestamp, + METH_VARARGS | METH_CLASS, + PyDoc_STR("timestamp -> local datetime from a POSIX timestamp " + "(like time.time()).")}, + + {"utcfromtimestamp", (PyCFunction)datetime_utcfromtimestamp, + METH_VARARGS | METH_CLASS, + PyDoc_STR("timestamp -> UTC datetime from a POSIX timestamp " + "(like time.time()).")}, + + {"combine", (PyCFunction)datetime_combine, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + PyDoc_STR("date, time -> datetime with same date and time fields")}, + + /* Instance methods: */ + {"timetuple", (PyCFunction)datetime_timetuple, METH_NOARGS, + PyDoc_STR("Return time tuple, compatible with time.localtime().")}, + + {"date", (PyCFunction)datetime_getdate, METH_NOARGS, + 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.")}, + + {"ctime", (PyCFunction)datetime_ctime, METH_NOARGS, + PyDoc_STR("Return ctime() style string.")}, + + {"isoformat", (PyCFunction)datetime_isoformat, METH_KEYWORDS, + PyDoc_STR("[sep] -> string in ISO 8601 format, " + "YYYY-MM-DDTHH:MM:SS[.mmmmmm].\n\n" + "sep is used to separate the year from the time, and " + "defaults\n" + "to 'T'.")}, + + {"__setstate__", (PyCFunction)datetime_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)datetime_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} +}; + +static char datetime_doc[] = +PyDoc_STR("Basic date/time type."); + +static PyNumberMethods datetime_as_number = { + datetime_add, /* nb_add */ + datetime_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_DateTimeType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.datetime", /* tp_name */ + sizeof(PyDateTime_DateTime), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)datetime_repr, /* tp_repr */ + &datetime_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)datetime_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)datetime_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + datetime_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)datetime_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + datetime_methods, /* tp_methods */ + 0, /* tp_members */ + datetime_getset, /* tp_getset */ + &PyDateTime_DateType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + datetime_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_Time implementation. + */ + +/* Accessor properties. */ + +static PyObject * +time_hour(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_HOUR(self)); +} + +static PyObject * +time_minute(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_MINUTE(self)); +} + +static PyObject * +time_second(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_SECOND(self)); +} + +static PyObject * +time_microsecond(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_MICROSECOND(self)); +} + +static PyGetSetDef time_getset[] = { + {"hour", (getter)time_hour}, + {"minute", (getter)time_minute}, + {"second", (getter)time_second}, + {"microsecond", (getter)time_microsecond}, + {NULL} +}; + +/* Constructors. */ + +static PyObject * +time_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + + static char *keywords[] = { + "hour", "minute", "second", "microsecond", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|iiii", keywords, + &hour, &minute, &second, &usecond)) { + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + self = new_time(hour, minute, second, usecond); + } + return self; +} + +/* Various ways to turn a time into a string. */ + +static PyObject * +time_repr(PyDateTime_Time *self) +{ + char buffer[100]; + char *typename = self->ob_type->tp_name; + int h = TIME_GET_HOUR(self); + int m = TIME_GET_MINUTE(self); + int s = TIME_GET_SECOND(self); + int us = TIME_GET_MICROSECOND(self); + + if (us) + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d)", typename, h, m, s, us); + else if (s) + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d)", typename, h, m, s); + else + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d)", typename, h, m); + return PyString_FromString(buffer); +} + +static PyObject * +time_str(PyDateTime_Time *self) +{ + return PyObject_CallMethod((PyObject *)self, "isoformat", "()"); +} + +static PyObject * +time_isoformat(PyDateTime_Time *self) +{ + char buffer[100]; + /* Reuse the time format code from the datetime type. */ + PyDateTime_DateTime datetime; + PyDateTime_DateTime *pdatetime = &datetime; + + /* Copy over just the time bytes. */ + memcpy(pdatetime->data + _PyDateTime_DATE_DATASIZE, + self->data, + _PyDateTime_TIME_DATASIZE); + + isoformat_time(pdatetime, buffer, sizeof(buffer)); + return PyString_FromString(buffer); +} + +static PyObject * +time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw) +{ + PyObject *result; + PyObject *format; + PyObject *tuple; + static char *keywords[] = {"format", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:strftime", keywords, + &PyString_Type, &format)) + return NULL; + + tuple = Py_BuildValue("iiiiiiiii", + 0, 0, 0, /* year, month, day */ + TIME_GET_HOUR(self), + TIME_GET_MINUTE(self), + TIME_GET_SECOND(self), + 0, 0, -1); /* weekday, daynum, dst */ + if (tuple == NULL) + return NULL; + assert(PyTuple_Size(tuple) == 9); + result = wrap_strftime((PyObject *)self, format, tuple); + Py_DECREF(tuple); + return result; +} + +/* Miscellaneous methods. */ + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + * Note that this routine handles all comparisons for time and timetz. + */ +static PyObject * +time_richcompare(PyDateTime_Time *self, PyObject *other, int op) +{ + int diff; + naivety n1, n2; + int offset1, offset2; + + if (! PyTime_Check(other)) { + /* Stop this from falling back to address comparison. */ + PyErr_Format(PyExc_TypeError, + "can't compare '%s' to '%s'", + self->ob_type->tp_name, + other->ob_type->tp_name); + return NULL; + } + n1 = classify_object((PyObject *)self, &offset1); + assert(n1 != OFFSET_UNKNOWN); + if (n1 == OFFSET_ERROR) + return NULL; + + n2 = classify_object(other, &offset2); + assert(n2 != OFFSET_UNKNOWN); + if (n2 == OFFSET_ERROR) + return NULL; + + /* If they're both naive, or both aware and have the same offsets, + * we get off cheap. Note that if they're both naive, offset1 == + * offset2 == 0 at this point. + */ + if (n1 == n2 && offset1 == offset2) { + diff = memcmp(self->data, ((PyDateTime_Time *)other)->data, + _PyDateTime_TIME_DATASIZE); + return diff_to_bool(diff, op); + } + + if (n1 == OFFSET_AWARE && n2 == OFFSET_AWARE) { + assert(offset1 != offset2); /* else last "if" handled it */ + /* Convert everything except microseconds to seconds. These + * can't overflow (no more than the # of seconds in 2 days). + */ + offset1 = TIME_GET_HOUR(self) * 3600 + + (TIME_GET_MINUTE(self) - offset1) * 60 + + TIME_GET_SECOND(self); + offset2 = TIME_GET_HOUR(other) * 3600 + + (TIME_GET_MINUTE(other) - offset2) * 60 + + TIME_GET_SECOND(other); + diff = offset1 - offset2; + if (diff == 0) + diff = TIME_GET_MICROSECOND(self) - + TIME_GET_MICROSECOND(other); + return diff_to_bool(diff, op); + } + + assert(n1 != n2); + PyErr_SetString(PyExc_TypeError, + "can't compare offset-naive and " + "offset-aware times"); + return NULL; +} + +static PyObject *time_getstate(PyDateTime_Time *self); + +static long +time_hash(PyDateTime_Time *self) +{ + if (self->hashcode == -1) { + naivety n; + int offset; + PyObject *temp; + + n = classify_object((PyObject *)self, &offset); + assert(n != OFFSET_UNKNOWN); + if (n == OFFSET_ERROR) + return -1; + + /* Reduce this to a hash of another object. */ + if (offset == 0) + temp = time_getstate(self); + else { + int hour; + int minute; + + assert(n == OFFSET_AWARE); + assert(PyTimeTZ_Check(self)); + hour = divmod(TIME_GET_HOUR(self) * 60 + + TIME_GET_MINUTE(self) - offset, + 60, + &minute); + if (0 <= hour && hour < 24) + temp = new_time(hour, minute, + TIME_GET_SECOND(self), + TIME_GET_MICROSECOND(self)); + else + temp = Py_BuildValue("iiii", + hour, minute, + TIME_GET_SECOND(self), + TIME_GET_MICROSECOND(self)); + } + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static int +time_nonzero(PyDateTime_Time *self) +{ + return TIME_GET_HOUR(self) || + TIME_GET_MINUTE(self) || + TIME_GET_SECOND(self) || + TIME_GET_MICROSECOND(self); +} + +/* Pickle support. Quite a maze! */ + +static PyObject * +time_getstate(PyDateTime_Time *self) +{ + return PyString_FromStringAndSize(self->data, + _PyDateTime_TIME_DATASIZE); +} + +static PyObject * +time_setstate(PyDateTime_Time *self, PyObject *state) +{ + const int len = PyString_Size(state); + unsigned char *pdata = (unsigned char*)PyString_AsString(state); + + if (! PyString_Check(state) || + len != _PyDateTime_TIME_DATASIZE) { + PyErr_SetString(PyExc_TypeError, + "bad argument to time.__setstate__"); + return NULL; + } + memcpy(self->data, pdata, _PyDateTime_TIME_DATASIZE); + self->hashcode = -1; + + Py_INCREF(Py_None); + return Py_None; +} + +/* XXX This seems a ridiculously inefficient way to pickle a short string. */ +static PyObject * +time_pickler(PyObject *module, PyDateTime_Time *time) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyTime_CheckExact(time)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to time pickler: %s", + time->ob_type->tp_name); + return NULL; + } + state = time_getstate(time); + if (state) { + result = Py_BuildValue("O(O)", + time_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +time_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_Time *self; + + if (! PyString_CheckExact(arg)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to time unpickler: %s", + arg->ob_type->tp_name); + return NULL; + } + self = PyObject_New(PyDateTime_Time, &PyDateTime_TimeType); + if (self != NULL) { + PyObject *res = time_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef time_methods[] = { + {"isoformat", (PyCFunction)time_isoformat, METH_KEYWORDS, + PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm].")}, + + {"strftime", (PyCFunction)time_strftime, METH_KEYWORDS, + PyDoc_STR("format -> strftime() style string.")}, + + {"__setstate__", (PyCFunction)time_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)time_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} +}; + +static char time_doc[] = +PyDoc_STR("Basic time type."); + +static PyNumberMethods time_as_number = { + 0, /* nb_add */ + 0, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + (inquiry)time_nonzero, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_TimeType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.time", /* tp_name */ + sizeof(PyDateTime_Time), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)time_repr, /* tp_repr */ + &time_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)time_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)time_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + time_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)time_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + time_methods, /* tp_methods */ + 0, /* tp_members */ + time_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + time_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_TZInfo implementation. + */ + +/* This is a pure abstract base class, so doesn't do anything beyond + * raising NotImplemented exceptions. Real tzinfo classes need + * to derive from this. This is mostly for clarity, and for efficiency in + * datetimetz and timetz constructors (their tzinfo arguments need to + * be subclasses of this tzinfo class, which is easy and quick to check). + * + * Note: For reasons having to do with pickling of subclasses, we have + * to allow tzinfo objects to be instantiated. This wasn't an issue + * in the Python implementation (__init__() could raise NotImplementedError + * there without ill effect), but doing so in the C implementation hit a + * brick wall. + */ + +static PyObject * +tzinfo_nogo(const char* methodname) +{ + PyErr_Format(PyExc_NotImplementedError, + "a tzinfo subclass must implement %s()", + methodname); + return NULL; +} + +/* Methods. A subclass must implement these. */ + +static PyObject* +tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("tzname"); +} + +static PyObject* +tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("utcoffset"); +} + +static PyObject* +tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("dst"); +} + +/* + * Pickle support. This is solely so that tzinfo subclasses can use + * pickling -- tzinfo itself is supposed to be uninstantiable. The + * pickler and unpickler functions are given module-level private + * names, and registered with copy_reg, by the module init function. + */ + +static PyObject* +tzinfo_pickler(PyDateTime_TZInfo *self) { + return Py_BuildValue("O()", tzinfo_unpickler_object); +} + +static PyObject* +tzinfo_unpickler(PyObject * unused) { + return PyType_GenericNew(&PyDateTime_TZInfoType, NULL, NULL); +} + + +static PyMethodDef tzinfo_methods[] = { + {"tzname", (PyCFunction)tzinfo_tzname, METH_O, + PyDoc_STR("datetime -> string name of time zone.")}, + + {"utcoffset", (PyCFunction)tzinfo_utcoffset, METH_O, + PyDoc_STR("datetime -> minutes east of UTC (negative for " + "west of UTC).")}, + + {"dst", (PyCFunction)tzinfo_dst, METH_O, + PyDoc_STR("datetime -> DST offset in minutes east of UTC.")}, + + {NULL, NULL} +}; + +static char tzinfo_doc[] = +PyDoc_STR("Abstract base class for time zone info objects."); + + statichere PyTypeObject PyDateTime_TZInfoType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.tzinfo", /* tp_name */ + sizeof(PyDateTime_TZInfo), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + tzinfo_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + tzinfo_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ + 0, /* tp_free */ +}; + +/* + * PyDateTime_TimeTZ implementation. + */ + +/* Accessor properties. Properties for hour, minute, second and microsecond + * are inherited from time. + */ + +static PyObject * +timetz_tzinfo(PyDateTime_TimeTZ *self, void *unused) +{ + Py_INCREF(self->tzinfo); + return self->tzinfo; +} + +static PyGetSetDef timetz_getset[] = { + {"tzinfo", (getter)timetz_tzinfo}, + {NULL} +}; + +/* + * Constructors. + */ + +static PyObject * +timetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + PyObject *tzinfo = Py_None; + + static char *keywords[] = { + "hour", "minute", "second", "microsecond", "tzinfo", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|llllO", keywords, + &hour, &minute, &second, &usecond, + &tzinfo)) { + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = new_timetz(hour, minute, second, usecond, tzinfo); + } + return self; +} + +/* + * Destructor. + */ + +static void +timetz_dealloc(PyDateTime_TimeTZ *self) +{ + Py_XDECREF(self->tzinfo); + self->ob_type->tp_free((PyObject *)self); +} + +/* + * Indirect access to tzinfo methods. One more "convenience function" and + * it won't be possible to find the useful methods anymore <0.5 wink>. + */ + +static PyObject * +timetz_convienience(PyDateTime_TimeTZ *self, char *name) +{ + PyObject *result; + + if (self->tzinfo == Py_None) { + result = Py_None; + Py_INCREF(result); + } + else + result = PyObject_CallMethod(self->tzinfo, name, "O", self); + return result; +} + +/* These are all METH_NOARGS, so don't need to check the arglist. */ +static PyObject * +timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) { + return timetz_convienience(self, "utcoffset"); +} + +static PyObject * +timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) { + return timetz_convienience(self, "tzname"); +} + +static PyObject * +timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) { + return timetz_convienience(self, "dst"); +} + +/* + * Various ways to turn a timetz into a string. + */ + +static PyObject * +timetz_repr(PyDateTime_TimeTZ *self) +{ + PyObject *baserepr = time_repr((PyDateTime_Time *)self); + + if (baserepr == NULL) + return NULL; + return append_keyword_tzinfo(baserepr, self->tzinfo); +} + +/* Note: tp_str is inherited from time. */ + +static PyObject * +timetz_isoformat(PyDateTime_TimeTZ *self) +{ + char buf[100]; + PyObject *result = time_isoformat((PyDateTime_Time *)self); + + if (result == NULL || self->tzinfo == Py_None) + return result; + + /* We need to append the UTC offset. */ + if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, + (PyObject *)self) < 0) { + Py_DECREF(result); + return NULL; + } + PyString_ConcatAndDel(&result, PyString_FromString(buf)); + return result; +} + +/* Note: strftime() is inherited from time. */ + +/* + * Miscellaneous methods. + */ + +/* Note: tp_richcompare and tp_hash are inherited from time. */ + +static int +timetz_nonzero(PyDateTime_TimeTZ *self) +{ + int offset; + int none; + + if (TIME_GET_SECOND(self) || TIME_GET_MICROSECOND(self)) { + /* Since utcoffset is in whole minutes, nothing can + * alter the conclusion that this is nonzero. + */ + return 1; + } + offset = 0; + if (self->tzinfo != Py_None) { + offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none); + if (offset == -1 && PyErr_Occurred()) + return -1; + } + return (TIME_GET_MINUTE(self) - offset + TIME_GET_HOUR(self)*60) != 0; +} + +/* + * Pickle support. Quite a maze! + */ + +/* Let basestate be the state string returned by time_getstate. + * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). + * So it's a tuple in any (non-error) case. + */ +static PyObject * +timetz_getstate(PyDateTime_TimeTZ *self) +{ + PyObject *basestate; + PyObject *result = NULL; + + basestate = time_getstate((PyDateTime_Time *)self); + if (basestate != NULL) { + if (self->tzinfo == Py_None) + result = Py_BuildValue("(O)", basestate); + else + result = Py_BuildValue("OO", basestate, self->tzinfo); + Py_DECREF(basestate); + } + return result; +} + +static PyObject * +timetz_setstate(PyDateTime_TimeTZ *self, PyObject *state) +{ + PyObject *temp; + PyObject *basestate; + PyObject *tzinfo = Py_None; + + if (! PyArg_ParseTuple(state, "O!|O:__setstate__", + &PyString_Type, &basestate, + &tzinfo)) + return NULL; + temp = time_setstate((PyDateTime_Time *)self, basestate); + if (temp == NULL) + return NULL; + Py_DECREF(temp); + + Py_INCREF(tzinfo); + Py_XDECREF(self->tzinfo); + self->tzinfo = tzinfo; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +timetz_pickler(PyObject *module, PyDateTime_TimeTZ *timetz) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyTimeTZ_CheckExact(timetz)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to timetz pickler: %s", + timetz->ob_type->tp_name); + return NULL; + } + state = timetz_getstate(timetz); + if (state) { + result = Py_BuildValue("O(O)", + timetz_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +timetz_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_TimeTZ *self; + + self = PyObject_New(PyDateTime_TimeTZ, &PyDateTime_TimeTZType); + if (self != NULL) { + PyObject *res; + + self->tzinfo = NULL; + res = timetz_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef timetz_methods[] = { + {"isoformat", (PyCFunction)timetz_isoformat, METH_KEYWORDS, + PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm]" + "[+HH:MM].")}, + + {"utcoffset", (PyCFunction)timetz_utcoffset, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, + + {"tzname", (PyCFunction)timetz_tzname, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.tzname(self).")}, + + {"dst", (PyCFunction)timetz_dst, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.dst(self).")}, + + {"__setstate__", (PyCFunction)timetz_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)timetz_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} + +}; + +static char timetz_doc[] = +PyDoc_STR("Time type."); + +static PyNumberMethods timetz_as_number = { + 0, /* nb_add */ + 0, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + (inquiry)timetz_nonzero, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_TimeTZType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.timetz", /* tp_name */ + sizeof(PyDateTime_TimeTZ), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)timetz_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)timetz_repr, /* tp_repr */ + &timetz_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + time_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + timetz_methods, /* tp_methods */ + 0, /* tp_members */ + timetz_getset, /* tp_getset */ + &PyDateTime_TimeType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + timetz_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_DateTimeTZ implementation. + */ + +/* Accessor properties. Properties for day, month, year, hour, minute, + * second and microsecond are inherited from datetime. + */ + +static PyObject * +datetimetz_tzinfo(PyDateTime_DateTimeTZ *self, void *unused) +{ + Py_INCREF(self->tzinfo); + return self->tzinfo; +} + +static PyGetSetDef datetimetz_getset[] = { + {"tzinfo", (getter)datetimetz_tzinfo}, + {NULL} +}; + +/* + * Constructors. + * These are like the datetime methods of the same names, but allow an + * optional tzinfo argument. + */ + +/* Internal helper. + * self is a datetimetz. Replace its tzinfo member. + */ +void +replace_tzinfo(PyObject *self, PyObject *newtzinfo) +{ + assert(self != NULL); + assert(newtzinfo != NULL); + assert(PyDateTimeTZ_Check(self)); + Py_INCREF(newtzinfo); + Py_DECREF(((PyDateTime_DateTimeTZ *)self)->tzinfo); + ((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo; +} + +static PyObject * +datetimetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int year; + int month; + int day; + int hour = 0; + int minute = 0; + int second = 0; + 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, + &year, &month, &day, &hour, &minute, + &second, &usecond, &tzinfo)) { + if (check_date_args(year, month, day) < 0) + return NULL; + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = new_datetimetz(year, month, day, + hour, minute, second, usecond, + tzinfo); + } + return self; +} + +/* Return best possible local time -- this isn't constrained by the + * precision of a timestamp. + */ +static PyObject * +datetimetz_now(PyObject *cls, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + PyObject *tzinfo = Py_None; + static char *keywords[] = {"tzinfo", NULL}; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|O:now", keywords, + &tzinfo)) { + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = datetime_best_possible(cls, localtime); + if (self != NULL) + replace_tzinfo(self, tzinfo); + } + return self; +} + +/* Return new local datetime from timestamp (Python timestamp -- a double). */ +static PyObject * +datetimetz_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + double timestamp; + PyObject *tzinfo = Py_None; + static char *keywords[] = {"timestamp", "tzinfo", NULL}; + + if (PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", + keywords, ×tamp, &tzinfo)) { + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = datetime_from_timestamp(cls, localtime, timestamp); + if (self != NULL) + replace_tzinfo(self, tzinfo); + } + return self; +} + +/* Note: utcnow() is inherited, and doesn't accept tzinfo. + * Ditto utcfromtimestamp(). Ditto combine(). + */ + + +/* + * Destructor. + */ + +static void +datetimetz_dealloc(PyDateTime_DateTimeTZ *self) +{ + Py_XDECREF(self->tzinfo); + self->ob_type->tp_free((PyObject *)self); +} + +/* + * Indirect access to tzinfo methods. + */ + +/* Internal helper. + * Call a tzinfo object's method, or return None if tzinfo is None. + */ +static PyObject * +datetimetz_convienience(PyDateTime_DateTimeTZ *self, char *name) +{ + PyObject *result; + + if (self->tzinfo == Py_None) { + result = Py_None; + Py_INCREF(result); + } + else + result = PyObject_CallMethod(self->tzinfo, name, "O", self); + return result; +} + +/* These are all METH_NOARGS, so don't need to check the arglist. */ +static PyObject * +datetimetz_utcoffset(PyDateTime_DateTimeTZ *self, PyObject *unused) { + return datetimetz_convienience(self, "utcoffset"); +} + +static PyObject * +datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) { + return datetimetz_convienience(self, "tzname"); +} + +static PyObject * +datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) { + return datetimetz_convienience(self, "dst"); +} + +/* + * datetimetz arithmetic. + */ + +/* If base is Py_NotImplemented or NULL, just return it. + * Else base is a datetime, exactly one of {left, right} is a datetimetz, + * and we want to create a datetimetz with the same date and time fields + * as base, and with the tzinfo field from left or right. Do that, + * return it, and decref base. This is used to transform the result of + * a binary datetime operation (base) into a datetimetz result. + */ +static PyObject * +attach_tzinfo(PyObject *base, PyObject *left, PyObject *right) +{ + PyDateTime_DateTimeTZ *self; + PyDateTime_DateTimeTZ *result; + + if (base == NULL || base == Py_NotImplemented) + return base; + + assert(PyDateTime_CheckExact(base)); + + if (PyDateTimeTZ_Check(left)) { + assert(! PyDateTimeTZ_Check(right)); + self = (PyDateTime_DateTimeTZ *)left; + } + else { + assert(PyDateTimeTZ_Check(right)); + self = (PyDateTime_DateTimeTZ *)right; + } + result = PyObject_New(PyDateTime_DateTimeTZ, + &PyDateTime_DateTimeTZType); + if (result != NULL) { + memcpy(result->data, ((PyDateTime_DateTime *)base)->data, + _PyDateTime_DATETIME_DATASIZE); + Py_INCREF(self->tzinfo); + result->tzinfo = self->tzinfo; + } + Py_DECREF(base); + return (PyObject *)result; +} + +static PyObject * +datetimetz_add(PyObject *left, PyObject *right) +{ + return attach_tzinfo(datetime_add(left, right), left, right); +} + +static PyObject * +datetimetz_subtract(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDateTime_Check(left)) { + /* datetime - ??? */ + if (PyDateTime_Check(right)) { + /* datetime - datetime */ + naivety n1, n2; + int offset1, offset2; + PyDateTime_Delta *delta; + + n1 = classify_object(left, &offset1); + assert(n1 != OFFSET_UNKNOWN); + if (n1 == OFFSET_ERROR) + return NULL; + + n2 = classify_object(right, &offset2); + assert(n2 != OFFSET_UNKNOWN); + if (n2 == OFFSET_ERROR) + return NULL; + + if (n1 != n2) { + PyErr_SetString(PyExc_TypeError, + "can't subtract offset-naive and " + "offset-aware datetimes"); + return NULL; + } + delta = (PyDateTime_Delta *)sub_datetime_datetime( + (PyDateTime_DateTime *)left, + (PyDateTime_DateTime *)right); + if (delta == NULL || offset1 == offset2) + return (PyObject *)delta; + /* (left - offset1) - (right - offset2) = + * (left - right) + (offset2 - offset1) + */ + result = new_delta(delta->days, + delta->seconds + + (offset2 - offset1) * 60, + delta->microseconds, + 1); + Py_DECREF(delta); + } + else if (PyDelta_Check(right)) { + /* datetimetz - delta */ + result = sub_datetime_timedelta( + (PyDateTime_DateTime *)left, + (PyDateTime_Delta *)right); + result = attach_tzinfo(result, left, right); + } + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* Various ways to turn a datetime into a string. */ + +static PyObject * +datetimetz_repr(PyDateTime_DateTimeTZ *self) +{ + PyObject *baserepr = datetime_repr((PyDateTime_DateTime *)self); + + if (baserepr == NULL) + return NULL; + return append_keyword_tzinfo(baserepr, self->tzinfo); +} + +/* Note: tp_str is inherited from datetime. */ + +static PyObject * +datetimetz_isoformat(PyDateTime_DateTimeTZ *self, + PyObject *args, PyObject *kw) +{ + char buf[100]; + PyObject *result = datetime_isoformat((PyDateTime_DateTime *)self, + args, kw); + + if (result == NULL || self->tzinfo == Py_None) + return result; + + /* We need to append the UTC offset. */ + if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, + (PyObject *)self) < 0) { + Py_DECREF(result); + return NULL; + } + PyString_ConcatAndDel(&result, PyString_FromString(buf)); + return result; +} + +/* Miscellaneous methods. */ + +/* Note: tp_richcompare and tp_hash are inherited from datetime. */ + +static PyObject * +datetimetz_timetuple(PyDateTime_DateTimeTZ *self) +{ + int dstflag = -1; + + if (self->tzinfo != Py_None) { + int none; + + dstflag = call_dst(self->tzinfo, (PyObject *)self, &none); + if (dstflag == -1 && PyErr_Occurred()) + return NULL; + + if (none) + dstflag = -1; + else if (dstflag != 0) + dstflag = 1; + + } + return build_struct_time(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self), + DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + dstflag); +} + +static PyObject * +datetimetz_utctimetuple(PyDateTime_DateTimeTZ *self) +{ + 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 = 0; /* microseconds are ignored in a timetuple */ + int offset = 0; + + if (self->tzinfo != Py_None) { + int none; + + offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none); + if (offset == -1 && PyErr_Occurred()) + return NULL; + } + /* Even if offset is 0, don't call timetuple() -- tm_isdst should be + * 0 in a UTC timetuple regardless of what dst() says. + */ + if (offset) { + /* Subtract offset minutes & normalize. */ + int stat; + + mm -= offset; + stat = normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us); + if (stat < 0) { + /* At the edges, it's possible we overflowed + * beyond MINYEAR or MAXYEAR. + */ + if (PyErr_ExceptionMatches(PyExc_OverflowError)) + PyErr_Clear(); + else + return NULL; + } + } + return build_struct_time(y, m, d, hh, mm, ss, 0); +} + +static PyObject * +datetimetz_gettimetz(PyDateTime_DateTimeTZ *self) +{ + return new_timetz(DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self), + self->tzinfo); +} + +/* + * Pickle support. Quite a maze! + */ + +/* Let basestate be the state string returned by datetime_getstate. + * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). + * So it's a tuple in any (non-error) case. + */ +static PyObject * +datetimetz_getstate(PyDateTime_DateTimeTZ *self) +{ + PyObject *basestate; + PyObject *result = NULL; + + basestate = datetime_getstate((PyDateTime_DateTime *)self); + if (basestate != NULL) { + if (self->tzinfo == Py_None) + result = Py_BuildValue("(O)", basestate); + else + result = Py_BuildValue("OO", basestate, self->tzinfo); + Py_DECREF(basestate); + } + return result; +} + +static PyObject * +datetimetz_setstate(PyDateTime_DateTimeTZ *self, PyObject *state) +{ + PyObject *temp; + PyObject *basestate; + PyObject *tzinfo = Py_None; + + if (! PyArg_ParseTuple(state, "O!|O:__setstate__", + &PyString_Type, &basestate, + &tzinfo)) + return NULL; + temp = datetime_setstate((PyDateTime_DateTime *)self, basestate); + if (temp == NULL) + return NULL; + Py_DECREF(temp); + + Py_INCREF(tzinfo); + Py_XDECREF(self->tzinfo); + self->tzinfo = tzinfo; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +datetimetz_pickler(PyObject *module, PyDateTime_DateTimeTZ *datetimetz) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyDateTimeTZ_CheckExact(datetimetz)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to datetimetz pickler: %s", + datetimetz->ob_type->tp_name); + return NULL; + } + state = datetimetz_getstate(datetimetz); + if (state) { + result = Py_BuildValue("O(O)", + datetimetz_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +datetimetz_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_DateTimeTZ *self; + + self = PyObject_New(PyDateTime_DateTimeTZ, &PyDateTime_DateTimeTZType); + if (self != NULL) { + PyObject *res; + + self->tzinfo = NULL; + res = datetimetz_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + + +static PyMethodDef datetimetz_methods[] = { + /* Class methods: */ + /* Inherited: combine(), utcnow(), utcfromtimestamp() */ + + {"now", (PyCFunction)datetimetz_now, + METH_KEYWORDS | METH_CLASS, + PyDoc_STR("[tzinfo] -> new datetimetz with local day and time.")}, + + {"fromtimestamp", (PyCFunction)datetimetz_fromtimestamp, + METH_KEYWORDS | METH_CLASS, + PyDoc_STR("timestamp[, tzinfo] -> local time from POSIX timestamp.")}, + + /* Instance methods: */ + /* Inherited: date(), time(), ctime(). */ + {"timetuple", (PyCFunction)datetimetz_timetuple, METH_NOARGS, + PyDoc_STR("Return time tuple, compatible with time.localtime().")}, + + {"utctimetuple", (PyCFunction)datetimetz_utctimetuple, METH_NOARGS, + PyDoc_STR("Return UTC time tuple, compatible with time.localtime().")}, + + {"timetz", (PyCFunction)datetimetz_gettimetz, METH_NOARGS, + PyDoc_STR("Return timetz object with same hour, minute, second, " + "microsecond, and tzinfo.")}, + + {"isoformat", (PyCFunction)datetimetz_isoformat, METH_KEYWORDS, + PyDoc_STR("[sep] -> string in ISO 8601 format, " + "YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].\n\n" + "sep is used to separate the year from the time, and " + "defaults to 'T'.")}, + + {"utcoffset", (PyCFunction)datetimetz_utcoffset, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, + + {"tzname", (PyCFunction)datetimetz_tzname, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.tzname(self).")}, + + {"dst", (PyCFunction)datetimetz_dst, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.dst(self).")}, + + {"__setstate__", (PyCFunction)datetimetz_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)datetimetz_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} +}; + +static char datetimetz_doc[] = +PyDoc_STR("date/time type."); + +static PyNumberMethods datetimetz_as_number = { + datetimetz_add, /* nb_add */ + datetimetz_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_DateTimeTZType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.datetimetz", /* tp_name */ + sizeof(PyDateTime_DateTimeTZ), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)datetimetz_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)datetimetz_repr, /* tp_repr */ + &datetimetz_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + datetimetz_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + datetimetz_methods, /* tp_methods */ + 0, /* tp_members */ + datetimetz_getset, /* tp_getset */ + &PyDateTime_DateTimeType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + datetimetz_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* --------------------------------------------------------------------------- + * Module methods and initialization. + */ + +static PyMethodDef module_methods[] = { + /* Private functions for pickling support, registered with the + * copy_reg module by the module init function. + */ + {"_date_pickler", (PyCFunction)date_pickler, METH_O, NULL}, + {"_date_unpickler", (PyCFunction)date_unpickler, METH_O, NULL}, + {"_datetime_pickler", (PyCFunction)datetime_pickler, METH_O, NULL}, + {"_datetime_unpickler", (PyCFunction)datetime_unpickler,METH_O, NULL}, + {"_datetimetz_pickler", (PyCFunction)datetimetz_pickler,METH_O, NULL}, + {"_datetimetz_unpickler",(PyCFunction)datetimetz_unpickler,METH_O, NULL}, + {"_time_pickler", (PyCFunction)time_pickler, METH_O, NULL}, + {"_time_unpickler", (PyCFunction)time_unpickler, METH_O, NULL}, + {"_timetz_pickler", (PyCFunction)timetz_pickler, METH_O, NULL}, + {"_timetz_unpickler", (PyCFunction)timetz_unpickler, METH_O, NULL}, + {"_tzinfo_pickler", (PyCFunction)tzinfo_pickler, METH_O, NULL}, + {"_tzinfo_unpickler", (PyCFunction)tzinfo_unpickler, METH_NOARGS, + NULL}, + {NULL, NULL} +}; + +PyMODINIT_FUNC +initdatetime(void) +{ + PyObject *m; /* a module object */ + PyObject *d; /* its dict */ + PyObject *x; + + /* Types that use __reduce__ for pickling need to set the following + * magical attr in the type dict, with a true value. + */ + PyObject *safepickle = PyString_FromString("__safe_for_unpickling__"); + if (safepickle == NULL) + return; + + m = Py_InitModule3("datetime", module_methods, + "Fast implementation of the datetime type."); + + if (PyType_Ready(&PyDateTime_DateType) < 0) + return; + if (PyType_Ready(&PyDateTime_DateTimeType) < 0) + return; + if (PyType_Ready(&PyDateTime_DeltaType) < 0) + return; + if (PyType_Ready(&PyDateTime_TimeType) < 0) + return; + if (PyType_Ready(&PyDateTime_TZInfoType) < 0) + return; + if (PyType_Ready(&PyDateTime_TimeTZType) < 0) + return; + if (PyType_Ready(&PyDateTime_DateTimeTZType) < 0) + return; + + /* Pickling support, via registering functions with copy_reg. */ + { + PyObject *pickler; + PyObject *copyreg = PyImport_ImportModule("copy_reg"); + + if (copyreg == NULL) return; + + pickler = PyObject_GetAttrString(m, "_date_pickler"); + if (pickler == NULL) return; + date_unpickler_object = PyObject_GetAttrString(m, + "_date_unpickler"); + if (date_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_DateType, + pickler, + date_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_datetime_pickler"); + if (pickler == NULL) return; + datetime_unpickler_object = PyObject_GetAttrString(m, + "_datetime_unpickler"); + if (datetime_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_DateTimeType, + pickler, + datetime_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_time_pickler"); + if (pickler == NULL) return; + time_unpickler_object = PyObject_GetAttrString(m, + "_time_unpickler"); + if (time_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_TimeType, + pickler, + time_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_timetz_pickler"); + if (pickler == NULL) return; + timetz_unpickler_object = PyObject_GetAttrString(m, + "_timetz_unpickler"); + if (timetz_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_TimeTZType, + pickler, + timetz_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_tzinfo_pickler"); + if (pickler == NULL) return; + tzinfo_unpickler_object = PyObject_GetAttrString(m, + "_tzinfo_unpickler"); + if (tzinfo_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_TZInfoType, + pickler, + tzinfo_unpickler_object); + if (x== NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_datetimetz_pickler"); + if (pickler == NULL) return; + datetimetz_unpickler_object = PyObject_GetAttrString(m, + "_datetimetz_unpickler"); + if (datetimetz_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_DateTimeTZType, + pickler, + datetimetz_unpickler_object); + if (x== NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + Py_DECREF(copyreg); + } + + /* timedelta values */ + d = PyDateTime_DeltaType.tp_dict; + + if (PyDict_SetItem(d, safepickle, Py_True) < 0) + return; + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(-MAX_DELTA_DAYS, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(MAX_DELTA_DAYS, 24*3600-1, 1000000-1, 0); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + /* date values */ + d = PyDateTime_DateType.tp_dict; + + x = new_date(1, 1, 1); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_date(MAXYEAR, 12, 31); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(1, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* datetime values */ + d = PyDateTime_DateTimeType.tp_dict; + + x = new_datetime(1, 1, 1, 0, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* time values */ + d = PyDateTime_TimeType.tp_dict; + + x = new_time(0, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_time(23, 59, 59, 999999); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* timetz values */ + d = PyDateTime_TimeTZType.tp_dict; + + x = new_timetz(0, 0, 0, 0, Py_None); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_timetz(23, 59, 59, 999999, Py_None); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* datetimetz values */ + d = PyDateTime_DateTimeTZType.tp_dict; + + x = new_datetimetz(1, 1, 1, 0, 0, 0, 0, Py_None); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_datetimetz(MAXYEAR, 12, 31, 23, 59, 59, 999999, Py_None); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + Py_DECREF(safepickle); + + /* module initialization */ + PyModule_AddIntConstant(m, "MINYEAR", MINYEAR); + PyModule_AddIntConstant(m, "MAXYEAR", MAXYEAR); + + 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); + + Py_INCREF(&PyDateTime_DateTimeTZType); + PyModule_AddObject(m, "datetimetz", + (PyObject *)&PyDateTime_DateTimeTZType); + + /* A 4-year cycle has an extra leap day over what we'd get from + * pasting together 4 single years. + */ + assert(DI4Y == 4 * 365 + 1); + assert(DI4Y == days_before_year(4+1)); + + /* Similarly, a 400-year cycle has an extra leap day over what we'd + * get from pasting together 4 100-year cycles. + */ + assert(DI400Y == 4 * DI100Y + 1); + assert(DI400Y == days_before_year(400+1)); + + /* OTOH, a 100-year cycle has one fewer leap day than we'd get from + * pasting together 25 4-year cycles. + */ + assert(DI100Y == 25 * DI4Y - 1); + assert(DI100Y == days_before_year(100+1)); + + us_per_us = PyInt_FromLong(1); + us_per_ms = PyInt_FromLong(1000); + us_per_second = PyInt_FromLong(1000000); + us_per_minute = PyInt_FromLong(60000000); + seconds_per_day = PyInt_FromLong(24 * 3600); + if (us_per_us == NULL || us_per_ms == NULL || us_per_second == NULL || + us_per_minute == NULL || seconds_per_day == NULL) + return; + + /* The rest are too big for 32-bit ints, but even + * us_per_week fits in 40 bits, so doubles should be exact. + */ + us_per_hour = PyLong_FromDouble(3600000000.0); + us_per_day = PyLong_FromDouble(86400000000.0); + us_per_week = PyLong_FromDouble(604800000000.0); + if (us_per_hour == NULL || us_per_day == NULL || us_per_week == NULL) + return; +} diff --git a/PC/dllbase_nt.txt b/PC/dllbase_nt.txt index 1a4e6772539..54b5db66840 100644 --- a/PC/dllbase_nt.txt +++ b/PC/dllbase_nt.txt @@ -5,7 +5,7 @@ is selected, and the DLL subject to fixups. Apparently, these fixups are very slow, and significant performance gains can be made by selecting a good base address. -This document is to allocate base addresses to core Python +This document is to allocate base addresses to core Python and Python .PYD files, to give a better change of optimal performance. This base address is passed to the linker using the /BASE command line switch. @@ -31,6 +31,7 @@ More standard extensions 1D100000 - 1e000000 - unicodedata 1D120000 - 1D160000 - winsound 1D160000 - 1D170000 - bZ2 1D170000 - 1D180000 + - datetime 1D180000 - 1D190000 Other extension modules - win32api 1e200000 - 1e220000 diff --git a/PCbuild/datetime.dsp b/PCbuild/datetime.dsp new file mode 100644 index 00000000000..be735ce6d49 --- /dev/null +++ b/PCbuild/datetime.dsp @@ -0,0 +1,99 @@ +# Microsoft Developer Studio Project File - Name="datetime" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=datetime - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "datetime.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "datetime.mak" CFG="datetime - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "datetime - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "datetime - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "datetime" +# PROP Scc_LocalPath ".." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "datetime - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "." +# PROP Intermediate_Dir "x86-temp-release\datetime" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +F90=df.exe +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /Zi /O2 /I "..\Include" /I "..\PC" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 user32.lib kernel32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib /nologo /base:"0x1D180000" /subsystem:windows /dll /debug /machine:I386 /nodefaultlib:"libc" /out:"./datetime.pyd" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "datetime - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "." +# PROP Intermediate_Dir "x86-temp-debug\datetime" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +F90=df.exe +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /Zi /Od /I "..\Include" /I "..\PC" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 user32.lib kernel32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib /nologo /base:"0x1D180000" /subsystem:windows /dll /debug /machine:I386 /nodefaultlib:"libc" /nodefaultlib:"msvcrt" /out:"./datetime_d.pyd" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "datetime - Win32 Release" +# Name "datetime - Win32 Debug" +# Begin Source File + +SOURCE=..\Modules\datetimemodule.c +# End Source File +# End Target +# End Project diff --git a/PCbuild/pcbuild.dsw b/PCbuild/pcbuild.dsw index b6d65c74ff9..7e80aaa86fd 100644 --- a/PCbuild/pcbuild.dsw +++ b/PCbuild/pcbuild.dsw @@ -129,6 +129,21 @@ Package=<4> ############################################################################### +Project: "datetime"=.\datetime.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name pythoncore + End Project Dependency +}}} + +############################################################################### + Project: "mmap"=.\mmap.dsp - Package Owner=<4> Package=<5> diff --git a/PCbuild/python20.wse b/PCbuild/python20.wse index 91ad5831097..239e6b79509 100644 --- a/PCbuild/python20.wse +++ b/PCbuild/python20.wse @@ -1739,6 +1739,11 @@ item: Install File Destination=%MAINDIR%\DLLs\bz2.pyd Flags=0000000000000010 end +item: Install File + Source=.\datetime.pyd + Destination=%MAINDIR%\DLLs\datetime.pyd + Flags=0000000000000010 +end item: Install File Source=.\mmap.pyd Destination=%MAINDIR%\DLLs\mmap.pyd @@ -1826,6 +1831,11 @@ item: Install File Destination=%MAINDIR%\libs\bz2.lib Flags=0000000000000010 end +item: Install File + Source=.\datetime.lib + Destination=%MAINDIR%\libs\datetime.lib + Flags=0000000000000010 +end item: Install File Source=.\mmap.lib Destination=%MAINDIR%\libs\mmap.lib diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index f565c5aec24..b6cd15c3aaa 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -48,6 +48,8 @@ _symtable _testcapi tests of the Python C API, run via Lib/test/test_capi.py, and implemented by module Modules/_testcapimodule.c +datetime + datetimemodule.c mmap mmapmodule.c parser