From cf86e368ebd17e10f68306ebad314eea31daaa1e Mon Sep 17 00:00:00 2001 From: Alexander Belopolsky Date: Fri, 23 Jul 2010 19:25:47 +0000 Subject: Issue #7989: Added pure python implementation of the datetime module. --- Lib/datetime.py | 2087 +++++++++++++++++ Lib/test/datetimetester.py | 3674 +++++++++++++++++++++++++++++ Lib/test/test_datetime.py | 3698 +---------------------------- Misc/NEWS | 8 + Modules/Setup.dist | 2 +- Modules/_datetimemodule.c | 5488 ++++++++++++++++++++++++++++++++++++++++++++ Modules/datetimemodule.c | 5488 -------------------------------------------- PC/config.c | 4 +- PCbuild/pythoncore.vcproj | 2 +- setup.py | 2 +- 10 files changed, 11298 insertions(+), 9155 deletions(-) create mode 100644 Lib/datetime.py create mode 100644 Lib/test/datetimetester.py create mode 100644 Modules/_datetimemodule.c delete mode 100644 Modules/datetimemodule.c diff --git a/Lib/datetime.py b/Lib/datetime.py new file mode 100644 index 0000000..7b7b8aa --- /dev/null +++ b/Lib/datetime.py @@ -0,0 +1,2087 @@ +"""Concrete date/time and related types -- prototype implemented in Python. + +See http://www.zope.org/Members/fdrake/DateTimeWiki/FrontPage + +See also http://dir.yahoo.com/Reference/calendars/ + +For a primer on DST, including many current DST rules, see +http://webexhibits.org/daylightsaving/ + +For more about DST than you ever wanted to know, see +ftp://elsie.nci.nih.gov/pub/ + +Sources for time zone and DST data: http://www.twinsun.com/tz/tz-link.htm + +This was originally copied from the sandbox of the CPython CVS repository. +Thanks to Tim Peters for suggesting using it. +""" + +import time as _time +import math as _math + +def _cmp(x, y): + return 0 if x == y else 1 if x > y else -1 + +MINYEAR = 1 +MAXYEAR = 9999 +_MAXORDINAL = 3652059 # date.max.toordinal() + +# Utility functions, adapted from Python's Demo/classes/Dates.py, which +# also assumes the current Gregorian calendar indefinitely extended in +# both directions. Difference: Dates.py calls January 1 of year 0 day +# number 1. The code here calls January 1 of year 1 day number 1. This is +# to match the definition of the "proleptic Gregorian" calendar in Dershowitz +# and Reingold's "Calendrical Calculations", where it's the base calendar +# for all computations. See the book for algorithms for converting between +# proleptic Gregorian ordinals and many other calendar systems. + +_DAYS_IN_MONTH = [None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + +_DAYS_BEFORE_MONTH = [None] +dbm = 0 +for dim in _DAYS_IN_MONTH[1:]: + _DAYS_BEFORE_MONTH.append(dbm) + dbm += dim +del dbm, dim + +def _is_leap(year): + "year -> 1 if leap year, else 0." + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + +def _days_before_year(year): + "year -> number of days before January 1st of year." + y = year - 1 + return y*365 + y//4 - y//100 + y//400 + +def _days_in_month(year, month): + "year, month -> number of days in that month in that year." + assert 1 <= month <= 12, month + if month == 2 and _is_leap(year): + return 29 + return _DAYS_IN_MONTH[month] + +def _days_before_month(year, month): + "year, month -> number of days in year preceeding first day of month." + assert 1 <= month <= 12, 'month must be in 1..12' + return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year)) + +def _ymd2ord(year, month, day): + "year, month, day -> ordinal, considering 01-Jan-0001 as day 1." + assert 1 <= month <= 12, 'month must be in 1..12' + dim = _days_in_month(year, month) + assert 1 <= day <= dim, ('day must be in 1..%d' % dim) + return (_days_before_year(year) + + _days_before_month(year, month) + + day) + +_DI400Y = _days_before_year(401) # number of days in 400 years +_DI100Y = _days_before_year(101) # " " " " 100 " +_DI4Y = _days_before_year(5) # " " " " 4 " + +# 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 + +# 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 + +# 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 + +def _ord2ymd(n): + "ordinal -> (year, month, day), considering 01-Jan-0001 as day 1." + + # n 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 n, then work with the offset + # from that boundary to n. Life is much clearer if we subtract 1 from + # n first -- then the values of n 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 + n -= 1 + n400, n = divmod(n, _DI400Y) + year = n400 * 400 + 1 # ..., -399, 1, 401, ... + + # 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 = divmod(n, _DI100Y) + + # Now compute how many 4-year cycles precede it. + n4, n = divmod(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 = divmod(n, 365) + + year += n100 * 100 + n4 * 4 + n1 + if n1 == 4 or n100 == 4: + assert n == 0 + return year-1, 12, 31 + + # 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 and (n4 != 24 or n100 == 3) + assert leapyear == _is_leap(year) + month = (n + 50) >> 5 + preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear) + if preceding > n: # estimate is too large + month -= 1 + preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear) + n -= preceding + assert 0 <= n < _days_in_month(year, month) + + # Now the year and month are correct, and n is the offset from the + # start of that month: we're done! + return year, month, n+1 + +# Month and day names. For localized versions, see the calendar module. +_MONTHNAMES = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] +_DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + + +def _build_struct_time(y, m, d, hh, mm, ss, dstflag): + wday = (_ymd2ord(y, m, d) + 6) % 7 + dnum = _days_before_month(y, m) + d + return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag)) + +def _format_time(hh, mm, ss, us): + # Skip trailing microseconds when us==0. + result = "%02d:%02d:%02d" % (hh, mm, ss) + if us: + result += ".%06d" % us + return result + +# Correctly substitute for %z and %Z escapes in strftime formats. +def _wrap_strftime(object, format, timetuple): + year = timetuple[0] + if year < 1900: + raise ValueError("year=%d is before 1900; the datetime strftime() " + "methods require year >= 1900" % year) + # Don't call utcoffset() or tzname() unless actually needed. + freplace = None # the string to use for %f + zreplace = None # the string to use for %z + Zreplace = None # the string to use for %Z + + # Scan format for %z and %Z escapes, replacing as needed. + newformat = [] + push = newformat.append + i, n = 0, len(format) + while i < n: + ch = format[i] + i += 1 + if ch == '%': + if i < n: + ch = format[i] + i += 1 + if ch == 'f': + if freplace is None: + freplace = '%06d' % getattr(object, + 'microsecond', 0) + newformat.append(freplace) + elif ch == 'z': + if zreplace is None: + zreplace = "" + if hasattr(object, "utcoffset"): + offset = object.utcoffset() + if offset is not None: + sign = '+' + if offset.days < 0: + offset = -offset + sign = '-' + h, m = divmod(offset, timedelta(hours=1)) + assert not m % timedelta(minutes=1), "whole minute" + m //= timedelta(minutes=1) + zreplace = '%c%02d%02d' % (sign, h, m) + assert '%' not in zreplace + newformat.append(zreplace) + elif ch == 'Z': + if Zreplace is None: + Zreplace = "" + if hasattr(object, "tzname"): + s = object.tzname() + if s is not None: + # strftime is going to have at this: escape % + Zreplace = s.replace('%', '%%') + newformat.append(Zreplace) + else: + push('%') + push(ch) + else: + push('%') + else: + push(ch) + newformat = "".join(newformat) + return _time.strftime(newformat, timetuple) + +def _call_tzinfo_method(tzinfo, methname, tzinfoarg): + if tzinfo is None: + return None + return getattr(tzinfo, methname)(tzinfoarg) + +# Just raise TypeError if the arg isn't None or a string. +def _check_tzname(name): + if name is not None and not isinstance(name, str): + raise TypeError("tzinfo.tzname() must return None or string, " + "not '%s'" % type(name)) + +# name is the offset-producing method, "utcoffset" or "dst". +# offset is what it returned. +# If offset isn't None or timedelta, raises TypeError. +# If offset is None, returns None. +# Else offset is checked for being in range, and a whole # of minutes. +# If it is, its integer value is returned. Else ValueError is raised. +def _check_utc_offset(name, offset): + assert name in ("utcoffset", "dst") + if offset is None: + return + if not isinstance(offset, timedelta): + raise TypeError("tzinfo.%s() must return None " + "or timedelta, not '%s'" % (name, type(offset))) + if offset % timedelta(minutes=1) or offset.microseconds: + raise ValueError("tzinfo.%s() must return a whole number " + "of minutes, got %s" % (name, offset)) + if not -timedelta(1) < offset < timedelta(1): + raise ValueError("%s()=%s, must be must be strictly between" + " -timedelta(hours=24) and timedelta(hours=24)" + % (name, offset)) + +def _check_date_fields(year, month, day): + if not isinstance(year, int): + raise TypeError('int expected') + if not MINYEAR <= year <= MAXYEAR: + raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year) + if not 1 <= month <= 12: + raise ValueError('month must be in 1..12', month) + dim = _days_in_month(year, month) + if not 1 <= day <= dim: + raise ValueError('day must be in 1..%d' % dim, day) + +def _check_time_fields(hour, minute, second, microsecond): + if not isinstance(hour, int): + raise TypeError('int expected') + if not 0 <= hour <= 23: + raise ValueError('hour must be in 0..23', hour) + if not 0 <= minute <= 59: + raise ValueError('minute must be in 0..59', minute) + if not 0 <= second <= 59: + raise ValueError('second must be in 0..59', second) + if not 0 <= microsecond <= 999999: + raise ValueError('microsecond must be in 0..999999', microsecond) + +def _check_tzinfo_arg(tz): + if tz is not None and not isinstance(tz, tzinfo): + raise TypeError("tzinfo argument must be None or of a tzinfo subclass") + +def _cmperror(x, y): + raise TypeError("can't compare '%s' to '%s'" % ( + type(x).__name__, type(y).__name__)) + +class timedelta: + """Represent the difference between two datetime objects. + + Supported operators: + + - add, subtract timedelta + - unary plus, minus, abs + - compare to timedelta + - multiply, divide by int/long + + In addition, datetime supports subtraction of two datetime objects + returning a timedelta, and addition or subtraction of a datetime + and a timedelta giving a datetime. + + Representation: (days, seconds, microseconds). Why? Because I + felt like it. + """ + __slots__ = '_days', '_seconds', '_microseconds' + + def __new__(cls, days=0, seconds=0, microseconds=0, + milliseconds=0, minutes=0, hours=0, weeks=0): + # Doing this efficiently and accurately in C is going to be difficult + # and error-prone, due to ubiquitous overflow possibilities, and that + # C double doesn't have enough bits of precision to represent + # microseconds over 10K years faithfully. The code here tries to make + # explicit where go-fast assumptions can be relied on, in order to + # guide the C implementation; it's way more convoluted than speed- + # ignoring auto-overflow-to-long idiomatic Python could be. + + # XXX Check that all inputs are ints or floats. + + # Final values, all integer. + # s and us fit in 32-bit signed ints; d isn't bounded. + d = s = us = 0 + + # Normalize everything to days, seconds, microseconds. + days += weeks*7 + seconds += minutes*60 + hours*3600 + microseconds += milliseconds*1000 + + # Get rid of all fractions, and normalize s and us. + # Take a deep breath . + if isinstance(days, float): + dayfrac, days = _math.modf(days) + daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.)) + assert daysecondswhole == int(daysecondswhole) # can't overflow + s = int(daysecondswhole) + assert days == int(days) + d = int(days) + else: + daysecondsfrac = 0.0 + d = days + assert isinstance(daysecondsfrac, float) + assert abs(daysecondsfrac) <= 1.0 + assert isinstance(d, int) + assert abs(s) <= 24 * 3600 + # days isn't referenced again before redefinition + + if isinstance(seconds, float): + secondsfrac, seconds = _math.modf(seconds) + assert seconds == int(seconds) + seconds = int(seconds) + secondsfrac += daysecondsfrac + assert abs(secondsfrac) <= 2.0 + else: + secondsfrac = daysecondsfrac + # daysecondsfrac isn't referenced again + assert isinstance(secondsfrac, float) + assert abs(secondsfrac) <= 2.0 + + assert isinstance(seconds, int) + days, seconds = divmod(seconds, 24*3600) + d += days + s += int(seconds) # can't overflow + assert isinstance(s, int) + assert abs(s) <= 2 * 24 * 3600 + # seconds isn't referenced again before redefinition + + usdouble = secondsfrac * 1e6 + assert abs(usdouble) < 2.1e6 # exact value not critical + # secondsfrac isn't referenced again + + if isinstance(microseconds, float): + microseconds += usdouble + microseconds = round(microseconds, 0) + seconds, microseconds = divmod(microseconds, 1e6) + assert microseconds == int(microseconds) + assert seconds == int(seconds) + days, seconds = divmod(seconds, 24.*3600.) + assert days == int(days) + assert seconds == int(seconds) + d += int(days) + s += int(seconds) # can't overflow + assert isinstance(s, int) + assert abs(s) <= 3 * 24 * 3600 + else: + seconds, microseconds = divmod(microseconds, 1000000) + days, seconds = divmod(seconds, 24*3600) + d += days + s += int(seconds) # can't overflow + assert isinstance(s, int) + assert abs(s) <= 3 * 24 * 3600 + microseconds = float(microseconds) + microseconds += usdouble + microseconds = round(microseconds, 0) + assert abs(s) <= 3 * 24 * 3600 + assert abs(microseconds) < 3.1e6 + + # Just a little bit of carrying possible for microseconds and seconds. + assert isinstance(microseconds, float) + assert int(microseconds) == microseconds + us = int(microseconds) + seconds, us = divmod(us, 1000000) + s += seconds # cant't overflow + assert isinstance(s, int) + days, s = divmod(s, 24*3600) + d += days + + assert isinstance(d, int) + assert isinstance(s, int) and 0 <= s < 24*3600 + assert isinstance(us, int) and 0 <= us < 1000000 + + self = object.__new__(cls) + + self._days = d + self._seconds = s + self._microseconds = us + if abs(d) > 999999999: + raise OverflowError("timedelta # of days is too large: %d" % d) + + return self + + def __repr__(self): + if self._microseconds: + return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__, + self._days, + self._seconds, + self._microseconds) + if self._seconds: + return "%s(%d, %d)" % ('datetime.' + self.__class__.__name__, + self._days, + self._seconds) + return "%s(%d)" % ('datetime.' + self.__class__.__name__, self._days) + + def __str__(self): + mm, ss = divmod(self._seconds, 60) + hh, mm = divmod(mm, 60) + s = "%d:%02d:%02d" % (hh, mm, ss) + if self._days: + def plural(n): + return n, abs(n) != 1 and "s" or "" + s = ("%d day%s, " % plural(self._days)) + s + if self._microseconds: + s = s + ".%06d" % self._microseconds + return s + + def total_seconds(self): + """Total seconds in the duration.""" + return ((self.days * 86400 + self.seconds)*10**6 + + self.microseconds) / 10**6 + + # Read-only field accessors + @property + def days(self): + """days""" + return self._days + + @property + def seconds(self): + """seconds""" + return self._seconds + + @property + def microseconds(self): + """microseconds""" + return self._microseconds + + def __add__(self, other): + if isinstance(other, timedelta): + # for CPython compatibility, we cannot use + # our __class__ here, but need a real timedelta + return timedelta(self._days + other._days, + self._seconds + other._seconds, + self._microseconds + other._microseconds) + return NotImplemented + + __radd__ = __add__ + + def __sub__(self, other): + if isinstance(other, timedelta): + return self + -other + return NotImplemented + + def __rsub__(self, other): + if isinstance(other, timedelta): + return -self + other + return NotImplemented + + def __neg__(self): + # for CPython compatibility, we cannot use + # our __class__ here, but need a real timedelta + return timedelta(-self._days, + -self._seconds, + -self._microseconds) + + def __pos__(self): + return self + + def __abs__(self): + if self._days < 0: + return -self + else: + return self + + def __mul__(self, other): + if isinstance(other, int): + # for CPython compatibility, we cannot use + # our __class__ here, but need a real timedelta + return timedelta(self._days * other, + self._seconds * other, + self._microseconds * other) + if isinstance(other, float): + a, b = other.as_integer_ratio() + return self * a / b + return NotImplemented + + __rmul__ = __mul__ + + def _to_microseconds(self): + return ((self._days * (24*3600) + self._seconds) * 1000000 + + self._microseconds) + + def __floordiv__(self, other): + if not isinstance(other, (int, timedelta)): + return NotImplemented + usec = self._to_microseconds() + if isinstance(other, timedelta): + return usec // other._to_microseconds() + if isinstance(other, int): + return timedelta(0, 0, usec // other) + + def __truediv__(self, other): + if not isinstance(other, (int, float, timedelta)): + return NotImplemented + usec = self._to_microseconds() + if isinstance(other, timedelta): + return usec / other._to_microseconds() + if isinstance(other, int): + return timedelta(0, 0, usec / other) + if isinstance(other, float): + a, b = other.as_integer_ratio() + return timedelta(0, 0, b * usec / a) + + def __mod__(self, other): + if isinstance(other, timedelta): + r = self._to_microseconds() % other._to_microseconds() + return timedelta(0, 0, r) + return NotImplemented + + def __divmod__(self, other): + if isinstance(other, timedelta): + q, r = divmod(self._to_microseconds(), + other._to_microseconds()) + return q, timedelta(0, 0, r) + return NotImplemented + + # Comparisons of timedelta objects with other. + + def __eq__(self, other): + if isinstance(other, timedelta): + return self._cmp(other) == 0 + else: + return False + + def __ne__(self, other): + if isinstance(other, timedelta): + return self._cmp(other) != 0 + else: + return True + + def __le__(self, other): + if isinstance(other, timedelta): + return self._cmp(other) <= 0 + else: + _cmperror(self, other) + + def __lt__(self, other): + if isinstance(other, timedelta): + return self._cmp(other) < 0 + else: + _cmperror(self, other) + + def __ge__(self, other): + if isinstance(other, timedelta): + return self._cmp(other) >= 0 + else: + _cmperror(self, other) + + def __gt__(self, other): + if isinstance(other, timedelta): + return self._cmp(other) > 0 + else: + _cmperror(self, other) + + def _cmp(self, other): + assert isinstance(other, timedelta) + return _cmp(self._getstate(), other._getstate()) + + def __hash__(self): + return hash(self._getstate()) + + def __bool__(self): + return (self._days != 0 or + self._seconds != 0 or + self._microseconds != 0) + + # Pickle support. + + def _getstate(self): + return (self._days, self._seconds, self._microseconds) + + def __reduce__(self): + return (self.__class__, self._getstate()) + +timedelta.min = timedelta(-999999999) +timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59, + microseconds=999999) +timedelta.resolution = timedelta(microseconds=1) + +class date: + """Concrete date type. + + Constructors: + + __new__() + fromtimestamp() + today() + fromordinal() + + Operators: + + __repr__, __str__ + __cmp__, __hash__ + __add__, __radd__, __sub__ (add/radd only with timedelta arg) + + Methods: + + timetuple() + toordinal() + weekday() + isoweekday(), isocalendar(), isoformat() + ctime() + strftime() + + Properties (readonly): + year, month, day + """ + __slots__ = '_year', '_month', '_day' + + def __new__(cls, year, month=None, day=None): + """Constructor. + + Arguments: + + year, month, day (required, base 1) + """ + if (isinstance(year, bytes) and len(year) == 4 and + 1 <= year[2] <= 12 and month is None): # Month is sane + # Pickle support + self = object.__new__(cls) + self.__setstate(year) + return self + _check_date_fields(year, month, day) + self = object.__new__(cls) + self._year = year + self._month = month + self._day = day + return self + + # Additional constructors + + @classmethod + def fromtimestamp(cls, t): + "Construct a date from a POSIX timestamp (like time.time())." + y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t) + return cls(y, m, d) + + @classmethod + def today(cls): + "Construct a date from time.time()." + t = _time.time() + return cls.fromtimestamp(t) + + @classmethod + def fromordinal(cls, n): + """Contruct a date from a proleptic Gregorian ordinal. + + January 1 of year 1 is day 1. Only the year, month and day are + non-zero in the result. + """ + y, m, d = _ord2ymd(n) + return cls(y, m, d) + + # Conversions to string + + def __repr__(self): + """Convert to formal string, for repr(). + + >>> dt = datetime(2010, 1, 1) + >>> repr(dt) + 'datetime.datetime(2010, 1, 1, 0, 0)' + + >>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc) + >>> repr(dt) + 'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)' + """ + return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__, + self._year, + self._month, + self._day) + # XXX These shouldn't depend on time.localtime(), because that + # clips the usable dates to [1970 .. 2038). At least ctime() is + # easily done without using strftime() -- that's better too because + # strftime("%c", ...) is locale specific. + + + def ctime(self): + "Return ctime() style string." + weekday = self.toordinal() % 7 or 7 + return "%s %s %2d 00:00:00 %04d" % ( + _DAYNAMES[weekday], + _MONTHNAMES[self._month], + self._day, self._year) + + def strftime(self, fmt): + "Format using strftime()." + return _wrap_strftime(self, fmt, self.timetuple()) + + def __format__(self, fmt): + if len(fmt) != 0: + return self.strftime(fmt) + return str(self) + + def isoformat(self): + """Return the date formatted according to ISO. + + This is 'YYYY-MM-DD'. + + References: + - http://www.w3.org/TR/NOTE-datetime + - http://www.cl.cam.ac.uk/~mgk25/iso-time.html + """ + return "%04d-%02d-%02d" % (self._year, self._month, self._day) + + __str__ = isoformat + + # Read-only field accessors + @property + def year(self): + """year (1-9999)""" + return self._year + + @property + def month(self): + """month (1-12)""" + return self._month + + @property + def day(self): + """day (1-31)""" + return self._day + + # Standard conversions, __cmp__, __hash__ (and helpers) + + def timetuple(self): + "Return local time tuple compatible with time.localtime()." + return _build_struct_time(self._year, self._month, self._day, + 0, 0, 0, -1) + + def toordinal(self): + """Return proleptic Gregorian ordinal for the year, month and day. + + January 1 of year 1 is day 1. Only the year, month and day values + contribute to the result. + """ + return _ymd2ord(self._year, self._month, self._day) + + def replace(self, year=None, month=None, day=None): + """Return a new date with new values for the specified fields.""" + if year is None: + year = self._year + if month is None: + month = self._month + if day is None: + day = self._day + _check_date_fields(year, month, day) + return date(year, month, day) + + # Comparisons of date objects with other. + + def __eq__(self, other): + if isinstance(other, date): + return self._cmp(other) == 0 + return NotImplemented + + def __ne__(self, other): + if isinstance(other, date): + return self._cmp(other) != 0 + return NotImplemented + + def __le__(self, other): + if isinstance(other, date): + return self._cmp(other) <= 0 + return NotImplemented + + def __lt__(self, other): + if isinstance(other, date): + return self._cmp(other) < 0 + return NotImplemented + + def __ge__(self, other): + if isinstance(other, date): + return self._cmp(other) >= 0 + return NotImplemented + + def __gt__(self, other): + if isinstance(other, date): + return self._cmp(other) > 0 + return NotImplemented + + def _cmp(self, other): + assert isinstance(other, date) + y, m, d = self._year, self._month, self._day + y2, m2, d2 = other._year, other._month, other._day + return _cmp((y, m, d), (y2, m2, d2)) + + def __hash__(self): + "Hash." + return hash(self._getstate()) + + # Computations + + def __add__(self, other): + "Add a date to a timedelta." + if isinstance(other, timedelta): + o = self.toordinal() + other.days + if 0 < o <= _MAXORDINAL: + return date.fromordinal(o) + raise OverflowError("result out of range") + return NotImplemented + + __radd__ = __add__ + + def __sub__(self, other): + """Subtract two dates, or a date and a timedelta.""" + if isinstance(other, timedelta): + return self + timedelta(-other.days) + if isinstance(other, date): + days1 = self.toordinal() + days2 = other.toordinal() + return timedelta(days1 - days2) + return NotImplemented + + def weekday(self): + "Return day of the week, where Monday == 0 ... Sunday == 6." + return (self.toordinal() + 6) % 7 + + # Day-of-the-week and week-of-the-year, according to ISO + + def isoweekday(self): + "Return day of the week, where Monday == 1 ... Sunday == 7." + # 1-Jan-0001 is a Monday + return self.toordinal() % 7 or 7 + + def isocalendar(self): + """Return a 3-tuple containing ISO year, week number, and weekday. + + The first ISO week of the year is the (Mon-Sun) week + containing the year's first Thursday; everything else derives + from that. + + The first week is 1; Monday is 1 ... Sunday is 7. + + ISO calendar algorithm taken from + http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm + """ + year = self._year + week1monday = _isoweek1monday(year) + today = _ymd2ord(self._year, self._month, self._day) + # Internally, week and day have origin 0 + week, day = divmod(today - week1monday, 7) + if week < 0: + year -= 1 + week1monday = _isoweek1monday(year) + week, day = divmod(today - week1monday, 7) + elif week >= 52: + if today >= _isoweek1monday(year+1): + year += 1 + week = 0 + return year, week+1, day+1 + + # Pickle support. + + def _getstate(self): + yhi, ylo = divmod(self._year, 256) + return bytes([yhi, ylo, self._month, self._day]), + + def __setstate(self, string): + if len(string) != 4 or not (1 <= string[2] <= 12): + raise TypeError("not enough arguments") + yhi, ylo, self._month, self._day = string + self._year = yhi * 256 + ylo + + def __reduce__(self): + return (self.__class__, self._getstate()) + +_date_class = date # so functions w/ args named "date" can get at the class + +date.min = date(1, 1, 1) +date.max = date(9999, 12, 31) +date.resolution = timedelta(days=1) + +class tzinfo: + """Abstract base class for time zone info classes. + + Subclasses must override the name(), utcoffset() and dst() methods. + """ + __slots__ = () + def tzname(self, dt): + "datetime -> string name of time zone." + raise NotImplementedError("tzinfo subclass must override tzname()") + + def utcoffset(self, dt): + "datetime -> minutes east of UTC (negative for west of UTC)" + raise NotImplementedError("tzinfo subclass must override utcoffset()") + + def dst(self, dt): + """datetime -> DST offset in minutes east of UTC. + + Return 0 if DST not in effect. utcoffset() must include the DST + offset. + """ + raise NotImplementedError("tzinfo subclass must override dst()") + + def fromutc(self, dt): + "datetime in UTC -> datetime in local time." + + if not isinstance(dt, datetime): + raise TypeError("fromutc() requires a datetime argument") + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + dtoff = dt.utcoffset() + if dtoff is None: + raise ValueError("fromutc() requires a non-None utcoffset() " + "result") + + # See the long comment block at the end of this file for an + # explanation of this algorithm. + dtdst = dt.dst() + if dtdst is None: + raise ValueError("fromutc() requires a non-None dst() result") + delta = dtoff - dtdst + if delta: + dt += delta + dtdst = dt.dst() + if dtdst is None: + raise ValueError("fromutc(): dt.dst gave inconsistent " + "results; cannot convert") + return dt + dtdst + + # Pickle support. + + def __reduce__(self): + getinitargs = getattr(self, "__getinitargs__", None) + if getinitargs: + args = getinitargs() + else: + args = () + getstate = getattr(self, "__getstate__", None) + if getstate: + state = getstate() + else: + state = getattr(self, "__dict__", None) or None + if state is None: + return (self.__class__, args) + else: + return (self.__class__, args, state) + +_tzinfo_class = tzinfo + +class time: + """Time with time zone. + + Constructors: + + __new__() + + Operators: + + __repr__, __str__ + __cmp__, __hash__ + + Methods: + + strftime() + isoformat() + utcoffset() + tzname() + dst() + + Properties (readonly): + hour, minute, second, microsecond, tzinfo + """ + + def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None): + """Constructor. + + Arguments: + + hour, minute (required) + second, microsecond (default to zero) + tzinfo (default to None) + """ + self = object.__new__(cls) + if isinstance(hour, bytes) and len(hour) == 6: + # Pickle support + self.__setstate(hour, minute or None) + return self + _check_tzinfo_arg(tzinfo) + _check_time_fields(hour, minute, second, microsecond) + self._hour = hour + self._minute = minute + self._second = second + self._microsecond = microsecond + self._tzinfo = tzinfo + return self + + # Read-only field accessors + @property + def hour(self): + """hour (0-23)""" + return self._hour + + @property + def minute(self): + """minute (0-59)""" + return self._minute + + @property + def second(self): + """second (0-59)""" + return self._second + + @property + def microsecond(self): + """microsecond (0-999999)""" + return self._microsecond + + @property + def tzinfo(self): + """timezone info object""" + return self._tzinfo + + # Standard conversions, __hash__ (and helpers) + + # Comparisons of time objects with other. + + def __eq__(self, other): + if isinstance(other, time): + return self._cmp(other) == 0 + else: + return False + + def __ne__(self, other): + if isinstance(other, time): + return self._cmp(other) != 0 + else: + return True + + def __le__(self, other): + if isinstance(other, time): + return self._cmp(other) <= 0 + else: + _cmperror(self, other) + + def __lt__(self, other): + if isinstance(other, time): + return self._cmp(other) < 0 + else: + _cmperror(self, other) + + def __ge__(self, other): + if isinstance(other, time): + return self._cmp(other) >= 0 + else: + _cmperror(self, other) + + def __gt__(self, other): + if isinstance(other, time): + return self._cmp(other) > 0 + else: + _cmperror(self, other) + + def _cmp(self, other): + assert isinstance(other, time) + mytz = self._tzinfo + ottz = other._tzinfo + myoff = otoff = None + + if mytz is ottz: + base_compare = True + else: + myoff = self.utcoffset() + otoff = other.utcoffset() + base_compare = myoff == otoff + + if base_compare: + return _cmp((self._hour, self._minute, self._second, + self._microsecond), + (other._hour, other._minute, other._second, + other._microsecond)) + if myoff is None or otoff is None: + raise TypeError("cannot compare naive and aware times") + myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1) + othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1) + return _cmp((myhhmm, self._second, self._microsecond), + (othhmm, other._second, other._microsecond)) + + def __hash__(self): + """Hash.""" + tzoff = self.utcoffset() + if not tzoff: # zero or None + return hash(self._getstate()[0]) + h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff, + timedelta(hours=1)) + assert not m % timedelta(minutes=1), "whole minute" + m //= timedelta(minutes=1) + if 0 <= h < 24: + return hash(time(h, m, self.second, self.microsecond)) + return hash((h, m, self.second, self.microsecond)) + + # Conversion to string + + def _tzstr(self, sep=":"): + """Return formatted timezone offset (+xx:xx) or None.""" + off = self.utcoffset() + if off is not None: + if off.days < 0: + sign = "-" + off = -off + else: + sign = "+" + hh, mm = divmod(off, timedelta(hours=1)) + assert not mm % timedelta(minutes=1), "whole minute" + mm //= timedelta(minutes=1) + assert 0 <= hh < 24 + off = "%s%02d%s%02d" % (sign, hh, sep, mm) + return off + + def __repr__(self): + """Convert to formal string, for repr().""" + if self._microsecond != 0: + s = ", %d, %d" % (self._second, self._microsecond) + elif self._second != 0: + s = ", %d" % self._second + else: + s = "" + s= "%s(%d, %d%s)" % ('datetime.' + self.__class__.__name__, + self._hour, self._minute, s) + if self._tzinfo is not None: + assert s[-1:] == ")" + s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" + return s + + def isoformat(self): + """Return the time formatted according to ISO. + + This is 'HH:MM:SS.mmmmmm+zz:zz', or 'HH:MM:SS+zz:zz' if + self.microsecond == 0. + """ + s = _format_time(self._hour, self._minute, self._second, + self._microsecond) + tz = self._tzstr() + if tz: + s += tz + return s + + __str__ = isoformat + + def strftime(self, fmt): + """Format using strftime(). The date part of the timestamp passed + to underlying strftime should not be used. + """ + # The year must be >= 1900 else Python's strftime implementation + # can raise a bogus exception. + timetuple = (1900, 1, 1, + self._hour, self._minute, self._second, + 0, 1, -1) + return _wrap_strftime(self, fmt, timetuple) + + def __format__(self, fmt): + if len(fmt) != 0: + return self.strftime(fmt) + return str(self) + + # Timezone functions + + def utcoffset(self): + """Return the timezone offset in minutes east of UTC (negative west of + UTC).""" + if self._tzinfo is None: + return None + offset = self._tzinfo.utcoffset(None) + _check_utc_offset("utcoffset", offset) + return offset + + def tzname(self): + """Return the timezone name. + + Note that the name is 100% informational -- there's no requirement that + it mean anything in particular. For example, "GMT", "UTC", "-500", + "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies. + """ + if self._tzinfo is None: + return None + name = self._tzinfo.tzname(None) + _check_tzname(name) + return name + + def dst(self): + """Return 0 if DST is not in effect, or the DST offset (in minutes + eastward) if DST is in effect. + + This is purely informational; the DST offset has already been added to + the UTC offset returned by utcoffset() if applicable, so there's no + need to consult dst() unless you're interested in displaying the DST + info. + """ + if self._tzinfo is None: + return None + offset = self._tzinfo.dst(None) + _check_utc_offset("dst", offset) + return offset + + def replace(self, hour=None, minute=None, second=None, microsecond=None, + tzinfo=True): + """Return a new time with new values for the specified fields.""" + if hour is None: + hour = self.hour + if minute is None: + minute = self.minute + if second is None: + second = self.second + if microsecond is None: + microsecond = self.microsecond + if tzinfo is True: + tzinfo = self.tzinfo + _check_time_fields(hour, minute, second, microsecond) + _check_tzinfo_arg(tzinfo) + return time(hour, minute, second, microsecond, tzinfo) + + def __bool__(self): + if self.second or self.microsecond: + return True + offset = self.utcoffset() or timedelta(0) + return timedelta(hours=self.hour, minutes=self.minute) != offset + + # Pickle support. + + def _getstate(self): + us2, us3 = divmod(self._microsecond, 256) + us1, us2 = divmod(us2, 256) + basestate = bytes([self._hour, self._minute, self._second, + us1, us2, us3]) + if self._tzinfo is None: + return (basestate,) + else: + return (basestate, self._tzinfo) + + def __setstate(self, string, tzinfo): + if len(string) != 6 or string[0] >= 24: + raise TypeError("an integer is required") + (self._hour, self._minute, self._second, + us1, us2, us3) = string + self._microsecond = (((us1 << 8) | us2) << 8) | us3 + if tzinfo is None or isinstance(tzinfo, _tzinfo_class): + self._tzinfo = tzinfo + else: + raise TypeError("bad tzinfo state arg %r" % tzinfo) + + def __reduce__(self): + return (time, self._getstate()) + +_time_class = time # so functions w/ args named "time" can get at the class + +time.min = time(0, 0, 0) +time.max = time(23, 59, 59, 999999) +time.resolution = timedelta(microseconds=1) + +class datetime(date): + """datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]) + + The year, month and day arguments are required. tzinfo may be None, or an + instance of a tzinfo subclass. The remaining arguments may be ints or longs. + """ + + __slots__ = date.__slots__ + ( + '_hour', '_minute', '_second', + '_microsecond', '_tzinfo') + def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, + microsecond=0, tzinfo=None): + if isinstance(year, bytes) and len(year) == 10: + # Pickle support + self = date.__new__(cls, year[:4]) + self.__setstate(year, month) + return self + _check_tzinfo_arg(tzinfo) + _check_time_fields(hour, minute, second, microsecond) + self = date.__new__(cls, year, month, day) + self._hour = hour + self._minute = minute + self._second = second + self._microsecond = microsecond + self._tzinfo = tzinfo + return self + + # Read-only field accessors + @property + def hour(self): + """hour (0-23)""" + return self._hour + + @property + def minute(self): + """minute (0-59)""" + return self._minute + + @property + def second(self): + """second (0-59)""" + return self._second + + @property + def microsecond(self): + """microsecond (0-999999)""" + return self._microsecond + + @property + def tzinfo(self): + """timezone info object""" + return self._tzinfo + + @classmethod + def fromtimestamp(cls, t, tz=None): + """Construct a datetime from a POSIX timestamp (like time.time()). + + A timezone info object may be passed in as well. + """ + + _check_tzinfo_arg(tz) + if tz is None: + converter = _time.localtime + else: + converter = _time.gmtime + if 1 - (t % 1.0) < 0.000001: + t = float(int(t)) + 1 + if t < 0: + t -= 1 + y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) + us = int((t % 1.0) * 1000000) + ss = min(ss, 59) # clamp out leap seconds if the platform has them + result = cls(y, m, d, hh, mm, ss, us, tz) + if tz is not None: + result = tz.fromutc(result) + return result + + @classmethod + def utcfromtimestamp(cls, t): + "Construct a UTC datetime from a POSIX timestamp (like time.time())." + if 1 - (t % 1.0) < 0.000001: + t = float(int(t)) + 1 + if t < 0: + t -= 1 + y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t) + us = int((t % 1.0) * 1000000) + ss = min(ss, 59) # clamp out leap seconds if the platform has them + return cls(y, m, d, hh, mm, ss, us) + + # XXX This is supposed to do better than we *can* do by using time.time(), + # XXX if the platform supports a more accurate way. The C implementation + # XXX uses gettimeofday on platforms that have it, but that isn't + # XXX available from Python. So now() may return different results + # XXX across the implementations. + @classmethod + def now(cls, tz=None): + "Construct a datetime from time.time() and optional time zone info." + t = _time.time() + return cls.fromtimestamp(t, tz) + + @classmethod + def utcnow(cls): + "Construct a UTC datetime from time.time()." + t = _time.time() + return cls.utcfromtimestamp(t) + + @classmethod + def combine(cls, date, time): + "Construct a datetime from a given date and a given time." + if not isinstance(date, _date_class): + raise TypeError("date argument must be a date instance") + if not isinstance(time, _time_class): + raise TypeError("time argument must be a time instance") + return cls(date.year, date.month, date.day, + time.hour, time.minute, time.second, time.microsecond, + time.tzinfo) + + def timetuple(self): + "Return local time tuple compatible with time.localtime()." + dst = self.dst() + if dst is None: + dst = -1 + elif dst: + dst = 1 + else: + dst = 0 + return _build_struct_time(self.year, self.month, self.day, + self.hour, self.minute, self.second, + dst) + + def utctimetuple(self): + "Return UTC time tuple compatible with time.gmtime()." + offset = self.utcoffset() + if offset: + self -= offset + y, m, d = self.year, self.month, self.day + hh, mm, ss = self.hour, self.minute, self.second + return _build_struct_time(y, m, d, hh, mm, ss, 0) + + def date(self): + "Return the date part." + return date(self._year, self._month, self._day) + + def time(self): + "Return the time part, with tzinfo None." + return time(self.hour, self.minute, self.second, self.microsecond) + + def timetz(self): + "Return the time part, with same tzinfo." + return time(self.hour, self.minute, self.second, self.microsecond, + self._tzinfo) + + def replace(self, year=None, month=None, day=None, hour=None, + minute=None, second=None, microsecond=None, tzinfo=True): + """Return a new datetime with new values for the specified fields.""" + if year is None: + year = self.year + if month is None: + month = self.month + if day is None: + day = self.day + if hour is None: + hour = self.hour + if minute is None: + minute = self.minute + if second is None: + second = self.second + if microsecond is None: + microsecond = self.microsecond + if tzinfo is True: + tzinfo = self.tzinfo + _check_date_fields(year, month, day) + _check_time_fields(hour, minute, second, microsecond) + _check_tzinfo_arg(tzinfo) + return datetime(year, month, day, hour, minute, second, + microsecond, tzinfo) + + def astimezone(self, tz): + if not isinstance(tz, tzinfo): + raise TypeError("tz argument must be an instance of tzinfo") + + mytz = self.tzinfo + if mytz is None: + raise ValueError("astimezone() requires an aware datetime") + + if tz is mytz: + return self + + # Convert self to UTC, and attach the new time zone object. + myoffset = self.utcoffset() + if myoffset is None: + raise ValueError("astimezone() requires an aware datetime") + utc = (self - myoffset).replace(tzinfo=tz) + + # Convert from UTC to tz's local time. + return tz.fromutc(utc) + + # Ways to produce a string. + + def ctime(self): + "Return ctime() style string." + weekday = self.toordinal() % 7 or 7 + return "%s %s %2d %02d:%02d:%02d %04d" % ( + _DAYNAMES[weekday], + _MONTHNAMES[self._month], + self._day, + self._hour, self._minute, self._second, + self._year) + + def isoformat(self, sep='T'): + """Return the time formatted according to ISO. + + This is 'YYYY-MM-DD HH:MM:SS.mmmmmm', or 'YYYY-MM-DD HH:MM:SS' if + self.microsecond == 0. + + If self.tzinfo is not None, the UTC offset is also attached, giving + 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM' or 'YYYY-MM-DD HH:MM:SS+HH:MM'. + + Optional argument sep specifies the separator between date and + time, default 'T'. + """ + s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, + sep) + + _format_time(self._hour, self._minute, self._second, + self._microsecond)) + off = self.utcoffset() + if off is not None: + if off.days < 0: + sign = "-" + off = -off + else: + sign = "+" + hh, mm = divmod(off, timedelta(hours=1)) + assert not mm % timedelta(minutes=1), "whole minute" + mm //= timedelta(minutes=1) + s += "%s%02d:%02d" % (sign, hh, mm) + return s + + def __repr__(self): + """Convert to formal string, for repr().""" + L = [self._year, self._month, self._day, # These are never zero + self._hour, self._minute, self._second, self._microsecond] + if L[-1] == 0: + del L[-1] + if L[-1] == 0: + del L[-1] + s = ", ".join(map(str, L)) + s = "%s(%s)" % ('datetime.' + self.__class__.__name__, s) + if self._tzinfo is not None: + assert s[-1:] == ")" + s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" + return s + + def __str__(self): + "Convert to string, for str()." + return self.isoformat(sep=' ') + + @classmethod + def strptime(cls, date_string, format): + 'string, format -> new datetime parsed from a string (like time.strptime()).' + import _strptime + return _strptime._strptime_datetime(cls, date_string, format) + + def utcoffset(self): + """Return the timezone offset in minutes east of UTC (negative west of + UTC).""" + if self._tzinfo is None: + return None + offset = self._tzinfo.utcoffset(self) + _check_utc_offset("utcoffset", offset) + return offset + + def tzname(self): + """Return the timezone name. + + Note that the name is 100% informational -- there's no requirement that + it mean anything in particular. For example, "GMT", "UTC", "-500", + "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies. + """ + name = _call_tzinfo_method(self._tzinfo, "tzname", self) + _check_tzname(name) + return name + + def dst(self): + """Return 0 if DST is not in effect, or the DST offset (in minutes + eastward) if DST is in effect. + + This is purely informational; the DST offset has already been added to + the UTC offset returned by utcoffset() if applicable, so there's no + need to consult dst() unless you're interested in displaying the DST + info. + """ + if self._tzinfo is None: + return None + offset = self._tzinfo.dst(self) + _check_utc_offset("dst", offset) + return offset + + # Comparisons of datetime objects with other. + + def __eq__(self, other): + if isinstance(other, datetime): + return self._cmp(other) == 0 + elif not isinstance(other, date): + return NotImplemented + else: + return False + + def __ne__(self, other): + if isinstance(other, datetime): + return self._cmp(other) != 0 + elif not isinstance(other, date): + return NotImplemented + else: + return True + + def __le__(self, other): + if isinstance(other, datetime): + return self._cmp(other) <= 0 + elif not isinstance(other, date): + return NotImplemented + else: + _cmperror(self, other) + + def __lt__(self, other): + if isinstance(other, datetime): + return self._cmp(other) < 0 + elif not isinstance(other, date): + return NotImplemented + else: + _cmperror(self, other) + + def __ge__(self, other): + if isinstance(other, datetime): + return self._cmp(other) >= 0 + elif not isinstance(other, date): + return NotImplemented + else: + _cmperror(self, other) + + def __gt__(self, other): + if isinstance(other, datetime): + return self._cmp(other) > 0 + elif not isinstance(other, date): + return NotImplemented + else: + _cmperror(self, other) + + def _cmp(self, other): + assert isinstance(other, datetime) + mytz = self._tzinfo + ottz = other._tzinfo + myoff = otoff = None + + if mytz is ottz: + base_compare = True + else: + if mytz is not None: + myoff = self.utcoffset() + if ottz is not None: + otoff = other.utcoffset() + base_compare = myoff == otoff + + if base_compare: + return _cmp((self._year, self._month, self._day, + self._hour, self._minute, self._second, + self._microsecond), + (other._year, other._month, other._day, + other._hour, other._minute, other._second, + other._microsecond)) + if myoff is None or otoff is None: + raise TypeError("cannot compare naive and aware datetimes") + # XXX What follows could be done more efficiently... + diff = self - other # this will take offsets into account + if diff.days < 0: + return -1 + return diff and 1 or 0 + + def __add__(self, other): + "Add a datetime and a timedelta." + if not isinstance(other, timedelta): + return NotImplemented + delta = timedelta(self.toordinal(), + hours=self._hour, + minutes=self._minute, + seconds=self._second, + microseconds=self._microsecond) + delta += other + hour, rem = divmod(delta.seconds, 3600) + minute, second = divmod(rem, 60) + if 0 < delta.days <= _MAXORDINAL: + return datetime.combine(date.fromordinal(delta.days), + time(hour, minute, second, + delta.microseconds, + tzinfo=self._tzinfo)) + raise OverflowError("result out of range") + + __radd__ = __add__ + + def __sub__(self, other): + "Subtract two datetimes, or a datetime and a timedelta." + if not isinstance(other, datetime): + if isinstance(other, timedelta): + return self + -other + return NotImplemented + + days1 = self.toordinal() + days2 = other.toordinal() + secs1 = self._second + self._minute * 60 + self._hour * 3600 + secs2 = other._second + other._minute * 60 + other._hour * 3600 + base = timedelta(days1 - days2, + secs1 - secs2, + self._microsecond - other._microsecond) + if self._tzinfo is other._tzinfo: + return base + myoff = self.utcoffset() + otoff = other.utcoffset() + if myoff == otoff: + return base + if myoff is None or otoff is None: + raise TypeError("cannot mix naive and timezone-aware time") + return base + otoff - myoff + + def __hash__(self): + tzoff = self.utcoffset() + if tzoff is None: + return hash(self._getstate()[0]) + days = _ymd2ord(self.year, self.month, self.day) + seconds = self.hour * 3600 + self.minute * 60 + self.second + return hash(timedelta(days, seconds, self.microsecond) - tzoff) + + # Pickle support. + + def _getstate(self): + yhi, ylo = divmod(self._year, 256) + us2, us3 = divmod(self._microsecond, 256) + us1, us2 = divmod(us2, 256) + basestate = bytes([yhi, ylo, self._month, self._day, + self._hour, self._minute, self._second, + us1, us2, us3]) + if self._tzinfo is None: + return (basestate,) + else: + return (basestate, self._tzinfo) + + def __setstate(self, string, tzinfo): + (yhi, ylo, self._month, self._day, self._hour, + self._minute, self._second, us1, us2, us3) = string + self._year = yhi * 256 + ylo + self._microsecond = (((us1 << 8) | us2) << 8) | us3 + if tzinfo is None or isinstance(tzinfo, _tzinfo_class): + self._tzinfo = tzinfo + else: + raise TypeError("bad tzinfo state arg %r" % tzinfo) + + def __reduce__(self): + return (self.__class__, self._getstate()) + + +datetime.min = datetime(1, 1, 1) +datetime.max = datetime(9999, 12, 31, 23, 59, 59, 999999) +datetime.resolution = timedelta(microseconds=1) + + +def _isoweek1monday(year): + # Helper to calculate the day number of the Monday starting week 1 + # XXX This could be done more efficiently + THURSDAY = 3 + firstday = _ymd2ord(year, 1, 1) + firstweekday = (firstday + 6) % 7 # See weekday() above + week1monday = firstday - firstweekday + if firstweekday > THURSDAY: + week1monday += 7 + return week1monday + +class timezone(tzinfo): + __slots__ = '_offset', '_name' + + # Sentinel value to disallow None + _Omitted = object() + def __init__(self, offset, name=_Omitted): + if name is self._Omitted: + name = None + elif not isinstance(name, str): + raise TypeError("name must be a string") + if isinstance(offset, timedelta): + if self._minoffset <= offset <= self._maxoffset: + if (offset.microseconds != 0 or + offset.seconds % 60 != 0): + raise ValueError("offset must be whole" + " number of minutes") + self._offset = offset + else: + raise ValueError("offset out of range") + else: + raise TypeError("offset must be timedelta") + + self._name = name + + def __getinitargs__(self): + """pickle support""" + if self._name is None: + return (self._offset,) + return (self._offset, self._name) + + def __eq__(self, other): + return self._offset == other._offset + + def __hash__(self): + return hash(self._offset) + + def __repr__(self): + """Convert to formal string, for repr(). + + >>> tz = timezone.utc + >>> repr(tz) + 'datetime.timezone.utc' + >>> tz = timezone(timedelta(hours=-5), 'EST') + >>> repr(tz) + "datetime.timezone(datetime.timedelta(-1, 68400), 'EST')" + """ + if self is self.utc: + return 'datetime.timezone.utc' + if self._name is None: + return "%s(%r)" % ('datetime.' + self.__class__.__name__, + self._offset) + return "%s(%r, %r)" % ('datetime.' + self.__class__.__name__, + self._offset, self._name) + + def __str__(self): + return self.tzname(None) + + def utcoffset(self, dt): + if isinstance(dt, datetime) or dt is None: + return self._offset + raise TypeError("utcoffset() argument must be a datetime instance" + " or None") + + def tzname(self, dt): + if isinstance(dt, datetime) or dt is None: + if self._name is None: + return self._name_from_offset(self._offset) + return self._name + raise TypeError("tzname() argument must be a datetime instance" + " or None") + + def dst(self, dt): + if isinstance(dt, datetime) or dt is None: + return None + raise TypeError("dst() argument must be a datetime instance" + " or None") + + def fromutc(self, dt): + if isinstance(dt, datetime): + if dt.tzinfo is not self: + raise ValueError("fromutc: dt.tzinfo " + "is not self") + return dt + self._offset + raise TypeError("fromutc() argument must be a datetime instance" + " or None") + + _maxoffset = timedelta(hours=23, minutes=59) + _minoffset = -_maxoffset + + @staticmethod + def _name_from_offset(delta): + if delta < timedelta(0): + sign = '-' + delta = -delta + else: + sign = '+' + hours, rest = divmod(delta, timedelta(hours=1)) + minutes = rest // timedelta(minutes=1) + return 'UTC{}{:02d}:{:02d}'.format(sign, hours, minutes) + +timezone.utc = timezone(timedelta(0)) +timezone.min = timezone(timezone._minoffset) +timezone.max = timezone(timezone._maxoffset) + +""" +Some time zone algebra. For a datetime x, let + x.n = x stripped of its timezone -- its naive time. + x.o = x.utcoffset(), and assuming that doesn't raise an exception or + return None + x.d = x.dst(), and assuming that doesn't raise an exception or + return None + x.s = x's standard offset, x.o - x.d + +Now some derived rules, where k is a duration (timedelta). + +1. x.o = x.s + x.d + This follows from the definition of x.s. + +2. If x and y have the same tzinfo member, x.s = y.s. + This is actually a requirement, an assumption we need to make about + sane tzinfo classes. + +3. The naive UTC time corresponding to x is x.n - x.o. + This is again a requirement for a sane tzinfo class. + +4. (x+k).s = x.s + This follows from #2, and that datimetimetz+timedelta preserves tzinfo. + +5. (x+k).n = x.n + k + Again follows from how arithmetic is defined. + +Now we can explain tz.fromutc(x). Let's assume it's an interesting case +(meaning that the various tzinfo methods exist, and don't blow up or return +None when called). + +The function wants to return a datetime y with timezone tz, equivalent to x. +x is already in UTC. + +By #3, we want + + y.n - y.o = x.n [1] + +The algorithm starts by attaching tz to x.n, and calling that y. So +x.n = y.n at the start. Then it wants to add a duration k to y, so that [1] +becomes true; in effect, we want to solve [2] for k: + + (y+k).n - (y+k).o = x.n [2] + +By #1, this is the same as + + (y+k).n - ((y+k).s + (y+k).d) = x.n [3] + +By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start. +Substituting that into [3], + + x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving + k - (y+k).s - (y+k).d = 0; rearranging, + k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so + k = y.s - (y+k).d + +On the RHS, (y+k).d can't be computed directly, but y.s can be, and we +approximate k by ignoring the (y+k).d term at first. Note that k can't be +very large, since all offset-returning methods return a duration of magnitude +less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must +be 0, so ignoring it has no consequence then. + +In any case, the new value is + + z = y + y.s [4] + +It's helpful to step back at look at [4] from a higher level: it's simply +mapping from UTC to tz's standard time. + +At this point, if + + z.n - z.o = x.n [5] + +we have an equivalent time, and are almost done. The insecurity here is +at the start of daylight time. Picture US Eastern for concreteness. The wall +time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good +sense then. The docs ask that an Eastern tzinfo class consider such a time to +be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST +on the day DST starts. We want to return the 1:MM EST spelling because that's +the only spelling that makes sense on the local wall clock. + +In fact, if [5] holds at this point, we do have the standard-time spelling, +but that takes a bit of proof. We first prove a stronger result. What's the +difference between the LHS and RHS of [5]? Let + + diff = x.n - (z.n - z.o) [6] + +Now + z.n = by [4] + (y + y.s).n = by #5 + y.n + y.s = since y.n = x.n + x.n + y.s = since z and y are have the same tzinfo member, + y.s = z.s by #2 + x.n + z.s + +Plugging that back into [6] gives + + diff = + x.n - ((x.n + z.s) - z.o) = expanding + x.n - x.n - z.s + z.o = cancelling + - z.s + z.o = by #2 + z.d + +So diff = z.d. + +If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time +spelling we wanted in the endcase described above. We're done. Contrarily, +if z.d = 0, then we have a UTC equivalent, and are also done. + +If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to +add to z (in effect, z is in tz's standard time, and we need to shift the +local clock into tz's daylight time). + +Let + + z' = z + z.d = z + diff [7] + +and we can again ask whether + + z'.n - z'.o = x.n [8] + +If so, we're done. If not, the tzinfo class is insane, according to the +assumptions we've made. This also requires a bit of proof. As before, let's +compute the difference between the LHS and RHS of [8] (and skipping some of +the justifications for the kinds of substitutions we've done several times +already): + + diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7] + x.n - (z.n + diff - z'.o) = replacing diff via [6] + x.n - (z.n + x.n - (z.n - z.o) - z'.o) = + x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n + - z.n + z.n - z.o + z'.o = cancel z.n + - z.o + z'.o = #1 twice + -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo + z'.d - z.d + +So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal, +we've found the UTC-equivalent so are done. In fact, we stop with [7] and +return z', not bothering to compute z'.d. + +How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by +a dst() offset, and starting *from* a time already in DST (we know z.d != 0), +would have to change the result dst() returns: we start in DST, and moving +a little further into it takes us out of DST. + +There isn't a sane case where this can happen. The closest it gets is at +the end of DST, where there's an hour in UTC with no spelling in a hybrid +tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During +that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM +UTC) because the docs insist on that, but 0:MM is taken as being in daylight +time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local +clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in +standard time. Since that's what the local clock *does*, we want to map both +UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous +in local time, but so it goes -- it's the way the local clock works. + +When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0, +so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going. +z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8] +(correctly) concludes that z' is not UTC-equivalent to x. + +Because we know z.d said z was in daylight time (else [5] would have held and +we would have stopped then), and we know z.d != z'.d (else [8] would have held +and we we have stopped then), and there are only 2 possible values dst() can +return in Eastern, it follows that z'.d must be 0 (which it is in the example, +but the reasoning doesn't depend on the example -- it depends on there being +two possible dst() outcomes, one zero and the other non-zero). Therefore +z' must be in standard time, and is the spelling we want in this case. + +Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is +concerned (because it takes z' as being in standard time rather than the +daylight time we intend here), but returning it gives the real-life "local +clock repeats an hour" behavior when mapping the "unspellable" UTC hour into +tz. + +When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with +the 1:MM standard time spelling we want. + +So how can this break? One of the assumptions must be violated. Two +possibilities: + +1) [2] effectively says that y.s is invariant across all y belong to a given + time zone. This isn't true if, for political reasons or continental drift, + a region decides to change its base offset from UTC. + +2) There may be versions of "double daylight" time where the tail end of + the analysis gives up a step too early. I haven't thought about that + enough to say. + +In any case, it's clear that the default fromutc() is strong enough to handle +"almost all" time zones: so long as the standard offset is invariant, it +doesn't matter if daylight time transition points change from year to year, or +if daylight time is skipped in some years; it doesn't matter how large or +small dst() may get within its bounds; and it doesn't even matter if some +perverse time zone returns a negative dst()). So a breaking case must be +pretty bizarre, and a tzinfo subclass can override fromutc() if it is. +""" +try: + from _datetime import * +except ImportError: + pass +else: + # Clean up unused names + del (_DAYNAMES, _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH, + _DI100Y, _DI400Y, _DI4Y, _MAXORDINAL, _MONTHNAMES, + _build_struct_time, _call_tzinfo_method, _check_date_fields, + _check_time_fields, _check_tzinfo_arg, _check_tzname, + _check_utc_offset, _cmp, _cmperror, _date_class, _days_before_month, + _days_before_year, _days_in_month, _format_time, _is_leap, + _isoweek1monday, _math, _ord2ymd, _time, _time_class, _tzinfo_class, + _wrap_strftime, _ymd2ord) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py new file mode 100644 index 0000000..8be72a4 --- /dev/null +++ b/Lib/test/datetimetester.py @@ -0,0 +1,3674 @@ +"""Test date/time type. + +See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases +""" + +import sys +import pickle +import unittest + +from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod + +from test import support + +import datetime as datetime_module +from datetime import MINYEAR, MAXYEAR +from datetime import timedelta +from datetime import tzinfo +from datetime import time +from datetime import timezone +from datetime import date, datetime +import time as _time + +# Needed by test_datetime +import _strptime +# + + +pickle_choices = [(pickle, pickle, proto) + for proto in range(pickle.HIGHEST_PROTOCOL + 1)] +assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1 + +# An arbitrary collection of objects of non-datetime types, for testing +# mixed-type comparisons. +OTHERSTUFF = (10, 34.5, "abc", {}, [], ()) + + +# XXX Copied from test_float. +INF = float("inf") +NAN = float("nan") + +# decorator for skipping tests on non-IEEE 754 platforms +requires_IEEE_754 = unittest.skipUnless( + float.__getformat__("double").startswith("IEEE"), + "test requires IEEE 754 doubles") + + +############################################################################# +# module tests + +class TestModule(unittest.TestCase): + + def test_constants(self): + datetime = datetime_module + self.assertEqual(datetime.MINYEAR, 1) + self.assertEqual(datetime.MAXYEAR, 9999) + +############################################################################# +# tzinfo tests + +class FixedOffset(tzinfo): + + def __init__(self, offset, name, dstoffset=42): + if isinstance(offset, int): + offset = timedelta(minutes=offset) + if isinstance(dstoffset, int): + dstoffset = timedelta(minutes=dstoffset) + 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 PicklableFixedOffset(FixedOffset): + + def __init__(self, offset=None, name=None, dstoffset=None): + FixedOffset.__init__(self, offset, name, 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.assertTrue(issubclass(NotEnough, tzinfo)) + ne = NotEnough(3, "NotByALongShot") + self.assertIsInstance(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.assertIsInstance(fo, tzinfo) + for dt in datetime.now(), None: + self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3)) + self.assertEqual(fo.tzname(dt), "Three") + self.assertEqual(fo.dst(dt), timedelta(minutes=42)) + + def test_pickling_base(self): + # 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.assertTrue(type(orig) is tzinfo) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertTrue(type(derived) is tzinfo) + + def test_pickling_subclass(self): + # Make sure we can pickle/unpickle an instance of a subclass. + offset = timedelta(minutes=-300) + for otype, args in [ + (PicklableFixedOffset, (offset, 'cookie')), + (timezone, (offset,)), + (timezone, (offset, "EST"))]: + orig = otype(*args) + oname = orig.tzname(None) + self.assertIsInstance(orig, tzinfo) + self.assertIs(type(orig), otype) + self.assertEqual(orig.utcoffset(None), offset) + self.assertEqual(orig.tzname(None), oname) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertIsInstance(derived, tzinfo) + self.assertIs(type(derived), otype) + self.assertEqual(derived.utcoffset(None), offset) + self.assertEqual(derived.tzname(None), oname) + +class TestTimeZone(unittest.TestCase): + + def setUp(self): + self.ACDT = timezone(timedelta(hours=9.5), 'ACDT') + self.EST = timezone(-timedelta(hours=5), 'EST') + self.DT = datetime(2010, 1, 1) + + def test_str(self): + for tz in [self.ACDT, self.EST, timezone.utc, + timezone.min, timezone.max]: + self.assertEqual(str(tz), tz.tzname(None)) + + def test_repr(self): + datetime = datetime_module + for tz in [self.ACDT, self.EST, timezone.utc, + timezone.min, timezone.max]: + # test round-trip + tzrep = repr(tz) + self.assertEqual(tz, eval(tzrep)) + + + def test_class_members(self): + limit = timedelta(hours=23, minutes=59) + self.assertEqual(timezone.utc.utcoffset(None), ZERO) + self.assertEqual(timezone.min.utcoffset(None), -limit) + self.assertEqual(timezone.max.utcoffset(None), limit) + + + def test_constructor(self): + self.assertEqual(timezone.utc, timezone(timedelta(0))) + # invalid offsets + for invalid in [timedelta(microseconds=1), timedelta(1, 1), + timedelta(seconds=1), timedelta(1), -timedelta(1)]: + self.assertRaises(ValueError, timezone, invalid) + self.assertRaises(ValueError, timezone, -invalid) + + with self.assertRaises(TypeError): timezone(None) + with self.assertRaises(TypeError): timezone(42) + with self.assertRaises(TypeError): timezone(ZERO, None) + with self.assertRaises(TypeError): timezone(ZERO, 42) + with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra') + + def test_inheritance(self): + self.assertIsInstance(timezone.utc, tzinfo) + self.assertIsInstance(self.EST, tzinfo) + + def test_utcoffset(self): + dummy = self.DT + for h in [0, 1.5, 12]: + offset = h * HOUR + self.assertEqual(offset, timezone(offset).utcoffset(dummy)) + self.assertEqual(-offset, timezone(-offset).utcoffset(dummy)) + + with self.assertRaises(TypeError): self.EST.utcoffset('') + with self.assertRaises(TypeError): self.EST.utcoffset(5) + + + def test_dst(self): + self.assertEqual(None, timezone.utc.dst(self.DT)) + + with self.assertRaises(TypeError): self.EST.dst('') + with self.assertRaises(TypeError): self.EST.dst(5) + + def test_tzname(self): + self.assertEqual('UTC+00:00', timezone(ZERO).tzname(None)) + self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None)) + self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None)) + self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None)) + self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None)) + + with self.assertRaises(TypeError): self.EST.tzname('') + with self.assertRaises(TypeError): self.EST.tzname(5) + + def test_fromutc(self): + with self.assertRaises(ValueError): + timezone.utc.fromutc(self.DT) + with self.assertRaises(TypeError): + timezone.utc.fromutc('not datetime') + for tz in [self.EST, self.ACDT, Eastern]: + utctime = self.DT.replace(tzinfo=tz) + local = tz.fromutc(utctime) + self.assertEqual(local - utctime, tz.utcoffset(local)) + self.assertEqual(local, + self.DT.replace(tzinfo=timezone.utc)) + + def test_comparison(self): + self.assertNotEqual(timezone(ZERO), timezone(HOUR)) + self.assertEqual(timezone(HOUR), timezone(HOUR)) + self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST')) + with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO) + self.assertIn(timezone(ZERO), {timezone(ZERO)}) + + def test_aware_datetime(self): + # test that timezone instances can be used by datetime + t = datetime(1, 1, 1) + for tz in [timezone.min, timezone.max, timezone.utc]: + self.assertEqual(tz.tzname(t), + t.replace(tzinfo=tz).tzname()) + self.assertEqual(tz.utcoffset(t), + t.replace(tzinfo=tz).utcoffset()) + self.assertEqual(tz.dst(t), + t.replace(tzinfo=tz).dst()) + +############################################################################# +# Base clase for testing a particular aspect of timedelta, time, date and +# datetime comparisons. + +class HarmlessMixedComparison: + # Test that __eq__ and __ne__ don't complain for mixed-type comparisons. + + # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a + # legit constructor. + + def test_harmless_mixed_comparison(self): + me = self.theclass(1, 1, 1) + + self.assertFalse(me == ()) + self.assertTrue(me != ()) + self.assertFalse(() == me) + self.assertTrue(() != me) + + self.assertIn(me, [1, 20, [], me]) + self.assertIn([], [me, 1, 20, []]) + + def test_harmful_mixed_comparison(self): + me = self.theclass(1, 1, 1) + + self.assertRaises(TypeError, lambda: me < ()) + self.assertRaises(TypeError, lambda: me <= ()) + self.assertRaises(TypeError, lambda: me > ()) + self.assertRaises(TypeError, lambda: me >= ()) + + self.assertRaises(TypeError, lambda: () < me) + self.assertRaises(TypeError, lambda: () <= me) + self.assertRaises(TypeError, lambda: () > me) + self.assertRaises(TypeError, lambda: () >= me) + +############################################################################# +# timedelta tests + +class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): + + theclass = timedelta + + 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(b.__rsub__(a), 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*10, 10*a) + eq(b*10, td(0, 600)) + eq(10*b, td(0, 600)) + eq(b*10, td(0, 600)) + eq(c*10, td(0, 0, 10000)) + eq(10*c, td(0, 0, 10000)) + eq(c*10, 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)) + eq(a/0.5, td(14)) + eq(b/0.5, td(0, 120)) + 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)) + + # Multiplication by float + us = td(microseconds=1) + eq((3*us) * 0.5, 2*us) + eq((5*us) * 0.5, 2*us) + eq(0.5 * (3*us), 2*us) + eq(0.5 * (5*us), 2*us) + eq((-3*us) * 0.5, -2*us) + eq((-5*us) * 0.5, -2*us) + + # Division by int and float + eq((3*us) / 2, 2*us) + eq((5*us) / 2, 2*us) + eq((-3*us) / 2.0, -2*us) + eq((-5*us) / 2.0, -2*us) + eq((3*us) / -2, -2*us) + eq((5*us) / -2, -2*us) + eq((3*us) / -2.0, -2*us) + eq((5*us) / -2.0, -2*us) + for i in range(-10, 10): + eq((i*us/3)//us, round(i/3)) + for i in range(-10, 10): + eq((i*us/-3)//us, round(i/-3)) + + def test_disallowed_computations(self): + a = timedelta(42) + + # Add/sub ints or floats should be illegal + for i in 1, 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) + + # Division of int by timedelta doesn't make sense. + # Division by zero doesn't make sense. + zero = 0 + self.assertRaises(TypeError, lambda: zero // a) + self.assertRaises(ZeroDivisionError, lambda: a // zero) + self.assertRaises(ZeroDivisionError, lambda: a / zero) + self.assertRaises(ZeroDivisionError, lambda: a / 0.0) + self.assertRaises(TypeError, lambda: a / '') + + @requires_IEEE_754 + def test_disallowed_special(self): + a = timedelta(42) + self.assertRaises(ValueError, a.__mul__, NAN) + self.assertRaises(ValueError, a.__truediv__, NAN) + + 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_total_seconds(self): + td = timedelta(days=365) + self.assertEqual(td.total_seconds(), 31536000.0) + for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]: + td = timedelta(seconds=total_seconds) + self.assertEqual(td.total_seconds(), total_seconds) + # Issue8644: Test that td.total_seconds() has the same + # accuracy as td / timedelta(seconds=1). + for ms in [-1, -2, -123]: + td = timedelta(microseconds=ms) + self.assertEqual(td.total_seconds(), td / timedelta(seconds=1)) + + 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): + args = 12, 34, 56 + orig = timedelta(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + def test_compare(self): + t1 = timedelta(2, 3, 4) + t2 = timedelta(2, 3, 4) + self.assertEqual(t1, t2) + self.assertTrue(t1 <= t2) + self.assertTrue(t1 >= t2) + self.assertTrue(not t1 != t2) + self.assertTrue(not t1 < t2) + self.assertTrue(not t1 > t2) + + for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): + t2 = timedelta(*args) # this is larger than t1 + self.assertTrue(t1 < t2) + self.assertTrue(t2 > t1) + self.assertTrue(t1 <= t2) + self.assertTrue(t2 >= t1) + self.assertTrue(t1 != t2) + self.assertTrue(t2 != t1) + self.assertTrue(not t1 == t2) + self.assertTrue(not t2 == t1) + self.assertTrue(not t1 > t2) + self.assertTrue(not t2 < t1) + self.assertTrue(not t1 >= t2) + self.assertTrue(not t2 <= t1) + + for badarg in OTHERSTUFF: + self.assertEqual(t1 == badarg, False) + self.assertEqual(t1 != badarg, True) + self.assertEqual(badarg == t1, False) + self.assertEqual(badarg != t1, True) + + 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) + + 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_repr(self): + name = 'datetime.' + self.theclass.__name__ + self.assertEqual(repr(self.theclass(1)), + "%s(1)" % name) + self.assertEqual(repr(self.theclass(10, 2)), + "%s(10, 2)" % name) + self.assertEqual(repr(self.theclass(-10, 2, 400000)), + "%s(-10, 2, 400000)" % name) + + def test_roundtrip(self): + for td in (timedelta(days=999999999, hours=23, minutes=59, + seconds=59, microseconds=999999), + timedelta(days=-999999999), + timedelta(days=-999999999, seconds=1), + timedelta(days=1, seconds=2, microseconds=3)): + + # Verify td -> string -> td identity. + s = repr(td) + self.assertTrue(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.assertIsInstance(timedelta.min, timedelta) + self.assertIsInstance(timedelta.max, timedelta) + self.assertIsInstance(timedelta.resolution, timedelta) + self.assertTrue(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) + + day = timedelta(1) + self.assertRaises(OverflowError, day.__mul__, 10**9) + self.assertRaises(OverflowError, day.__mul__, 1e9) + self.assertRaises(OverflowError, day.__truediv__, 1e-20) + self.assertRaises(OverflowError, day.__truediv__, 1e-10) + self.assertRaises(OverflowError, day.__truediv__, 9e-10) + + @requires_IEEE_754 + def _test_overflow_special(self): + day = timedelta(1) + self.assertRaises(OverflowError, day.__mul__, INF) + self.assertRaises(OverflowError, day.__mul__, -INF) + + 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.assertTrue(timedelta(1)) + self.assertTrue(timedelta(0, 1)) + self.assertTrue(timedelta(0, 0, 1)) + self.assertTrue(timedelta(microseconds=1)) + self.assertTrue(not timedelta(0)) + + def test_subclass_timedelta(self): + + class T(timedelta): + @staticmethod + def from_td(td): + return T(td.days, td.seconds, td.microseconds) + + def as_hours(self): + sum = (self.days * 24 + + self.seconds / 3600.0 + + self.microseconds / 3600e6) + return round(sum) + + t1 = T(days=1) + self.assertTrue(type(t1) is T) + self.assertEqual(t1.as_hours(), 24) + + t2 = T(days=-1, seconds=-3600) + self.assertTrue(type(t2) is T) + self.assertEqual(t2.as_hours(), -25) + + t3 = t1 + t2 + self.assertTrue(type(t3) is timedelta) + t4 = T.from_td(t3) + self.assertTrue(type(t4) is T) + self.assertEqual(t3.days, t4.days) + self.assertEqual(t3.seconds, t4.seconds) + self.assertEqual(t3.microseconds, t4.microseconds) + self.assertEqual(str(t3), str(t4)) + self.assertEqual(t4.as_hours(), -1) + + def test_division(self): + t = timedelta(hours=1, minutes=24, seconds=19) + second = timedelta(seconds=1) + self.assertEqual(t / second, 5059.0) + self.assertEqual(t // second, 5059) + + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + self.assertEqual(t / minute, 2.5) + self.assertEqual(t // minute, 2) + + zerotd = timedelta(0) + self.assertRaises(ZeroDivisionError, truediv, t, zerotd) + self.assertRaises(ZeroDivisionError, floordiv, t, zerotd) + + # self.assertRaises(TypeError, truediv, t, 2) + # note: floor division of a timedelta by an integer *is* + # currently permitted. + + def test_remainder(self): + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + r = t % minute + self.assertEqual(r, timedelta(seconds=30)) + + t = timedelta(minutes=-2, seconds=30) + r = t % minute + self.assertEqual(r, timedelta(seconds=30)) + + zerotd = timedelta(0) + self.assertRaises(ZeroDivisionError, mod, t, zerotd) + + self.assertRaises(TypeError, mod, t, 10) + + def test_divmod(self): + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + q, r = divmod(t, minute) + self.assertEqual(q, 2) + self.assertEqual(r, timedelta(seconds=30)) + + t = timedelta(minutes=-2, seconds=30) + q, r = divmod(t, minute) + self.assertEqual(q, -2) + self.assertEqual(r, timedelta(seconds=30)) + + zerotd = timedelta(0) + self.assertRaises(ZeroDivisionError, divmod, t, zerotd) + + self.assertRaises(TypeError, divmod, t, 10) + + +############################################################################# +# 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 SubclassDate(date): + sub_var = 1 + +class TestDate(HarmlessMixedComparison, 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.assertTrue(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 range(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) + c = self.theclass(2001,2,1) + + 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) + self.assertEqual(c - (c - day), day) + + # Add/sub ints or floats should be illegal + for i in 1, 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 + + for delta in [tiny, timedelta(1), timedelta(2)]: + dt = self.theclass.min + delta + dt -= delta # no problem + self.assertRaises(OverflowError, dt.__sub__, delta) + self.assertRaises(OverflowError, dt.__add__, -delta) + + dt = self.theclass.max - delta + dt += delta # no problem + self.assertRaises(OverflowError, dt.__add__, delta) + self.assertRaises(OverflowError, dt.__sub__, -delta) + + 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_insane_fromtimestamp(self): + # It's possible that some platform maps time_t to double, + # and that this test will fail there. This test should + # exempt such platforms (provided they return reasonable + # results!). + for insane in -1e200, 1e200: + self.assertRaises(ValueError, self.theclass.fromtimestamp, + insane) + + 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.assertTrue(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 = sorted(map(int, ISO_LONG_YEARS_TABLE.split())) + 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.assertEqual(t.strftime(""), "") # SF bug #761337 + self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784 + + 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 + + # test that unicode input is allowed (issue 2782) + self.assertEqual(t.strftime("%m"), "03") + + # A naive object replaces %z and %Z w/ empty strings. + self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") + + #make sure that invalid format specifiers are handled correctly + #self.assertRaises(ValueError, t.strftime, "%e") + #self.assertRaises(ValueError, t.strftime, "%") + #self.assertRaises(ValueError, t.strftime, "%#") + + #oh well, some systems just ignore those invalid ones. + #at least, excercise them to make sure that no crashes + #are generated + for f in ["%e", "%", "%#"]: + try: + t.strftime(f) + except ValueError: + pass + + #check that this standard extension works + t.strftime("%f") + + + def test_format(self): + dt = self.theclass(2007, 9, 10) + self.assertEqual(dt.__format__(''), str(dt)) + + # check that a derived class's __str__() gets called + class A(self.theclass): + def __str__(self): + return 'A' + a = A(2007, 9, 10) + self.assertEqual(a.__format__(''), 'A') + + # check that a derived class's strftime gets called + class B(self.theclass): + def strftime(self, format_spec): + return 'B' + b = B(2007, 9, 10) + self.assertEqual(b.__format__(''), str(dt)) + + for fmt in ["m:%m d:%d y:%y", + "m:%m d:%d y:%y H:%H M:%M S:%S", + "%z %Z", + ]: + self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) + self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) + self.assertEqual(b.__format__(fmt), 'B') + + def test_resolution_info(self): + # XXX: Should min and max respect subclassing? + if issubclass(self.theclass, datetime): + expected_class = datetime + else: + expected_class = date + self.assertIsInstance(self.theclass.min, expected_class) + self.assertIsInstance(self.theclass.max, expected_class) + self.assertIsInstance(self.theclass.resolution, timedelta) + self.assertTrue(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): + args = 6, 7, 23 + orig = self.theclass(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + def test_compare(self): + t1 = self.theclass(2, 3, 4) + t2 = self.theclass(2, 3, 4) + self.assertEqual(t1, t2) + self.assertTrue(t1 <= t2) + self.assertTrue(t1 >= t2) + self.assertTrue(not t1 != t2) + self.assertTrue(not t1 < t2) + self.assertTrue(not t1 > t2) + + for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): + t2 = self.theclass(*args) # this is larger than t1 + self.assertTrue(t1 < t2) + self.assertTrue(t2 > t1) + self.assertTrue(t1 <= t2) + self.assertTrue(t2 >= t1) + self.assertTrue(t1 != t2) + self.assertTrue(t2 != t1) + self.assertTrue(not t1 == t2) + self.assertTrue(not t2 == t1) + self.assertTrue(not t1 > t2) + self.assertTrue(not t2 < t1) + self.assertTrue(not t1 >= t2) + self.assertTrue(not t2 <= t1) + + for badarg in OTHERSTUFF: + self.assertEqual(t1 == badarg, False) + self.assertEqual(t1 != badarg, True) + self.assertEqual(badarg == t1, False) + self.assertEqual(badarg != t1, True) + + 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) + + def test_mixed_compare(self): + our = self.theclass(2000, 4, 5) + + # Our class can be compared for equality to other classes + self.assertEqual(our == 1, False) + self.assertEqual(1 == our, False) + self.assertEqual(our != 1, True) + self.assertEqual(1 != our, True) + + # But the ordering is undefined + self.assertRaises(TypeError, lambda: our < 1) + self.assertRaises(TypeError, lambda: 1 < our) + + # Repeat those tests with a different class + + class SomeClass: + pass + + their = SomeClass() + self.assertEqual(our == their, False) + self.assertEqual(their == our, False) + self.assertEqual(our != their, True) + self.assertEqual(their != our, True) + self.assertRaises(TypeError, lambda: our < their) + self.assertRaises(TypeError, lambda: their < our) + + # However, if the other class explicitly defines ordering + # relative to our class, it is allowed to do so + + class LargerThanAnything: + def __lt__(self, other): + return False + def __le__(self, other): + return isinstance(other, LargerThanAnything) + def __eq__(self, other): + return isinstance(other, LargerThanAnything) + def __ne__(self, other): + return not isinstance(other, LargerThanAnything) + def __gt__(self, other): + return not isinstance(other, LargerThanAnything) + def __ge__(self, other): + return True + + their = LargerThanAnything() + self.assertEqual(our == their, False) + self.assertEqual(their == our, False) + self.assertEqual(our != their, True) + self.assertEqual(their != our, True) + self.assertEqual(our < their, True) + self.assertEqual(their < our, False) + + def test_bool(self): + # All dates are considered true. + self.assertTrue(self.theclass.min) + self.assertTrue(self.theclass.max) + + def test_strftime_out_of_range(self): + # For nasty technical reasons, we can't handle years before 1900. + cls = self.theclass + self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900") + for y in 1, 49, 51, 99, 100, 1000, 1899: + self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y") + + def test_replace(self): + cls = self.theclass + args = [1, 2, 3] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("year", 2), + ("month", 3), + ("day", 4)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Out of bounds. + base = cls(2000, 2, 29) + self.assertRaises(ValueError, base.replace, year=2001) + + def test_subclass_date(self): + + class C(self.theclass): + theAnswer = 42 + + def __new__(cls, *args, **kws): + temp = kws.copy() + extra = temp.pop('extra') + result = self.theclass.__new__(cls, *args, **temp) + result.extra = extra + return result + + def newmeth(self, start): + return start + self.year + self.month + + args = 2003, 4, 14 + + dt1 = self.theclass(*args) + dt2 = C(*args, **{'extra': 7}) + + self.assertEqual(dt2.__class__, C) + self.assertEqual(dt2.theAnswer, 42) + self.assertEqual(dt2.extra, 7) + self.assertEqual(dt1.toordinal(), dt2.toordinal()) + self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7) + + def test_pickling_subclass_date(self): + + args = 6, 7, 23 + orig = SubclassDate(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + def test_backdoor_resistance(self): + # For fast unpickling, the constructor accepts a pickle byte string. + # This is a low-overhead backdoor. A user can (by intent or + # mistake) pass a string directly, which (if it's the right length) + # will get treated like a pickle, and bypass the normal sanity + # checks in the constructor. This can create insane objects. + # The constructor doesn't want to burn the time to validate all + # fields, but does check the month field. This stops, e.g., + # datetime.datetime('1995-03-25') from yielding an insane object. + base = b'1995-03-25' + if not issubclass(self.theclass, datetime): + base = base[:4] + for month_byte in b'9', b'\0', b'\r', b'\xff': + self.assertRaises(TypeError, self.theclass, + base[:2] + month_byte + base[3:]) + # Good bytes, but bad tzinfo: + self.assertRaises(TypeError, self.theclass, + bytes([1] * len(base)), 'EST') + + for ord_byte in range(1, 13): + # This shouldn't blow up because of the month byte alone. If + # the implementation changes to do more-careful checking, it may + # blow up because other fields are insane. + self.theclass(base[:2] + bytes([ord_byte]) + base[3:]) + +############################################################################# +# datetime tests + +class SubclassDatetime(datetime): + sub_var = 1 + +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.assertTrue(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") + self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004: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_format(self): + dt = self.theclass(2007, 9, 10, 4, 5, 1, 123) + self.assertEqual(dt.__format__(''), str(dt)) + + # check that a derived class's __str__() gets called + class A(self.theclass): + def __str__(self): + return 'A' + a = A(2007, 9, 10, 4, 5, 1, 123) + self.assertEqual(a.__format__(''), 'A') + + # check that a derived class's strftime gets called + class B(self.theclass): + def strftime(self, format_spec): + return 'B' + b = B(2007, 9, 10, 4, 5, 1, 123) + self.assertEqual(b.__format__(''), str(dt)) + + for fmt in ["m:%m d:%d y:%y", + "m:%m d:%d y:%y H:%H M:%M S:%S", + "%z %Z", + ]: + self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) + self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) + self.assertEqual(b.__format__(fmt), 'B') + + 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.assertTrue(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.assertTrue(dt1 < dt2) + + def test_strftime_with_bad_tzname_replace(self): + # verify ok if tzinfo.tzname().replace() returns a non-string + class MyTzInfo(FixedOffset): + def tzname(self, dt): + class MyStr(str): + def replace(self, *args): + return None + return MyStr('name') + t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name')) + self.assertRaises(TypeError, t.strftime, '%Z') + + 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 or floats should be illegal + for i in 1, 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): + args = 6, 7, 23, 20, 59, 1, 64**2 + orig = self.theclass(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + def test_more_pickling(self): + a = self.theclass(2003, 2, 7, 16, 48, 37, 444116) + s = pickle.dumps(a) + b = pickle.loads(s) + self.assertEqual(b.year, 2003) + self.assertEqual(b.month, 2) + self.assertEqual(b.day, 7) + + def test_pickling_subclass_datetime(self): + args = 6, 7, 23, 20, 59, 1, 64**2 + orig = SubclassDatetime(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.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.assertEqual(t1, t2) + self.assertTrue(t1 <= t2) + self.assertTrue(t1 >= t2) + self.assertTrue(not t1 != t2) + self.assertTrue(not t1 < t2) + self.assertTrue(not t1 > t2) + + for i in range(len(args)): + newargs = args[:] + newargs[i] = args[i] + 1 + t2 = self.theclass(*newargs) # this is larger than t1 + self.assertTrue(t1 < t2) + self.assertTrue(t2 > t1) + self.assertTrue(t1 <= t2) + self.assertTrue(t2 >= t1) + self.assertTrue(t1 != t2) + self.assertTrue(t2 != t1) + self.assertTrue(not t1 == t2) + self.assertTrue(not t2 == t1) + self.assertTrue(not t1 > t2) + self.assertTrue(not t2 < t1) + self.assertTrue(not t1 >= t2) + self.assertTrue(not t2 <= t1) + + + # 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_microsecond_rounding(self): + # Test whether fromtimestamp "rounds up" floats that are less + # than one microsecond smaller than an integer. + self.assertEqual(self.theclass.fromtimestamp(0.9999999), + self.theclass.fromtimestamp(1)) + + def test_insane_fromtimestamp(self): + # It's possible that some platform maps time_t to double, + # and that this test will fail there. This test should + # exempt such platforms (provided they return reasonable + # results!). + for insane in -1e200, 1e200: + self.assertRaises(ValueError, self.theclass.fromtimestamp, + insane) + + def test_insane_utcfromtimestamp(self): + # It's possible that some platform maps time_t to double, + # and that this test will fail there. This test should + # exempt such platforms (provided they return reasonable + # results!). + for insane in -1e200, 1e200: + self.assertRaises(ValueError, self.theclass.utcfromtimestamp, + insane) + @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") + def test_negative_float_fromtimestamp(self): + # The result is tz-dependent; at least test that this doesn't + # fail (like it did before bug 1646728 was fixed). + self.theclass.fromtimestamp(-1.05) + + @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") + def test_negative_float_utcfromtimestamp(self): + d = self.theclass.utcfromtimestamp(-1.05) + self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000)) + + 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.assertTrue(abs(from_timestamp - from_now) <= tolerance) + + def test_strptime(self): + import _strptime + + string = '2004-12-01 13:02:47.197' + format = '%Y-%m-%d %H:%M:%S.%f' + expected = _strptime._strptime_datetime(self.theclass, string, format) + got = self.theclass.strptime(string, format) + self.assertEqual(expected, got) + self.assertIs(type(expected), self.theclass) + self.assertIs(type(got), self.theclass) + + strptime = self.theclass.strptime + self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) + self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) + # Only local timezone and UTC are supported + for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'), + (-_time.timezone, _time.tzname[0])): + if tzseconds < 0: + sign = '-' + seconds = -tzseconds + else: + sign ='+' + seconds = tzseconds + hours, minutes = divmod(seconds//60, 60) + dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname) + dt = strptime(dtstr, "%z %Z") + self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds)) + self.assertEqual(dt.tzname(), tzname) + # Can produce inconsistent datetime + dtstr, fmt = "+1234 UTC", "%z %Z" + dt = strptime(dtstr, fmt) + self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE) + self.assertEqual(dt.tzname(), 'UTC') + # yet will roundtrip + self.assertEqual(dt.strftime(fmt), dtstr) + + # Produce naive datetime if no %z is provided + self.assertEqual(strptime("UTC", "%Z").tzinfo, None) + + with self.assertRaises(ValueError): strptime("-2400", "%z") + with self.assertRaises(ValueError): strptime("-000", "%z") + + 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, 47) + self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"), + "12 31 04 000047 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 + self.assertRaises(TypeError, combine, d, "time") # wrong type + self.assertRaises(TypeError, combine, "date", t) # wrong type + + def test_replace(self): + cls = self.theclass + args = [1, 2, 3, 4, 5, 6, 7] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("year", 2), + ("month", 3), + ("day", 4), + ("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Out of bounds. + base = cls(2000, 2, 29) + self.assertRaises(ValueError, base.replace, year=2001) + + def test_astimezone(self): + # Pretty boring! The TZ test is more interesting here. astimezone() + # simply can't be applied to a naive object. + dt = self.theclass.now() + f = FixedOffset(44, "") + self.assertRaises(TypeError, dt.astimezone) # not enough args + self.assertRaises(TypeError, dt.astimezone, f, f) # too many args + self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type + self.assertRaises(ValueError, dt.astimezone, f) # naive + self.assertRaises(ValueError, dt.astimezone, tz=f) # naive + + class Bogus(tzinfo): + def utcoffset(self, dt): return None + def dst(self, dt): return timedelta(0) + bog = Bogus() + self.assertRaises(ValueError, dt.astimezone, bog) # naive + self.assertRaises(ValueError, + dt.replace(tzinfo=bog).astimezone, f) + + class AlsoBogus(tzinfo): + def utcoffset(self, dt): return timedelta(0) + def dst(self, dt): return None + alsobog = AlsoBogus() + self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive + + def test_subclass_datetime(self): + + class C(self.theclass): + theAnswer = 42 + + def __new__(cls, *args, **kws): + temp = kws.copy() + extra = temp.pop('extra') + result = self.theclass.__new__(cls, *args, **temp) + result.extra = extra + return result + + def newmeth(self, start): + return start + self.year + self.month + self.second + + args = 2003, 4, 14, 12, 13, 41 + + dt1 = self.theclass(*args) + dt2 = C(*args, **{'extra': 7}) + + self.assertEqual(dt2.__class__, C) + self.assertEqual(dt2.theAnswer, 42) + self.assertEqual(dt2.extra, 7) + self.assertEqual(dt1.toordinal(), dt2.toordinal()) + self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month + + dt1.second - 7) + +class TestSubclassDateTime(TestDateTime): + theclass = SubclassDatetime + # Override tests not designed for subclass + def test_roundtrip(self): + pass + +class SubclassTime(time): + sub_var = 1 + +class TestTime(HarmlessMixedComparison, 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.assertTrue(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.assertEqual(t1, t2) + self.assertTrue(t1 <= t2) + self.assertTrue(t1 >= t2) + self.assertTrue(not t1 != t2) + self.assertTrue(not t1 < t2) + self.assertTrue(not t1 > t2) + + for i in range(len(args)): + newargs = args[:] + newargs[i] = args[i] + 1 + t2 = self.theclass(*newargs) # this is larger than t1 + self.assertTrue(t1 < t2) + self.assertTrue(t2 > t1) + self.assertTrue(t1 <= t2) + self.assertTrue(t2 >= t1) + self.assertTrue(t1 != t2) + self.assertTrue(t2 != t1) + self.assertTrue(not t1 == t2) + self.assertTrue(not t2 == t1) + self.assertTrue(not t1 > t2) + self.assertTrue(not t2 < t1) + self.assertTrue(not t1 >= t2) + self.assertTrue(not t2 <= t1) + + for badarg in OTHERSTUFF: + self.assertEqual(t1 == badarg, False) + self.assertEqual(t1 != badarg, True) + self.assertEqual(badarg == t1, False) + self.assertEqual(badarg != t1, True) + + 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) + + 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_1653736(self): + # verify it doesn't accept extra keyword arguments + t = self.theclass(second=1) + self.assertRaises(TypeError, t.isoformat, foo=3) + + def test_strftime(self): + t = self.theclass(1, 2, 3, 4) + self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004") + # A naive object replaces %z and %Z with empty strings. + self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") + + def test_format(self): + t = self.theclass(1, 2, 3, 4) + self.assertEqual(t.__format__(''), str(t)) + + # check that a derived class's __str__() gets called + class A(self.theclass): + def __str__(self): + return 'A' + a = A(1, 2, 3, 4) + self.assertEqual(a.__format__(''), 'A') + + # check that a derived class's strftime gets called + class B(self.theclass): + def strftime(self, format_spec): + return 'B' + b = B(1, 2, 3, 4) + self.assertEqual(b.__format__(''), str(t)) + + for fmt in ['%H %M %S', + ]: + self.assertEqual(t.__format__(fmt), t.strftime(fmt)) + self.assertEqual(a.__format__(fmt), t.strftime(fmt)) + self.assertEqual(b.__format__(fmt), 'B') + + 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.assertIsInstance(self.theclass.min, self.theclass) + self.assertIsInstance(self.theclass.max, self.theclass) + self.assertIsInstance(self.theclass.resolution, timedelta) + self.assertTrue(self.theclass.max > self.theclass.min) + + def test_pickling(self): + args = 20, 59, 16, 64**2 + orig = self.theclass(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + def test_pickling_subclass_time(self): + args = 20, 59, 16, 64**2 + orig = SubclassTime(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + def test_bool(self): + cls = self.theclass + self.assertTrue(cls(1)) + self.assertTrue(cls(0, 1)) + self.assertTrue(cls(0, 0, 1)) + self.assertTrue(cls(0, 0, 0, 1)) + self.assertTrue(not cls(0)) + self.assertTrue(not cls()) + + def test_replace(self): + cls = self.theclass + args = [1, 2, 3, 4] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Out of bounds. + base = cls(1) + self.assertRaises(ValueError, base.replace, hour=24) + self.assertRaises(ValueError, base.replace, minute=-1) + self.assertRaises(ValueError, base.replace, second=100) + self.assertRaises(ValueError, base.replace, microsecond=1000000) + + def test_subclass_time(self): + + class C(self.theclass): + theAnswer = 42 + + def __new__(cls, *args, **kws): + temp = kws.copy() + extra = temp.pop('extra') + result = self.theclass.__new__(cls, *args, **temp) + result.extra = extra + return result + + def newmeth(self, start): + return start + self.hour + self.second + + args = 4, 5, 6 + + dt1 = self.theclass(*args) + dt2 = C(*args, **{'extra': 7}) + + self.assertEqual(dt2.__class__, C) + self.assertEqual(dt2.theAnswer, 42) + self.assertEqual(dt2.extra, 7) + self.assertEqual(dt1.isoformat(), dt2.isoformat()) + self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) + + def test_backdoor_resistance(self): + # see TestDate.test_backdoor_resistance(). + base = '2:59.0' + for hour_byte in ' ', '9', chr(24), '\xff': + self.assertRaises(TypeError, self.theclass, + hour_byte + base[1:]) + +# A mixin for classes with a tzinfo= argument. Subclasses must define +# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever) +# must be legit (which is true for time and datetime). +class TZInfoBase: + + def test_argument_passing(self): + cls = self.theclass + # A datetime passes itself on, a time passes None. + class introspective(tzinfo): + def tzname(self, dt): return dt and "real" or "none" + def utcoffset(self, dt): + return timedelta(minutes = dt and 42 or -42) + dst = utcoffset + + obj = cls(1, 2, 3, tzinfo=introspective()) + + expected = cls is time and "none" or "real" + self.assertEqual(obj.tzname(), expected) + + expected = timedelta(minutes=(cls is time and -42 or 42)) + self.assertEqual(obj.utcoffset(), expected) + self.assertEqual(obj.dst(), expected) + + def test_bad_tzinfo_classes(self): + cls = self.theclass + self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12) + + class NiceTry(object): + def __init__(self): pass + def utcoffset(self, dt): pass + self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry) + + class BetterTry(tzinfo): + def __init__(self): pass + def utcoffset(self, dt): pass + b = BetterTry() + t = cls(1, 1, 1, tzinfo=b) + self.assertTrue(t.tzinfo is b) + + def test_utc_offset_out_of_bounds(self): + class Edgy(tzinfo): + def __init__(self, offset): + self.offset = timedelta(minutes=offset) + def utcoffset(self, dt): + return self.offset + + cls = self.theclass + for offset, legit in ((-1440, False), + (-1439, True), + (1439, True), + (1440, False)): + if cls is time: + t = cls(1, 2, 3, tzinfo=Edgy(offset)) + elif cls is datetime: + t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset)) + else: + assert 0, "impossible" + if legit: + aofs = abs(offset) + h, m = divmod(aofs, 60) + tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m) + if isinstance(t, datetime): + t = t.timetz() + self.assertEqual(str(t), "01:02:03" + tag) + else: + self.assertRaises(ValueError, str, t) + + def test_tzinfo_classes(self): + cls = self.theclass + class C1(tzinfo): + def utcoffset(self, dt): return None + def dst(self, dt): return None + def tzname(self, dt): return None + for t in (cls(1, 1, 1), + cls(1, 1, 1, tzinfo=None), + cls(1, 1, 1, tzinfo=C1())): + self.assertTrue(t.utcoffset() is None) + self.assertTrue(t.dst() is None) + self.assertTrue(t.tzname() is None) + + class C3(tzinfo): + def utcoffset(self, dt): return timedelta(minutes=-1439) + def dst(self, dt): return timedelta(minutes=1439) + def tzname(self, dt): return "aname" + t = cls(1, 1, 1, tzinfo=C3()) + self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) + self.assertEqual(t.dst(), timedelta(minutes=1439)) + self.assertEqual(t.tzname(), "aname") + + # Wrong types. + class C4(tzinfo): + def utcoffset(self, dt): return "aname" + def dst(self, dt): return 7 + def tzname(self, dt): return 0 + t = cls(1, 1, 1, tzinfo=C4()) + self.assertRaises(TypeError, t.utcoffset) + self.assertRaises(TypeError, t.dst) + self.assertRaises(TypeError, t.tzname) + + # Offset out of range. + class C6(tzinfo): + def utcoffset(self, dt): return timedelta(hours=-24) + def dst(self, dt): return timedelta(hours=24) + t = cls(1, 1, 1, tzinfo=C6()) + self.assertRaises(ValueError, t.utcoffset) + self.assertRaises(ValueError, t.dst) + + # Not a whole number of minutes. + class C7(tzinfo): + def utcoffset(self, dt): return timedelta(seconds=61) + def dst(self, dt): return timedelta(microseconds=-81) + t = cls(1, 1, 1, tzinfo=C7()) + self.assertRaises(ValueError, t.utcoffset) + self.assertRaises(ValueError, t.dst) + + def test_aware_compare(self): + cls = self.theclass + + # Ensure that utcoffset() gets ignored if the comparands have + # the same tzinfo member. + class OperandDependentOffset(tzinfo): + def utcoffset(self, t): + if t.minute < 10: + # d0 and d1 equal after adjustment + return timedelta(minutes=t.minute) + else: + # d2 off in the weeds + return timedelta(minutes=59) + + base = cls(8, 9, 10, tzinfo=OperandDependentOffset()) + d0 = base.replace(minute=3) + d1 = base.replace(minute=9) + d2 = base.replace(minute=11) + for x in d0, d1, d2: + for y in d0, d1, d2: + for op in lt, le, gt, ge, eq, ne: + got = op(x, y) + expected = op(x.minute, y.minute) + self.assertEqual(got, expected) + + # However, if they're different members, uctoffset is not ignored. + # Note that a time can't actually have an operand-depedent offset, + # though (and time.utcoffset() passes None to tzinfo.utcoffset()), + # so skip this test for time. + if cls is not time: + d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) + d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) + d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) + for x in d0, d1, d2: + for y in d0, d1, d2: + got = (x > y) - (x < y) + if (x is d0 or x is d1) and (y is d0 or y is d1): + expected = 0 + elif x is y is d2: + expected = 0 + elif x is d2: + expected = -1 + else: + assert y is d2 + expected = 1 + self.assertEqual(got, expected) + + +# Testing time objects with a non-None tzinfo. +class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): + theclass = time + + 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.assertTrue(t.tzinfo is None) + + def test_zones(self): + est = FixedOffset(-300, "EST", 1) + utc = FixedOffset(0, "UTC", -2) + met = FixedOffset(60, "MET", 3) + t1 = time( 7, 47, tzinfo=est) + t2 = time(12, 47, tzinfo=utc) + t3 = time(13, 47, tzinfo=met) + t4 = time(microsecond=40) + t5 = time(microsecond=40, tzinfo=utc) + + self.assertEqual(t1.tzinfo, est) + self.assertEqual(t2.tzinfo, utc) + self.assertEqual(t3.tzinfo, met) + self.assertTrue(t4.tzinfo is None) + self.assertEqual(t5.tzinfo, utc) + + self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) + self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) + self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) + self.assertTrue(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.assertTrue(t4.tzname() is None) + self.assertRaises(TypeError, t1.tzname, "no args") + + self.assertEqual(t1.dst(), timedelta(minutes=1)) + self.assertEqual(t2.dst(), timedelta(minutes=-2)) + self.assertEqual(t3.dst(), timedelta(minutes=3)) + self.assertTrue(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.time' + self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)") + self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)") + self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)") + 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 = time(23, 59, tzinfo=yuck) + self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"), + "23:59 %Z='%z %Z %%z%%Z' %z='-2359'") + + # Check that an invalid tzname result raises an exception. + class Badtzname(tzinfo): + def tzname(self, dt): return 42 + t = time(2, 3, 4, tzinfo=Badtzname()) + self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04") + self.assertRaises(TypeError, t.strftime, "%Z") + + 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_pickling(self): + # Try one without a tzinfo. + args = 20, 59, 16, 64**2 + orig = self.theclass(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + # Try one with a tzinfo. + tinfo = PicklableFixedOffset(-300, 'cookie') + orig = self.theclass(5, 6, 7, tzinfo=tinfo) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) + self.assertEqual(derived.utcoffset(), timedelta(minutes=-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.assertTrue(t) + + t = cls(5, tzinfo=FixedOffset(-300, "")) + self.assertTrue(t) + + t = cls(5, tzinfo=FixedOffset(300, "")) + self.assertTrue(not t) + + t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, "")) + self.assertTrue(not t) + + # Mostly ensuring this doesn't overflow internally. + t = cls(0, tzinfo=FixedOffset(23*60 + 59, "")) + self.assertTrue(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)) + + def test_replace(self): + cls = self.theclass + z100 = FixedOffset(100, "+100") + zm200 = FixedOffset(timedelta(minutes=-200), "-200") + args = [1, 2, 3, 4, z100] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8), + ("tzinfo", zm200)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Ensure we can get rid of a tzinfo. + self.assertEqual(base.tzname(), "+100") + base2 = base.replace(tzinfo=None) + self.assertTrue(base2.tzinfo is None) + self.assertTrue(base2.tzname() is None) + + # Ensure we can add one. + base3 = base2.replace(tzinfo=z100) + self.assertEqual(base, base3) + self.assertTrue(base.tzinfo is base3.tzinfo) + + # Out of bounds. + base = cls(1) + self.assertRaises(ValueError, base.replace, hour=24) + self.assertRaises(ValueError, base.replace, minute=-1) + self.assertRaises(ValueError, base.replace, second=100) + self.assertRaises(ValueError, base.replace, microsecond=1000000) + + def test_mixed_compare(self): + t1 = time(1, 2, 3) + t2 = time(1, 2, 3) + self.assertEqual(t1, t2) + t2 = t2.replace(tzinfo=None) + self.assertEqual(t1, t2) + t2 = t2.replace(tzinfo=FixedOffset(None, "")) + self.assertEqual(t1, t2) + t2 = t2.replace(tzinfo=FixedOffset(0, "")) + self.assertRaises(TypeError, lambda: t1 == t2) + + # In time w/ identical tzinfo objects, utcoffset is ignored. + class Varies(tzinfo): + def __init__(self): + self.offset = timedelta(minutes=22) + def utcoffset(self, t): + self.offset += timedelta(minutes=1) + return self.offset + + v = Varies() + t1 = t2.replace(tzinfo=v) + t2 = t2.replace(tzinfo=v) + self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) + self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) + self.assertEqual(t1, t2) + + # But if they're not identical, it isn't ignored. + t2 = t2.replace(tzinfo=Varies()) + self.assertTrue(t1 < t2) # t1's offset counter still going up + + def test_subclass_timetz(self): + + class C(self.theclass): + theAnswer = 42 + + def __new__(cls, *args, **kws): + temp = kws.copy() + extra = temp.pop('extra') + result = self.theclass.__new__(cls, *args, **temp) + result.extra = extra + return result + + def newmeth(self, start): + return start + self.hour + self.second + + args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1) + + dt1 = self.theclass(*args) + dt2 = C(*args, **{'extra': 7}) + + self.assertEqual(dt2.__class__, C) + self.assertEqual(dt2.theAnswer, 42) + self.assertEqual(dt2.extra, 7) + self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) + self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) + + +# Testing datetime objects with a non-None tzinfo. + +class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase): + theclass = datetime + + 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.assertTrue(t1 < t2) + self.assertTrue(t1 != t2) + self.assertTrue(t2 > t1) + + self.assertEqual(t1, t1) + self.assertEqual(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.assertTrue(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.assertTrue(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.assertTrue(t1 > t2) + + # Likewise, but make microseconds resolve it. + t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), + microsecond=1) + self.assertTrue(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 timedelta(minutes=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 == t2) + + def test_pickling(self): + # Try one without a tzinfo. + args = 6, 7, 23, 20, 59, 1, 64**2 + orig = self.theclass(*args) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + + # Try one with a tzinfo. + tinfo = PicklableFixedOffset(-300, 'cookie') + orig = self.theclass(*args, **{'tzinfo': tinfo}) + derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0)) + for pickler, unpickler, proto in pickle_choices: + green = pickler.dumps(orig, proto) + derived = unpickler.loads(green) + self.assertEqual(orig, derived) + self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) + self.assertEqual(derived.utcoffset(), timedelta(minutes=-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 = datetime(2002, 3, 19, 7, 47, tzinfo=est) + t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc) + t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met) + self.assertEqual(t1.tzinfo, est) + self.assertEqual(t2.tzinfo, utc) + self.assertEqual(t3.tzinfo, met) + self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) + self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) + self.assertEqual(t3.utcoffset(), timedelta(minutes=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.datetime(2002, 3, 19, ' + self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)") + self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)") + self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)") + + def test_combine(self): + met = FixedOffset(60, "MET") + d = date(2002, 3, 4) + tz = time(18, 45, 3, 1234, tzinfo=met) + dt = datetime.combine(d, tz) + self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234, + tzinfo=met)) + + def test_extract(self): + 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(), time(18, 45, 3, 1234, tzinfo=met)) + + def test_tz_aware_arithmetic(self): + import random + + now = self.theclass.now() + tz55 = FixedOffset(-330, "west 5:30") + timeaware = now.time().replace(tzinfo=tz55) + nowaware = self.theclass.combine(now.date(), timeaware) + self.assertTrue(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 datetime's doesn't make sense, aware or not. + self.assertRaises(TypeError, lambda: now + nowaware) + self.assertRaises(TypeError, lambda: nowaware + now) + self.assertRaises(TypeError, lambda: nowaware + nowaware) + + # 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.assertTrue(nowaware.tzinfo is tz55) + nowawareplus2 = delta + nowaware + self.assertTrue(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.assertTrue(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. + nowawareplus = nowawareplus.replace(tzinfo=tzr) + self.assertTrue(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 = 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)) + # Different tzinfo, but the same offset + tza = timezone(HOUR, 'A') + tzb = timezone(HOUR, 'B') + delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb) + self.assertEqual(delta, self.theclass.min - self.theclass.max) + + 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(tz=off42) + self.assertTrue(another.tzinfo is again.tzinfo) + self.assertEqual(another.utcoffset(), timedelta(minutes=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) + + # We don't know which time zone we're in, and don't have a tzinfo + # class to represent it, so seeing whether a tz argument actually + # does a conversion is tricky. + utc = FixedOffset(0, "utc", 0) + for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0), + timezone(timedelta(hours=15, minutes=58), "weirdtz"),]: + for dummy in range(3): + now = datetime.now(weirdtz) + self.assertTrue(now.tzinfo is weirdtz) + utcnow = datetime.utcnow().replace(tzinfo=utc) + now2 = utcnow.astimezone(weirdtz) + if abs(now - now2) < timedelta(seconds=30): + break + # Else the code is broken, or more than 30 seconds passed between + # calls; assuming the latter, just try again. + else: + # Three strikes and we're out. + self.fail("utcnow(), now(tz), or astimezone() may be broken") + + 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, tz=off42) + self.assertTrue(another.tzinfo is again.tzinfo) + self.assertEqual(another.utcoffset(), timedelta(minutes=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) + + # Try to make sure tz= actually does some conversion. + timestamp = 1000000000 + utcdatetime = datetime.utcfromtimestamp(timestamp) + # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take. + # But on some flavor of Mac, it's nowhere near that. So we can't have + # any idea here what time that actually is, we can only test that + # relative changes match. + utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero + tz = FixedOffset(utcoffset, "tz", 0) + expected = utcdatetime + utcoffset + got = datetime.fromtimestamp(timestamp, tz) + self.assertEqual(expected, got.replace(tzinfo=None)) + + 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. datetime adds a twist to the + # DST flag. + class DST(tzinfo): + def __init__(self, dstvalue): + if isinstance(dstvalue, int): + dstvalue = timedelta(minutes=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=0): + if isinstance(dstvalue, int): + dstvalue = timedelta(minutes=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 = timedelta(minutes=uofs) + def utcoffset(self, dt): + return self.uofs + + 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) + # Ensure tm_isdst is 0 regardless of what dst() says: DST + # is never in effect for a UTC time. + self.assertEqual(0, t.tm_isdst) + + # For naive datetime, utctimetuple == timetuple except for isdst + d = cls(1, 2, 3, 10, 20, 30, 40) + t = d.utctimetuple() + self.assertEqual(t[:-1], d.timetuple()[:-1]) + self.assertEqual(0, t.tm_isdst) + # Same if utcoffset is None + class NOFS(DST): + def utcoffset(self, dt): + return None + d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS()) + t = d.utctimetuple() + self.assertEqual(t[:-1], d.timetuple()[:-1]) + self.assertEqual(0, t.tm_isdst) + # Check that bad tzinfo is detected + class BOFS(DST): + def utcoffset(self, dt): + return "EST" + d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS()) + self.assertRaises(TypeError, d.utctimetuple) + + # Check that utctimetuple() is the same as + # astimezone(utc).timetuple() + d = cls(2010, 11, 13, 14, 15, 16, 171819) + for tz in [timezone.min, timezone.utc, timezone.max]: + dtz = d.replace(tzinfo=tz) + self.assertEqual(dtz.utctimetuple()[:-1], + dtz.astimezone(timezone.utc).timetuple()[:-1]) + # At the edges, UTC adjustment can produce years out-of-range + # for a datetime object. Ensure that an OverflowError is + # raised. + tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439)) + # That goes back 1 minute less than a full day. + self.assertRaises(OverflowError, tiny.utctimetuple) + + huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439)) + # That goes forward 1 minute less than a full day. + self.assertRaises(OverflowError, huge.utctimetuple) + # More overflow cases + tiny = cls.min.replace(tzinfo=timezone(MINUTE)) + self.assertRaises(OverflowError, tiny.utctimetuple) + huge = cls.max.replace(tzinfo=timezone(-MINUTE)) + self.assertRaises(OverflowError, huge.utctimetuple) + + 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(d.isoformat('\u1234'), datestr + '\u1234' + tailstr) + self.assertEqual(str(d), datestr + ' ' + tailstr) + + def test_replace(self): + cls = self.theclass + z100 = FixedOffset(100, "+100") + zm200 = FixedOffset(timedelta(minutes=-200), "-200") + args = [1, 2, 3, 4, 5, 6, 7, z100] + base = cls(*args) + self.assertEqual(base, base.replace()) + + i = 0 + for name, newval in (("year", 2), + ("month", 3), + ("day", 4), + ("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8), + ("tzinfo", zm200)): + newargs = args[:] + newargs[i] = newval + expected = cls(*newargs) + got = base.replace(**{name: newval}) + self.assertEqual(expected, got) + i += 1 + + # Ensure we can get rid of a tzinfo. + self.assertEqual(base.tzname(), "+100") + base2 = base.replace(tzinfo=None) + self.assertTrue(base2.tzinfo is None) + self.assertTrue(base2.tzname() is None) + + # Ensure we can add one. + base3 = base2.replace(tzinfo=z100) + self.assertEqual(base, base3) + self.assertTrue(base.tzinfo is base3.tzinfo) + + # Out of bounds. + base = cls(2000, 2, 29) + self.assertRaises(ValueError, base.replace, year=2001) + + def test_more_astimezone(self): + # The inherited test_astimezone covered some trivial and error cases. + fnone = FixedOffset(None, "None") + f44m = FixedOffset(44, "44") + fm5h = FixedOffset(-timedelta(hours=5), "m300") + + dt = self.theclass.now(tz=f44m) + self.assertTrue(dt.tzinfo is f44m) + # Replacing with degenerate tzinfo raises an exception. + self.assertRaises(ValueError, dt.astimezone, fnone) + # Ditto with None tz. + self.assertRaises(TypeError, dt.astimezone, None) + # Replacing with same tzinfo makes no change. + x = dt.astimezone(dt.tzinfo) + self.assertTrue(x.tzinfo is f44m) + self.assertEqual(x.date(), dt.date()) + self.assertEqual(x.time(), dt.time()) + + # Replacing with different tzinfo does adjust. + got = dt.astimezone(fm5h) + self.assertTrue(got.tzinfo is fm5h) + self.assertEqual(got.utcoffset(), timedelta(hours=-5)) + expected = dt - dt.utcoffset() # in effect, convert to UTC + expected += fm5h.utcoffset(dt) # and from there to local time + expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo + self.assertEqual(got.date(), expected.date()) + self.assertEqual(got.time(), expected.time()) + self.assertEqual(got.timetz(), expected.timetz()) + self.assertTrue(got.tzinfo is expected.tzinfo) + self.assertEqual(got, expected) + + def test_aware_subtract(self): + cls = self.theclass + + # Ensure that utcoffset() is ignored when the operands have the + # same tzinfo member. + class OperandDependentOffset(tzinfo): + def utcoffset(self, t): + if t.minute < 10: + # d0 and d1 equal after adjustment + return timedelta(minutes=t.minute) + else: + # d2 off in the weeds + return timedelta(minutes=59) + + base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset()) + d0 = base.replace(minute=3) + d1 = base.replace(minute=9) + d2 = base.replace(minute=11) + for x in d0, d1, d2: + for y in d0, d1, d2: + got = x - y + expected = timedelta(minutes=x.minute - y.minute) + self.assertEqual(got, expected) + + # OTOH, if the tzinfo members are distinct, utcoffsets aren't + # ignored. + base = cls(8, 9, 10, 11, 12, 13, 14) + d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) + d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) + d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) + for x in d0, d1, d2: + for y in d0, d1, d2: + got = x - y + if (x is d0 or x is d1) and (y is d0 or y is d1): + expected = timedelta(0) + elif x is y is d2: + expected = timedelta(0) + elif x is d2: + expected = timedelta(minutes=(11-59)-0) + else: + assert y is d2 + expected = timedelta(minutes=0-(11-59)) + self.assertEqual(got, expected) + + def test_mixed_compare(self): + t1 = datetime(1, 2, 3, 4, 5, 6, 7) + t2 = datetime(1, 2, 3, 4, 5, 6, 7) + self.assertEqual(t1, t2) + t2 = t2.replace(tzinfo=None) + self.assertEqual(t1, t2) + t2 = t2.replace(tzinfo=FixedOffset(None, "")) + self.assertEqual(t1, t2) + t2 = t2.replace(tzinfo=FixedOffset(0, "")) + self.assertRaises(TypeError, lambda: t1 == t2) + + # In datetime w/ identical tzinfo objects, utcoffset is ignored. + class Varies(tzinfo): + def __init__(self): + self.offset = timedelta(minutes=22) + def utcoffset(self, t): + self.offset += timedelta(minutes=1) + return self.offset + + v = Varies() + t1 = t2.replace(tzinfo=v) + t2 = t2.replace(tzinfo=v) + self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) + self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) + self.assertEqual(t1, t2) + + # But if they're not identical, it isn't ignored. + t2 = t2.replace(tzinfo=Varies()) + self.assertTrue(t1 < t2) # t1's offset counter still going up + + def test_subclass_datetimetz(self): + + class C(self.theclass): + theAnswer = 42 + + def __new__(cls, *args, **kws): + temp = kws.copy() + extra = temp.pop('extra') + result = self.theclass.__new__(cls, *args, **temp) + result.extra = extra + return result + + def newmeth(self, start): + return start + self.hour + self.year + + args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1) + + dt1 = self.theclass(*args) + dt2 = C(*args, **{'extra': 7}) + + self.assertEqual(dt2.__class__, C) + self.assertEqual(dt2.theAnswer, 42) + self.assertEqual(dt2.extra, 7) + self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) + self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7) + +# Pain to set up DST-aware tzinfo classes. + +def first_sunday_on_or_after(dt): + days_to_go = 6 - dt.weekday() + if days_to_go: + dt += timedelta(days_to_go) + return dt + +ZERO = timedelta(0) +MINUTE = timedelta(minutes=1) +HOUR = timedelta(hours=1) +DAY = timedelta(days=1) +# In the US, DST starts at 2am (standard time) on the first Sunday in April. +DSTSTART = datetime(1, 4, 1, 2) +# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct, +# which is the first Sunday on or after Oct 25. Because we view 1:MM as +# being standard time on that day, there is no spelling in local time of +# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time). +DSTEND = datetime(1, 10, 25, 1) + +class USTimeZone(tzinfo): + + def __init__(self, hours, reprname, stdname, dstname): + self.stdoffset = timedelta(hours=hours) + self.reprname = reprname + self.stdname = stdname + self.dstname = dstname + + def __repr__(self): + return self.reprname + + def tzname(self, dt): + if self.dst(dt): + return self.dstname + else: + return self.stdname + + def utcoffset(self, dt): + return self.stdoffset + self.dst(dt) + + def dst(self, dt): + if dt is None or dt.tzinfo is None: + # An exception instead may be sensible here, in one or more of + # the cases. + return ZERO + assert dt.tzinfo is self + + # Find first Sunday in April. + start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) + assert start.weekday() == 6 and start.month == 4 and start.day <= 7 + + # Find last Sunday in October. + end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) + assert end.weekday() == 6 and end.month == 10 and end.day >= 25 + + # Can't compare naive to aware objects, so strip the timezone from + # dt first. + if start <= dt.replace(tzinfo=None) < end: + return HOUR + else: + return ZERO + +Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") +Central = USTimeZone(-6, "Central", "CST", "CDT") +Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") +Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") +utc_real = FixedOffset(0, "UTC", 0) +# For better test coverage, we want another flavor of UTC that's west of +# the Eastern and Pacific timezones. +utc_fake = FixedOffset(-12*60, "UTCfake", 0) + +class TestTimezoneConversions(unittest.TestCase): + # The DST switch times for 2002, in std time. + dston = datetime(2002, 4, 7, 2) + dstoff = datetime(2002, 10, 27, 1) + + theclass = datetime + + # Check a time that's inside DST. + def checkinside(self, dt, tz, utc, dston, dstoff): + self.assertEqual(dt.dst(), HOUR) + + # Conversion to our own timezone is always an identity. + self.assertEqual(dt.astimezone(tz), dt) + + asutc = dt.astimezone(utc) + there_and_back = asutc.astimezone(tz) + + # Conversion to UTC and back isn't always an identity here, + # because there are redundant spellings (in local time) of + # UTC time when DST begins: the clock jumps from 1:59:59 + # to 3:00:00, and a local time of 2:MM:SS doesn't really + # make sense then. The classes above treat 2:MM:SS as + # daylight time then (it's "after 2am"), really an alias + # for 1:MM:SS standard time. The latter form is what + # conversion back from UTC produces. + if dt.date() == dston.date() and dt.hour == 2: + # We're in the redundant hour, and coming back from + # UTC gives the 1:MM:SS standard-time spelling. + self.assertEqual(there_and_back + HOUR, dt) + # Although during was considered to be in daylight + # time, there_and_back is not. + self.assertEqual(there_and_back.dst(), ZERO) + # They're the same times in UTC. + self.assertEqual(there_and_back.astimezone(utc), + dt.astimezone(utc)) + else: + # We're not in the redundant hour. + self.assertEqual(dt, there_and_back) + + # Because we have a redundant spelling when DST begins, there is + # (unforunately) an hour when DST ends that can't be spelled at all in + # local time. When DST ends, the clock jumps from 1:59 back to 1:00 + # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be + # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be + # daylight time. The hour 1:MM daylight == 0:MM standard can't be + # expressed in local time. Nevertheless, we want conversion back + # from UTC to mimic the local clock's "repeat an hour" behavior. + nexthour_utc = asutc + HOUR + nexthour_tz = nexthour_utc.astimezone(tz) + if dt.date() == dstoff.date() and dt.hour == 0: + # We're in the hour before the last DST hour. The last DST hour + # is ineffable. We want the conversion back to repeat 1:MM. + self.assertEqual(nexthour_tz, dt.replace(hour=1)) + nexthour_utc += HOUR + nexthour_tz = nexthour_utc.astimezone(tz) + self.assertEqual(nexthour_tz, dt.replace(hour=1)) + else: + self.assertEqual(nexthour_tz - dt, HOUR) + + # Check a time that's outside DST. + def checkoutside(self, dt, tz, utc): + self.assertEqual(dt.dst(), ZERO) + + # Conversion to our own timezone is always an identity. + self.assertEqual(dt.astimezone(tz), dt) + + # Converting to UTC and back is an identity too. + asutc = dt.astimezone(utc) + there_and_back = asutc.astimezone(tz) + self.assertEqual(dt, there_and_back) + + def convert_between_tz_and_utc(self, tz, utc): + dston = self.dston.replace(tzinfo=tz) + # Because 1:MM on the day DST ends is taken as being standard time, + # there is no spelling in tz for the last hour of daylight time. + # For purposes of the test, the last hour of DST is 0:MM, which is + # taken as being daylight time (and 1:MM is taken as being standard + # time). + dstoff = self.dstoff.replace(tzinfo=tz) + for delta in (timedelta(weeks=13), + DAY, + HOUR, + timedelta(minutes=1), + timedelta(microseconds=1)): + + self.checkinside(dston, tz, utc, dston, dstoff) + for during in dston + delta, dstoff - delta: + self.checkinside(during, tz, utc, dston, dstoff) + + self.checkoutside(dstoff, tz, utc) + for outside in dston - delta, dstoff + delta: + self.checkoutside(outside, tz, utc) + + def test_easy(self): + # Despite the name of this test, the endcases are excruciating. + self.convert_between_tz_and_utc(Eastern, utc_real) + self.convert_between_tz_and_utc(Pacific, utc_real) + self.convert_between_tz_and_utc(Eastern, utc_fake) + self.convert_between_tz_and_utc(Pacific, utc_fake) + # The next is really dancing near the edge. It works because + # Pacific and Eastern are far enough apart that their "problem + # hours" don't overlap. + self.convert_between_tz_and_utc(Eastern, Pacific) + self.convert_between_tz_and_utc(Pacific, Eastern) + # OTOH, these fail! Don't enable them. The difficulty is that + # the edge case tests assume that every hour is representable in + # the "utc" class. This is always true for a fixed-offset tzinfo + # class (lke utc_real and utc_fake), but not for Eastern or Central. + # For these adjacent DST-aware time zones, the range of time offsets + # tested ends up creating hours in the one that aren't representable + # in the other. For the same reason, we would see failures in the + # Eastern vs Pacific tests too if we added 3*HOUR to the list of + # offset deltas in convert_between_tz_and_utc(). + # + # self.convert_between_tz_and_utc(Eastern, Central) # can't work + # self.convert_between_tz_and_utc(Central, Eastern) # can't work + + def test_tricky(self): + # 22:00 on day before daylight starts. + fourback = self.dston - timedelta(hours=4) + ninewest = FixedOffset(-9*60, "-0900", 0) + fourback = fourback.replace(tzinfo=ninewest) + # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after + # 2", we should get the 3 spelling. + # If we plug 22:00 the day before into Eastern, it "looks like std + # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4 + # to 22:00 lands on 2:00, which makes no sense in local time (the + # local clock jumps from 1 to 3). The point here is to make sure we + # get the 3 spelling. + expected = self.dston.replace(hour=3) + got = fourback.astimezone(Eastern).replace(tzinfo=None) + self.assertEqual(expected, got) + + # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that + # case we want the 1:00 spelling. + sixutc = self.dston.replace(hour=6, tzinfo=utc_real) + # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4, + # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST + # spelling. + expected = self.dston.replace(hour=1) + got = sixutc.astimezone(Eastern).replace(tzinfo=None) + self.assertEqual(expected, got) + + # Now on the day DST ends, we want "repeat an hour" behavior. + # UTC 4:MM 5:MM 6:MM 7:MM checking these + # EST 23:MM 0:MM 1:MM 2:MM + # EDT 0:MM 1:MM 2:MM 3:MM + # wall 0:MM 1:MM 1:MM 2:MM against these + for utc in utc_real, utc_fake: + for tz in Eastern, Pacific: + first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM + # Convert that to UTC. + first_std_hour -= tz.utcoffset(None) + # Adjust for possibly fake UTC. + asutc = first_std_hour + utc.utcoffset(None) + # First UTC hour to convert; this is 4:00 when utc=utc_real & + # tz=Eastern. + asutcbase = asutc.replace(tzinfo=utc) + for tzhour in (0, 1, 1, 2): + expectedbase = self.dstoff.replace(hour=tzhour) + for minute in 0, 30, 59: + expected = expectedbase.replace(minute=minute) + asutc = asutcbase.replace(minute=minute) + astz = asutc.astimezone(tz) + self.assertEqual(astz.replace(tzinfo=None), expected) + asutcbase += HOUR + + + def test_bogus_dst(self): + class ok(tzinfo): + def utcoffset(self, dt): return HOUR + def dst(self, dt): return HOUR + + now = self.theclass.now().replace(tzinfo=utc_real) + # Doesn't blow up. + now.astimezone(ok()) + + # Does blow up. + class notok(ok): + def dst(self, dt): return None + self.assertRaises(ValueError, now.astimezone, notok()) + + # Sometimes blow up. In the following, tzinfo.dst() + # implementation may return None or not None depending on + # whether DST is assumed to be in effect. In this situation, + # a ValueError should be raised by astimezone(). + class tricky_notok(ok): + def dst(self, dt): + if dt.year == 2000: + return None + else: + return 10*HOUR + dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real) + self.assertRaises(ValueError, dt.astimezone, tricky_notok()) + + def test_fromutc(self): + self.assertRaises(TypeError, Eastern.fromutc) # not enough args + now = datetime.utcnow().replace(tzinfo=utc_real) + self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo + now = now.replace(tzinfo=Eastern) # insert correct tzinfo + enow = Eastern.fromutc(now) # doesn't blow up + self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member + self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args + self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type + + # Always converts UTC to standard time. + class FauxUSTimeZone(USTimeZone): + def fromutc(self, dt): + return dt + self.stdoffset + FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT") + + # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM + # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM + # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM + + # Check around DST start. + start = self.dston.replace(hour=4, tzinfo=Eastern) + fstart = start.replace(tzinfo=FEastern) + for wall in 23, 0, 1, 3, 4, 5: + expected = start.replace(hour=wall) + if wall == 23: + expected -= timedelta(days=1) + got = Eastern.fromutc(start) + self.assertEqual(expected, got) + + expected = fstart + FEastern.stdoffset + got = FEastern.fromutc(fstart) + self.assertEqual(expected, got) + + # Ensure astimezone() calls fromutc() too. + got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) + self.assertEqual(expected, got) + + start += HOUR + fstart += HOUR + + # Check around DST end. + start = self.dstoff.replace(hour=4, tzinfo=Eastern) + fstart = start.replace(tzinfo=FEastern) + for wall in 0, 1, 1, 2, 3, 4: + expected = start.replace(hour=wall) + got = Eastern.fromutc(start) + self.assertEqual(expected, got) + + expected = fstart + FEastern.stdoffset + got = FEastern.fromutc(fstart) + self.assertEqual(expected, got) + + # Ensure astimezone() calls fromutc() too. + got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) + self.assertEqual(expected, got) + + start += HOUR + fstart += HOUR + + +############################################################################# +# oddballs + +class Oddballs(unittest.TestCase): + + def test_bug_1028306(self): + # Trying to compare a date to a datetime should act like a mixed- + # type comparison, despite that datetime is a subclass of date. + as_date = date.today() + as_datetime = datetime.combine(as_date, time()) + self.assertTrue(as_date != as_datetime) + self.assertTrue(as_datetime != as_date) + self.assertTrue(not as_date == as_datetime) + self.assertTrue(not as_datetime == as_date) + self.assertRaises(TypeError, lambda: as_date < as_datetime) + self.assertRaises(TypeError, lambda: as_datetime < as_date) + self.assertRaises(TypeError, lambda: as_date <= as_datetime) + self.assertRaises(TypeError, lambda: as_datetime <= as_date) + self.assertRaises(TypeError, lambda: as_date > as_datetime) + self.assertRaises(TypeError, lambda: as_datetime > as_date) + self.assertRaises(TypeError, lambda: as_date >= as_datetime) + self.assertRaises(TypeError, lambda: as_datetime >= as_date) + + # Neverthelss, comparison should work with the base-class (date) + # projection if use of a date method is forced. + self.assertEqual(as_date.__eq__(as_datetime), True) + different_day = (as_date.day + 1) % 20 + 1 + as_different = as_datetime.replace(day= different_day) + self.assertEqual(as_date.__eq__(as_different), False) + + # And date should compare with other subclasses of date. If a + # subclass wants to stop this, it's up to the subclass to do so. + date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) + self.assertEqual(as_date, date_sc) + self.assertEqual(date_sc, as_date) + + # Ditto for datetimes. + datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month, + as_date.day, 0, 0, 0) + self.assertEqual(as_datetime, datetime_sc) + self.assertEqual(datetime_sc, as_datetime) + +def test_main(): + support.run_unittest(__name__) + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index bb36001..ded2aa9 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -1,3668 +1,42 @@ -"""Test date/time type. - -See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases -""" - -import sys -import pickle import unittest - -from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod - -from test import support - -from datetime import MINYEAR, MAXYEAR -from datetime import timedelta -from datetime import tzinfo -from datetime import time -from datetime import timezone -from datetime import date, datetime -import time as _time - -pickle_choices = [(pickle, pickle, proto) - for proto in range(pickle.HIGHEST_PROTOCOL + 1)] -assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1 - -# An arbitrary collection of objects of non-datetime types, for testing -# mixed-type comparisons. -OTHERSTUFF = (10, 34.5, "abc", {}, [], ()) - - -# XXX Copied from test_float. -INF = float("inf") -NAN = float("nan") - -# decorator for skipping tests on non-IEEE 754 platforms -requires_IEEE_754 = unittest.skipUnless( - float.__getformat__("double").startswith("IEEE"), - "test requires IEEE 754 doubles") - - -############################################################################# -# 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): - if isinstance(offset, int): - offset = timedelta(minutes=offset) - if isinstance(dstoffset, int): - dstoffset = timedelta(minutes=dstoffset) - 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 PicklableFixedOffset(FixedOffset): - - def __init__(self, offset=None, name=None, dstoffset=None): - FixedOffset.__init__(self, offset, name, 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.assertTrue(issubclass(NotEnough, tzinfo)) - ne = NotEnough(3, "NotByALongShot") - self.assertIsInstance(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.assertIsInstance(fo, tzinfo) - for dt in datetime.now(), None: - self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3)) - self.assertEqual(fo.tzname(dt), "Three") - self.assertEqual(fo.dst(dt), timedelta(minutes=42)) - - def test_pickling_base(self): - # 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.assertTrue(type(orig) is tzinfo) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertTrue(type(derived) is tzinfo) - - def test_pickling_subclass(self): - # Make sure we can pickle/unpickle an instance of a subclass. - offset = timedelta(minutes=-300) - for otype, args in [ - (PicklableFixedOffset, (offset, 'cookie')), - (timezone, (offset,)), - (timezone, (offset, "EST"))]: - orig = otype(*args) - oname = orig.tzname(None) - self.assertIsInstance(orig, tzinfo) - self.assertIs(type(orig), otype) - self.assertEqual(orig.utcoffset(None), offset) - self.assertEqual(orig.tzname(None), oname) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertIsInstance(derived, tzinfo) - self.assertIs(type(derived), otype) - self.assertEqual(derived.utcoffset(None), offset) - self.assertEqual(derived.tzname(None), oname) - -class TestTimeZone(unittest.TestCase): - - def setUp(self): - self.ACDT = timezone(timedelta(hours=9.5), 'ACDT') - self.EST = timezone(-timedelta(hours=5), 'EST') - self.DT = datetime(2010, 1, 1) - - def test_str(self): - for tz in [self.ACDT, self.EST, timezone.utc, - timezone.min, timezone.max]: - self.assertEqual(str(tz), tz.tzname(None)) - - def test_repr(self): - import datetime - for tz in [self.ACDT, self.EST, timezone.utc, - timezone.min, timezone.max]: - # test round-trip - tzrep = repr(tz) - self.assertEqual(tz, eval(tzrep)) - - - def test_class_members(self): - limit = timedelta(hours=23, minutes=59) - self.assertEqual(timezone.utc.utcoffset(None), ZERO) - self.assertEqual(timezone.min.utcoffset(None), -limit) - self.assertEqual(timezone.max.utcoffset(None), limit) - - - def test_constructor(self): - self.assertEqual(timezone.utc, timezone(timedelta(0))) - # invalid offsets - for invalid in [timedelta(microseconds=1), timedelta(1, 1), - timedelta(seconds=1), timedelta(1), -timedelta(1)]: - self.assertRaises(ValueError, timezone, invalid) - self.assertRaises(ValueError, timezone, -invalid) - - with self.assertRaises(TypeError): timezone(None) - with self.assertRaises(TypeError): timezone(42) - with self.assertRaises(TypeError): timezone(ZERO, None) - with self.assertRaises(TypeError): timezone(ZERO, 42) - with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra') - - def test_inheritance(self): - self.assertIsInstance(timezone.utc, tzinfo) - self.assertIsInstance(self.EST, tzinfo) - - def test_utcoffset(self): - dummy = self.DT - for h in [0, 1.5, 12]: - offset = h * HOUR - self.assertEqual(offset, timezone(offset).utcoffset(dummy)) - self.assertEqual(-offset, timezone(-offset).utcoffset(dummy)) - - with self.assertRaises(TypeError): self.EST.utcoffset('') - with self.assertRaises(TypeError): self.EST.utcoffset(5) - - - def test_dst(self): - self.assertEqual(None, timezone.utc.dst(self.DT)) - - with self.assertRaises(TypeError): self.EST.dst('') - with self.assertRaises(TypeError): self.EST.dst(5) - - def test_tzname(self): - self.assertEqual('UTC+00:00', timezone(ZERO).tzname(None)) - self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None)) - self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None)) - self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None)) - self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None)) - - with self.assertRaises(TypeError): self.EST.tzname('') - with self.assertRaises(TypeError): self.EST.tzname(5) - - def test_fromutc(self): - with self.assertRaises(ValueError): - timezone.utc.fromutc(self.DT) - with self.assertRaises(TypeError): - timezone.utc.fromutc('not datetime') - for tz in [self.EST, self.ACDT, Eastern]: - utctime = self.DT.replace(tzinfo=tz) - local = tz.fromutc(utctime) - self.assertEqual(local - utctime, tz.utcoffset(local)) - self.assertEqual(local, - self.DT.replace(tzinfo=timezone.utc)) - - def test_comparison(self): - self.assertNotEqual(timezone(ZERO), timezone(HOUR)) - self.assertEqual(timezone(HOUR), timezone(HOUR)) - self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST')) - with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO) - self.assertIn(timezone(ZERO), {timezone(ZERO)}) - - def test_aware_datetime(self): - # test that timezone instances can be used by datetime - t = datetime(1, 1, 1) - for tz in [timezone.min, timezone.max, timezone.utc]: - self.assertEqual(tz.tzname(t), - t.replace(tzinfo=tz).tzname()) - self.assertEqual(tz.utcoffset(t), - t.replace(tzinfo=tz).utcoffset()) - self.assertEqual(tz.dst(t), - t.replace(tzinfo=tz).dst()) - -############################################################################# -# Base clase for testing a particular aspect of timedelta, time, date and -# datetime comparisons. - -class HarmlessMixedComparison: - # Test that __eq__ and __ne__ don't complain for mixed-type comparisons. - - # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a - # legit constructor. - - def test_harmless_mixed_comparison(self): - me = self.theclass(1, 1, 1) - - self.assertFalse(me == ()) - self.assertTrue(me != ()) - self.assertFalse(() == me) - self.assertTrue(() != me) - - self.assertIn(me, [1, 20, [], me]) - self.assertIn([], [me, 1, 20, []]) - - def test_harmful_mixed_comparison(self): - me = self.theclass(1, 1, 1) - - self.assertRaises(TypeError, lambda: me < ()) - self.assertRaises(TypeError, lambda: me <= ()) - self.assertRaises(TypeError, lambda: me > ()) - self.assertRaises(TypeError, lambda: me >= ()) - - self.assertRaises(TypeError, lambda: () < me) - self.assertRaises(TypeError, lambda: () <= me) - self.assertRaises(TypeError, lambda: () > me) - self.assertRaises(TypeError, lambda: () >= me) - -############################################################################# -# timedelta tests - -class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): - - theclass = timedelta - - 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(b.__rsub__(a), 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*10, 10*a) - eq(b*10, td(0, 600)) - eq(10*b, td(0, 600)) - eq(b*10, td(0, 600)) - eq(c*10, td(0, 0, 10000)) - eq(10*c, td(0, 0, 10000)) - eq(c*10, 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)) - eq(a/0.5, td(14)) - eq(b/0.5, td(0, 120)) - 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)) - - # Multiplication by float - us = td(microseconds=1) - eq((3*us) * 0.5, 2*us) - eq((5*us) * 0.5, 2*us) - eq(0.5 * (3*us), 2*us) - eq(0.5 * (5*us), 2*us) - eq((-3*us) * 0.5, -2*us) - eq((-5*us) * 0.5, -2*us) - - # Division by int and float - eq((3*us) / 2, 2*us) - eq((5*us) / 2, 2*us) - eq((-3*us) / 2.0, -2*us) - eq((-5*us) / 2.0, -2*us) - eq((3*us) / -2, -2*us) - eq((5*us) / -2, -2*us) - eq((3*us) / -2.0, -2*us) - eq((5*us) / -2.0, -2*us) - for i in range(-10, 10): - eq((i*us/3)//us, round(i/3)) - for i in range(-10, 10): - eq((i*us/-3)//us, round(i/-3)) - - def test_disallowed_computations(self): - a = timedelta(42) - - # Add/sub ints or floats should be illegal - for i in 1, 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) - - # Division of int by timedelta doesn't make sense. - # Division by zero doesn't make sense. - zero = 0 - self.assertRaises(TypeError, lambda: zero // a) - self.assertRaises(ZeroDivisionError, lambda: a // zero) - self.assertRaises(ZeroDivisionError, lambda: a / zero) - self.assertRaises(ZeroDivisionError, lambda: a / 0.0) - self.assertRaises(TypeError, lambda: a / '') - - @requires_IEEE_754 - def test_disallowed_special(self): - a = timedelta(42) - self.assertRaises(ValueError, a.__mul__, NAN) - self.assertRaises(ValueError, a.__truediv__, NAN) - - 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_total_seconds(self): - td = timedelta(days=365) - self.assertEqual(td.total_seconds(), 31536000.0) - for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]: - td = timedelta(seconds=total_seconds) - self.assertEqual(td.total_seconds(), total_seconds) - # Issue8644: Test that td.total_seconds() has the same - # accuracy as td / timedelta(seconds=1). - for ms in [-1, -2, -123]: - td = timedelta(microseconds=ms) - self.assertEqual(td.total_seconds(), td / timedelta(seconds=1)) - - 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): - args = 12, 34, 56 - orig = timedelta(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_compare(self): - t1 = timedelta(2, 3, 4) - t2 = timedelta(2, 3, 4) - self.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): - t2 = timedelta(*args) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - for badarg in OTHERSTUFF: - self.assertEqual(t1 == badarg, False) - self.assertEqual(t1 != badarg, True) - self.assertEqual(badarg == t1, False) - self.assertEqual(badarg != t1, True) - - 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) - - 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_repr(self): - name = 'datetime.' + self.theclass.__name__ - self.assertEqual(repr(self.theclass(1)), - "%s(1)" % name) - self.assertEqual(repr(self.theclass(10, 2)), - "%s(10, 2)" % name) - self.assertEqual(repr(self.theclass(-10, 2, 400000)), - "%s(-10, 2, 400000)" % name) - - def test_roundtrip(self): - for td in (timedelta(days=999999999, hours=23, minutes=59, - seconds=59, microseconds=999999), - timedelta(days=-999999999), - timedelta(days=-999999999, seconds=1), - timedelta(days=1, seconds=2, microseconds=3)): - - # Verify td -> string -> td identity. - s = repr(td) - self.assertTrue(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.assertIsInstance(timedelta.min, timedelta) - self.assertIsInstance(timedelta.max, timedelta) - self.assertIsInstance(timedelta.resolution, timedelta) - self.assertTrue(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) - - day = timedelta(1) - self.assertRaises(OverflowError, day.__mul__, 10**9) - self.assertRaises(OverflowError, day.__mul__, 1e9) - self.assertRaises(OverflowError, day.__truediv__, 1e-20) - self.assertRaises(OverflowError, day.__truediv__, 1e-10) - self.assertRaises(OverflowError, day.__truediv__, 9e-10) - - @requires_IEEE_754 - def _test_overflow_special(self): - day = timedelta(1) - self.assertRaises(OverflowError, day.__mul__, INF) - self.assertRaises(OverflowError, day.__mul__, -INF) - - 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.assertTrue(timedelta(1)) - self.assertTrue(timedelta(0, 1)) - self.assertTrue(timedelta(0, 0, 1)) - self.assertTrue(timedelta(microseconds=1)) - self.assertTrue(not timedelta(0)) - - def test_subclass_timedelta(self): - - class T(timedelta): - @staticmethod - def from_td(td): - return T(td.days, td.seconds, td.microseconds) - - def as_hours(self): - sum = (self.days * 24 + - self.seconds / 3600.0 + - self.microseconds / 3600e6) - return round(sum) - - t1 = T(days=1) - self.assertTrue(type(t1) is T) - self.assertEqual(t1.as_hours(), 24) - - t2 = T(days=-1, seconds=-3600) - self.assertTrue(type(t2) is T) - self.assertEqual(t2.as_hours(), -25) - - t3 = t1 + t2 - self.assertTrue(type(t3) is timedelta) - t4 = T.from_td(t3) - self.assertTrue(type(t4) is T) - self.assertEqual(t3.days, t4.days) - self.assertEqual(t3.seconds, t4.seconds) - self.assertEqual(t3.microseconds, t4.microseconds) - self.assertEqual(str(t3), str(t4)) - self.assertEqual(t4.as_hours(), -1) - - def test_division(self): - t = timedelta(hours=1, minutes=24, seconds=19) - second = timedelta(seconds=1) - self.assertEqual(t / second, 5059.0) - self.assertEqual(t // second, 5059) - - t = timedelta(minutes=2, seconds=30) - minute = timedelta(minutes=1) - self.assertEqual(t / minute, 2.5) - self.assertEqual(t // minute, 2) - - zerotd = timedelta(0) - self.assertRaises(ZeroDivisionError, truediv, t, zerotd) - self.assertRaises(ZeroDivisionError, floordiv, t, zerotd) - - # self.assertRaises(TypeError, truediv, t, 2) - # note: floor division of a timedelta by an integer *is* - # currently permitted. - - def test_remainder(self): - t = timedelta(minutes=2, seconds=30) - minute = timedelta(minutes=1) - r = t % minute - self.assertEqual(r, timedelta(seconds=30)) - - t = timedelta(minutes=-2, seconds=30) - r = t % minute - self.assertEqual(r, timedelta(seconds=30)) - - zerotd = timedelta(0) - self.assertRaises(ZeroDivisionError, mod, t, zerotd) - - self.assertRaises(TypeError, mod, t, 10) - - def test_divmod(self): - t = timedelta(minutes=2, seconds=30) - minute = timedelta(minutes=1) - q, r = divmod(t, minute) - self.assertEqual(q, 2) - self.assertEqual(r, timedelta(seconds=30)) - - t = timedelta(minutes=-2, seconds=30) - q, r = divmod(t, minute) - self.assertEqual(q, -2) - self.assertEqual(r, timedelta(seconds=30)) - - zerotd = timedelta(0) - self.assertRaises(ZeroDivisionError, divmod, t, zerotd) - - self.assertRaises(TypeError, divmod, t, 10) - - -############################################################################# -# 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 SubclassDate(date): - sub_var = 1 - -class TestDate(HarmlessMixedComparison, 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.assertTrue(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 range(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) - c = self.theclass(2001,2,1) - - 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) - self.assertEqual(c - (c - day), day) - - # Add/sub ints or floats should be illegal - for i in 1, 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 - - for delta in [tiny, timedelta(1), timedelta(2)]: - dt = self.theclass.min + delta - dt -= delta # no problem - self.assertRaises(OverflowError, dt.__sub__, delta) - self.assertRaises(OverflowError, dt.__add__, -delta) - - dt = self.theclass.max - delta - dt += delta # no problem - self.assertRaises(OverflowError, dt.__add__, delta) - self.assertRaises(OverflowError, dt.__sub__, -delta) - - 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_insane_fromtimestamp(self): - # It's possible that some platform maps time_t to double, - # and that this test will fail there. This test should - # exempt such platforms (provided they return reasonable - # results!). - for insane in -1e200, 1e200: - self.assertRaises(ValueError, self.theclass.fromtimestamp, - insane) - - 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.assertTrue(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 = sorted(map(int, ISO_LONG_YEARS_TABLE.split())) - 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.assertEqual(t.strftime(""), "") # SF bug #761337 - self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784 - - 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 - - # test that unicode input is allowed (issue 2782) - self.assertEqual(t.strftime("%m"), "03") - - # A naive object replaces %z and %Z w/ empty strings. - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") - - #make sure that invalid format specifiers are handled correctly - #self.assertRaises(ValueError, t.strftime, "%e") - #self.assertRaises(ValueError, t.strftime, "%") - #self.assertRaises(ValueError, t.strftime, "%#") - - #oh well, some systems just ignore those invalid ones. - #at least, excercise them to make sure that no crashes - #are generated - for f in ["%e", "%", "%#"]: - try: - t.strftime(f) - except ValueError: - pass - - #check that this standard extension works - t.strftime("%f") - - - def test_format(self): - dt = self.theclass(2007, 9, 10) - self.assertEqual(dt.__format__(''), str(dt)) - - # check that a derived class's __str__() gets called - class A(self.theclass): - def __str__(self): - return 'A' - a = A(2007, 9, 10) - self.assertEqual(a.__format__(''), 'A') - - # check that a derived class's strftime gets called - class B(self.theclass): - def strftime(self, format_spec): - return 'B' - b = B(2007, 9, 10) - self.assertEqual(b.__format__(''), str(dt)) - - for fmt in ["m:%m d:%d y:%y", - "m:%m d:%d y:%y H:%H M:%M S:%S", - "%z %Z", - ]: - self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(b.__format__(fmt), 'B') - - def test_resolution_info(self): - # XXX: Should min and max respect subclassing? - if issubclass(self.theclass, datetime): - expected_class = datetime - else: - expected_class = date - self.assertIsInstance(self.theclass.min, expected_class) - self.assertIsInstance(self.theclass.max, expected_class) - self.assertIsInstance(self.theclass.resolution, timedelta) - self.assertTrue(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): - args = 6, 7, 23 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_compare(self): - t1 = self.theclass(2, 3, 4) - t2 = self.theclass(2, 3, 4) - self.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): - t2 = self.theclass(*args) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - for badarg in OTHERSTUFF: - self.assertEqual(t1 == badarg, False) - self.assertEqual(t1 != badarg, True) - self.assertEqual(badarg == t1, False) - self.assertEqual(badarg != t1, True) - - 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) - - def test_mixed_compare(self): - our = self.theclass(2000, 4, 5) - - # Our class can be compared for equality to other classes - self.assertEqual(our == 1, False) - self.assertEqual(1 == our, False) - self.assertEqual(our != 1, True) - self.assertEqual(1 != our, True) - - # But the ordering is undefined - self.assertRaises(TypeError, lambda: our < 1) - self.assertRaises(TypeError, lambda: 1 < our) - - # Repeat those tests with a different class - - class SomeClass: - pass - - their = SomeClass() - self.assertEqual(our == their, False) - self.assertEqual(their == our, False) - self.assertEqual(our != their, True) - self.assertEqual(their != our, True) - self.assertRaises(TypeError, lambda: our < their) - self.assertRaises(TypeError, lambda: their < our) - - # However, if the other class explicitly defines ordering - # relative to our class, it is allowed to do so - - class LargerThanAnything: - def __lt__(self, other): - return False - def __le__(self, other): - return isinstance(other, LargerThanAnything) - def __eq__(self, other): - return isinstance(other, LargerThanAnything) - def __ne__(self, other): - return not isinstance(other, LargerThanAnything) - def __gt__(self, other): - return not isinstance(other, LargerThanAnything) - def __ge__(self, other): - return True - - their = LargerThanAnything() - self.assertEqual(our == their, False) - self.assertEqual(their == our, False) - self.assertEqual(our != their, True) - self.assertEqual(their != our, True) - self.assertEqual(our < their, True) - self.assertEqual(their < our, False) - - def test_bool(self): - # All dates are considered true. - self.assertTrue(self.theclass.min) - self.assertTrue(self.theclass.max) - - def test_strftime_out_of_range(self): - # For nasty technical reasons, we can't handle years before 1900. - cls = self.theclass - self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900") - for y in 1, 49, 51, 99, 100, 1000, 1899: - self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y") - - def test_replace(self): - cls = self.theclass - args = [1, 2, 3] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("year", 2), - ("month", 3), - ("day", 4)): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Out of bounds. - base = cls(2000, 2, 29) - self.assertRaises(ValueError, base.replace, year=2001) - - def test_subclass_date(self): - - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop('extra') - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.year + self.month - - args = 2003, 4, 14 - - dt1 = self.theclass(*args) - dt2 = C(*args, **{'extra': 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.toordinal(), dt2.toordinal()) - self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7) - - def test_pickling_subclass_date(self): - - args = 6, 7, 23 - orig = SubclassDate(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_backdoor_resistance(self): - # For fast unpickling, the constructor accepts a pickle byte string. - # This is a low-overhead backdoor. A user can (by intent or - # mistake) pass a string directly, which (if it's the right length) - # will get treated like a pickle, and bypass the normal sanity - # checks in the constructor. This can create insane objects. - # The constructor doesn't want to burn the time to validate all - # fields, but does check the month field. This stops, e.g., - # datetime.datetime('1995-03-25') from yielding an insane object. - base = b'1995-03-25' - if not issubclass(self.theclass, datetime): - base = base[:4] - for month_byte in b'9', b'\0', b'\r', b'\xff': - self.assertRaises(TypeError, self.theclass, - base[:2] + month_byte + base[3:]) - # Good bytes, but bad tzinfo: - self.assertRaises(TypeError, self.theclass, - bytes([1] * len(base)), 'EST') - - for ord_byte in range(1, 13): - # This shouldn't blow up because of the month byte alone. If - # the implementation changes to do more-careful checking, it may - # blow up because other fields are insane. - self.theclass(base[:2] + bytes([ord_byte]) + base[3:]) - -############################################################################# -# datetime tests - -class SubclassDatetime(datetime): - sub_var = 1 - -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.assertTrue(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") - self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004: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_format(self): - dt = self.theclass(2007, 9, 10, 4, 5, 1, 123) - self.assertEqual(dt.__format__(''), str(dt)) - - # check that a derived class's __str__() gets called - class A(self.theclass): - def __str__(self): - return 'A' - a = A(2007, 9, 10, 4, 5, 1, 123) - self.assertEqual(a.__format__(''), 'A') - - # check that a derived class's strftime gets called - class B(self.theclass): - def strftime(self, format_spec): - return 'B' - b = B(2007, 9, 10, 4, 5, 1, 123) - self.assertEqual(b.__format__(''), str(dt)) - - for fmt in ["m:%m d:%d y:%y", - "m:%m d:%d y:%y H:%H M:%M S:%S", - "%z %Z", - ]: - self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(b.__format__(fmt), 'B') - - 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.assertTrue(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.assertTrue(dt1 < dt2) - - def test_strftime_with_bad_tzname_replace(self): - # verify ok if tzinfo.tzname().replace() returns a non-string - class MyTzInfo(FixedOffset): - def tzname(self, dt): - class MyStr(str): - def replace(self, *args): - return None - return MyStr('name') - t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name')) - self.assertRaises(TypeError, t.strftime, '%Z') - - 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 or floats should be illegal - for i in 1, 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): - args = 6, 7, 23, 20, 59, 1, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_more_pickling(self): - a = self.theclass(2003, 2, 7, 16, 48, 37, 444116) - s = pickle.dumps(a) - b = pickle.loads(s) - self.assertEqual(b.year, 2003) - self.assertEqual(b.month, 2) - self.assertEqual(b.day, 7) - - def test_pickling_subclass_datetime(self): - args = 6, 7, 23, 20, 59, 1, 64**2 - orig = SubclassDatetime(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.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.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for i in range(len(args)): - newargs = args[:] - newargs[i] = args[i] + 1 - t2 = self.theclass(*newargs) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - - # 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_microsecond_rounding(self): - # Test whether fromtimestamp "rounds up" floats that are less - # than one microsecond smaller than an integer. - self.assertEqual(self.theclass.fromtimestamp(0.9999999), - self.theclass.fromtimestamp(1)) - - def test_insane_fromtimestamp(self): - # It's possible that some platform maps time_t to double, - # and that this test will fail there. This test should - # exempt such platforms (provided they return reasonable - # results!). - for insane in -1e200, 1e200: - self.assertRaises(ValueError, self.theclass.fromtimestamp, - insane) - - def test_insane_utcfromtimestamp(self): - # It's possible that some platform maps time_t to double, - # and that this test will fail there. This test should - # exempt such platforms (provided they return reasonable - # results!). - for insane in -1e200, 1e200: - self.assertRaises(ValueError, self.theclass.utcfromtimestamp, - insane) - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") - def test_negative_float_fromtimestamp(self): - # The result is tz-dependent; at least test that this doesn't - # fail (like it did before bug 1646728 was fixed). - self.theclass.fromtimestamp(-1.05) - - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") - def test_negative_float_utcfromtimestamp(self): - d = self.theclass.utcfromtimestamp(-1.05) - self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000)) - - 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.assertTrue(abs(from_timestamp - from_now) <= tolerance) - - def test_strptime(self): - import _strptime - - string = '2004-12-01 13:02:47.197' - format = '%Y-%m-%d %H:%M:%S.%f' - expected = _strptime._strptime_datetime(self.theclass, string, format) - got = self.theclass.strptime(string, format) - self.assertEqual(expected, got) - self.assertIs(type(expected), self.theclass) - self.assertIs(type(got), self.theclass) - - strptime = self.theclass.strptime - self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) - self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) - # Only local timezone and UTC are supported - for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'), - (-_time.timezone, _time.tzname[0])): - if tzseconds < 0: - sign = '-' - seconds = -tzseconds - else: - sign ='+' - seconds = tzseconds - hours, minutes = divmod(seconds//60, 60) - dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname) - dt = strptime(dtstr, "%z %Z") - self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds)) - self.assertEqual(dt.tzname(), tzname) - # Can produce inconsistent datetime - dtstr, fmt = "+1234 UTC", "%z %Z" - dt = strptime(dtstr, fmt) - self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE) - self.assertEqual(dt.tzname(), 'UTC') - # yet will roundtrip - self.assertEqual(dt.strftime(fmt), dtstr) - - # Produce naive datetime if no %z is provided - self.assertEqual(strptime("UTC", "%Z").tzinfo, None) - - with self.assertRaises(ValueError): strptime("-2400", "%z") - with self.assertRaises(ValueError): strptime("-000", "%z") - - 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, 47) - self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"), - "12 31 04 000047 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 - self.assertRaises(TypeError, combine, d, "time") # wrong type - self.assertRaises(TypeError, combine, "date", t) # wrong type - - def test_replace(self): - cls = self.theclass - args = [1, 2, 3, 4, 5, 6, 7] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("year", 2), - ("month", 3), - ("day", 4), - ("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8)): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Out of bounds. - base = cls(2000, 2, 29) - self.assertRaises(ValueError, base.replace, year=2001) - - def test_astimezone(self): - # Pretty boring! The TZ test is more interesting here. astimezone() - # simply can't be applied to a naive object. - dt = self.theclass.now() - f = FixedOffset(44, "") - self.assertRaises(TypeError, dt.astimezone) # not enough args - self.assertRaises(TypeError, dt.astimezone, f, f) # too many args - self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type - self.assertRaises(ValueError, dt.astimezone, f) # naive - self.assertRaises(ValueError, dt.astimezone, tz=f) # naive - - class Bogus(tzinfo): - def utcoffset(self, dt): return None - def dst(self, dt): return timedelta(0) - bog = Bogus() - self.assertRaises(ValueError, dt.astimezone, bog) # naive - self.assertRaises(ValueError, - dt.replace(tzinfo=bog).astimezone, f) - - class AlsoBogus(tzinfo): - def utcoffset(self, dt): return timedelta(0) - def dst(self, dt): return None - alsobog = AlsoBogus() - self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive - - def test_subclass_datetime(self): - - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop('extra') - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.year + self.month + self.second - - args = 2003, 4, 14, 12, 13, 41 - - dt1 = self.theclass(*args) - dt2 = C(*args, **{'extra': 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.toordinal(), dt2.toordinal()) - self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month + - dt1.second - 7) - -class TestSubclassDateTime(TestDateTime): - theclass = SubclassDatetime - # Override tests not designed for subclass - def test_roundtrip(self): - pass - -class SubclassTime(time): - sub_var = 1 - -class TestTime(HarmlessMixedComparison, 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.assertTrue(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.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for i in range(len(args)): - newargs = args[:] - newargs[i] = args[i] + 1 - t2 = self.theclass(*newargs) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - for badarg in OTHERSTUFF: - self.assertEqual(t1 == badarg, False) - self.assertEqual(t1 != badarg, True) - self.assertEqual(badarg == t1, False) - self.assertEqual(badarg != t1, True) - - 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) - - 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_1653736(self): - # verify it doesn't accept extra keyword arguments - t = self.theclass(second=1) - self.assertRaises(TypeError, t.isoformat, foo=3) - - def test_strftime(self): - t = self.theclass(1, 2, 3, 4) - self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004") - # A naive object replaces %z and %Z with empty strings. - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") - - def test_format(self): - t = self.theclass(1, 2, 3, 4) - self.assertEqual(t.__format__(''), str(t)) - - # check that a derived class's __str__() gets called - class A(self.theclass): - def __str__(self): - return 'A' - a = A(1, 2, 3, 4) - self.assertEqual(a.__format__(''), 'A') - - # check that a derived class's strftime gets called - class B(self.theclass): - def strftime(self, format_spec): - return 'B' - b = B(1, 2, 3, 4) - self.assertEqual(b.__format__(''), str(t)) - - for fmt in ['%H %M %S', - ]: - self.assertEqual(t.__format__(fmt), t.strftime(fmt)) - self.assertEqual(a.__format__(fmt), t.strftime(fmt)) - self.assertEqual(b.__format__(fmt), 'B') - - 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.assertIsInstance(self.theclass.min, self.theclass) - self.assertIsInstance(self.theclass.max, self.theclass) - self.assertIsInstance(self.theclass.resolution, timedelta) - self.assertTrue(self.theclass.max > self.theclass.min) - - def test_pickling(self): - args = 20, 59, 16, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_pickling_subclass_time(self): - args = 20, 59, 16, 64**2 - orig = SubclassTime(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_bool(self): - cls = self.theclass - self.assertTrue(cls(1)) - self.assertTrue(cls(0, 1)) - self.assertTrue(cls(0, 0, 1)) - self.assertTrue(cls(0, 0, 0, 1)) - self.assertTrue(not cls(0)) - self.assertTrue(not cls()) - - def test_replace(self): - cls = self.theclass - args = [1, 2, 3, 4] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8)): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Out of bounds. - base = cls(1) - self.assertRaises(ValueError, base.replace, hour=24) - self.assertRaises(ValueError, base.replace, minute=-1) - self.assertRaises(ValueError, base.replace, second=100) - self.assertRaises(ValueError, base.replace, microsecond=1000000) - - def test_subclass_time(self): - - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop('extra') - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.hour + self.second - - args = 4, 5, 6 - - dt1 = self.theclass(*args) - dt2 = C(*args, **{'extra': 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.isoformat(), dt2.isoformat()) - self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) - - def test_backdoor_resistance(self): - # see TestDate.test_backdoor_resistance(). - base = '2:59.0' - for hour_byte in ' ', '9', chr(24), '\xff': - self.assertRaises(TypeError, self.theclass, - hour_byte + base[1:]) - -# A mixin for classes with a tzinfo= argument. Subclasses must define -# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever) -# must be legit (which is true for time and datetime). -class TZInfoBase: - - def test_argument_passing(self): - cls = self.theclass - # A datetime passes itself on, a time passes None. - class introspective(tzinfo): - def tzname(self, dt): return dt and "real" or "none" - def utcoffset(self, dt): - return timedelta(minutes = dt and 42 or -42) - dst = utcoffset - - obj = cls(1, 2, 3, tzinfo=introspective()) - - expected = cls is time and "none" or "real" - self.assertEqual(obj.tzname(), expected) - - expected = timedelta(minutes=(cls is time and -42 or 42)) - self.assertEqual(obj.utcoffset(), expected) - self.assertEqual(obj.dst(), expected) - - def test_bad_tzinfo_classes(self): - cls = self.theclass - self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12) - - class NiceTry(object): - def __init__(self): pass - def utcoffset(self, dt): pass - self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry) - - class BetterTry(tzinfo): - def __init__(self): pass - def utcoffset(self, dt): pass - b = BetterTry() - t = cls(1, 1, 1, tzinfo=b) - self.assertTrue(t.tzinfo is b) - - def test_utc_offset_out_of_bounds(self): - class Edgy(tzinfo): - def __init__(self, offset): - self.offset = timedelta(minutes=offset) - def utcoffset(self, dt): - return self.offset - - cls = self.theclass - for offset, legit in ((-1440, False), - (-1439, True), - (1439, True), - (1440, False)): - if cls is time: - t = cls(1, 2, 3, tzinfo=Edgy(offset)) - elif cls is datetime: - t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset)) - else: - assert 0, "impossible" - if legit: - aofs = abs(offset) - h, m = divmod(aofs, 60) - tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m) - if isinstance(t, datetime): - t = t.timetz() - self.assertEqual(str(t), "01:02:03" + tag) - else: - self.assertRaises(ValueError, str, t) - - def test_tzinfo_classes(self): - cls = self.theclass - class C1(tzinfo): - def utcoffset(self, dt): return None - def dst(self, dt): return None - def tzname(self, dt): return None - for t in (cls(1, 1, 1), - cls(1, 1, 1, tzinfo=None), - cls(1, 1, 1, tzinfo=C1())): - self.assertTrue(t.utcoffset() is None) - self.assertTrue(t.dst() is None) - self.assertTrue(t.tzname() is None) - - class C3(tzinfo): - def utcoffset(self, dt): return timedelta(minutes=-1439) - def dst(self, dt): return timedelta(minutes=1439) - def tzname(self, dt): return "aname" - t = cls(1, 1, 1, tzinfo=C3()) - self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) - self.assertEqual(t.dst(), timedelta(minutes=1439)) - self.assertEqual(t.tzname(), "aname") - - # Wrong types. - class C4(tzinfo): - def utcoffset(self, dt): return "aname" - def dst(self, dt): return 7 - def tzname(self, dt): return 0 - t = cls(1, 1, 1, tzinfo=C4()) - self.assertRaises(TypeError, t.utcoffset) - self.assertRaises(TypeError, t.dst) - self.assertRaises(TypeError, t.tzname) - - # Offset out of range. - class C6(tzinfo): - def utcoffset(self, dt): return timedelta(hours=-24) - def dst(self, dt): return timedelta(hours=24) - t = cls(1, 1, 1, tzinfo=C6()) - self.assertRaises(ValueError, t.utcoffset) - self.assertRaises(ValueError, t.dst) - - # Not a whole number of minutes. - class C7(tzinfo): - def utcoffset(self, dt): return timedelta(seconds=61) - def dst(self, dt): return timedelta(microseconds=-81) - t = cls(1, 1, 1, tzinfo=C7()) - self.assertRaises(ValueError, t.utcoffset) - self.assertRaises(ValueError, t.dst) - - def test_aware_compare(self): - cls = self.theclass - - # Ensure that utcoffset() gets ignored if the comparands have - # the same tzinfo member. - class OperandDependentOffset(tzinfo): - def utcoffset(self, t): - if t.minute < 10: - # d0 and d1 equal after adjustment - return timedelta(minutes=t.minute) - else: - # d2 off in the weeds - return timedelta(minutes=59) - - base = cls(8, 9, 10, tzinfo=OperandDependentOffset()) - d0 = base.replace(minute=3) - d1 = base.replace(minute=9) - d2 = base.replace(minute=11) - for x in d0, d1, d2: - for y in d0, d1, d2: - for op in lt, le, gt, ge, eq, ne: - got = op(x, y) - expected = op(x.minute, y.minute) - self.assertEqual(got, expected) - - # However, if they're different members, uctoffset is not ignored. - # Note that a time can't actually have an operand-depedent offset, - # though (and time.utcoffset() passes None to tzinfo.utcoffset()), - # so skip this test for time. - if cls is not time: - d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) - d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) - d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) - for x in d0, d1, d2: - for y in d0, d1, d2: - got = (x > y) - (x < y) - if (x is d0 or x is d1) and (y is d0 or y is d1): - expected = 0 - elif x is y is d2: - expected = 0 - elif x is d2: - expected = -1 - else: - assert y is d2 - expected = 1 - self.assertEqual(got, expected) - - -# Testing time objects with a non-None tzinfo. -class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): - theclass = time - - 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.assertTrue(t.tzinfo is None) - - def test_zones(self): - est = FixedOffset(-300, "EST", 1) - utc = FixedOffset(0, "UTC", -2) - met = FixedOffset(60, "MET", 3) - t1 = time( 7, 47, tzinfo=est) - t2 = time(12, 47, tzinfo=utc) - t3 = time(13, 47, tzinfo=met) - t4 = time(microsecond=40) - t5 = time(microsecond=40, tzinfo=utc) - - self.assertEqual(t1.tzinfo, est) - self.assertEqual(t2.tzinfo, utc) - self.assertEqual(t3.tzinfo, met) - self.assertTrue(t4.tzinfo is None) - self.assertEqual(t5.tzinfo, utc) - - self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) - self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) - self.assertTrue(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.assertTrue(t4.tzname() is None) - self.assertRaises(TypeError, t1.tzname, "no args") - - self.assertEqual(t1.dst(), timedelta(minutes=1)) - self.assertEqual(t2.dst(), timedelta(minutes=-2)) - self.assertEqual(t3.dst(), timedelta(minutes=3)) - self.assertTrue(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.time' - self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)") - self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)") - self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)") - 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 = time(23, 59, tzinfo=yuck) - self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"), - "23:59 %Z='%z %Z %%z%%Z' %z='-2359'") - - # Check that an invalid tzname result raises an exception. - class Badtzname(tzinfo): - def tzname(self, dt): return 42 - t = time(2, 3, 4, tzinfo=Badtzname()) - self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04") - self.assertRaises(TypeError, t.strftime, "%Z") - - 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_pickling(self): - # Try one without a tzinfo. - args = 20, 59, 16, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - # Try one with a tzinfo. - tinfo = PicklableFixedOffset(-300, 'cookie') - orig = self.theclass(5, 6, 7, tzinfo=tinfo) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) - self.assertEqual(derived.utcoffset(), timedelta(minutes=-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.assertTrue(t) - - t = cls(5, tzinfo=FixedOffset(-300, "")) - self.assertTrue(t) - - t = cls(5, tzinfo=FixedOffset(300, "")) - self.assertTrue(not t) - - t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, "")) - self.assertTrue(not t) - - # Mostly ensuring this doesn't overflow internally. - t = cls(0, tzinfo=FixedOffset(23*60 + 59, "")) - self.assertTrue(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)) - - def test_replace(self): - cls = self.theclass - z100 = FixedOffset(100, "+100") - zm200 = FixedOffset(timedelta(minutes=-200), "-200") - args = [1, 2, 3, 4, z100] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8), - ("tzinfo", zm200)): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Ensure we can get rid of a tzinfo. - self.assertEqual(base.tzname(), "+100") - base2 = base.replace(tzinfo=None) - self.assertTrue(base2.tzinfo is None) - self.assertTrue(base2.tzname() is None) - - # Ensure we can add one. - base3 = base2.replace(tzinfo=z100) - self.assertEqual(base, base3) - self.assertTrue(base.tzinfo is base3.tzinfo) - - # Out of bounds. - base = cls(1) - self.assertRaises(ValueError, base.replace, hour=24) - self.assertRaises(ValueError, base.replace, minute=-1) - self.assertRaises(ValueError, base.replace, second=100) - self.assertRaises(ValueError, base.replace, microsecond=1000000) - - def test_mixed_compare(self): - t1 = time(1, 2, 3) - t2 = time(1, 2, 3) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=None) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(None, "")) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertRaises(TypeError, lambda: t1 == t2) - - # In time w/ identical tzinfo objects, utcoffset is ignored. - class Varies(tzinfo): - def __init__(self): - self.offset = timedelta(minutes=22) - def utcoffset(self, t): - self.offset += timedelta(minutes=1) - return self.offset - - v = Varies() - t1 = t2.replace(tzinfo=v) - t2 = t2.replace(tzinfo=v) - self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) - self.assertEqual(t1, t2) - - # But if they're not identical, it isn't ignored. - t2 = t2.replace(tzinfo=Varies()) - self.assertTrue(t1 < t2) # t1's offset counter still going up - - def test_subclass_timetz(self): - - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop('extra') - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.hour + self.second - - args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1) - - dt1 = self.theclass(*args) - dt2 = C(*args, **{'extra': 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) - self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) - - -# Testing datetime objects with a non-None tzinfo. - -class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase): - theclass = datetime - - 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.assertTrue(t1 < t2) - self.assertTrue(t1 != t2) - self.assertTrue(t2 > t1) - - self.assertEqual(t1, t1) - self.assertEqual(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.assertTrue(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.assertTrue(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.assertTrue(t1 > t2) - - # Likewise, but make microseconds resolve it. - t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), - microsecond=1) - self.assertTrue(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 timedelta(minutes=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 == t2) - - def test_pickling(self): - # Try one without a tzinfo. - args = 6, 7, 23, 20, 59, 1, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - # Try one with a tzinfo. - tinfo = PicklableFixedOffset(-300, 'cookie') - orig = self.theclass(*args, **{'tzinfo': tinfo}) - derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0)) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) - self.assertEqual(derived.utcoffset(), timedelta(minutes=-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 = datetime(2002, 3, 19, 7, 47, tzinfo=est) - t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc) - t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met) - self.assertEqual(t1.tzinfo, est) - self.assertEqual(t2.tzinfo, utc) - self.assertEqual(t3.tzinfo, met) - self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) - self.assertEqual(t3.utcoffset(), timedelta(minutes=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.datetime(2002, 3, 19, ' - self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)") - self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)") - self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)") - - def test_combine(self): - met = FixedOffset(60, "MET") - d = date(2002, 3, 4) - tz = time(18, 45, 3, 1234, tzinfo=met) - dt = datetime.combine(d, tz) - self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234, - tzinfo=met)) - - def test_extract(self): - 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(), time(18, 45, 3, 1234, tzinfo=met)) - - def test_tz_aware_arithmetic(self): - import random - - now = self.theclass.now() - tz55 = FixedOffset(-330, "west 5:30") - timeaware = now.time().replace(tzinfo=tz55) - nowaware = self.theclass.combine(now.date(), timeaware) - self.assertTrue(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 datetime's doesn't make sense, aware or not. - self.assertRaises(TypeError, lambda: now + nowaware) - self.assertRaises(TypeError, lambda: nowaware + now) - self.assertRaises(TypeError, lambda: nowaware + nowaware) - - # 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.assertTrue(nowaware.tzinfo is tz55) - nowawareplus2 = delta + nowaware - self.assertTrue(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.assertTrue(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. - nowawareplus = nowawareplus.replace(tzinfo=tzr) - self.assertTrue(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 = 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)) - # Different tzinfo, but the same offset - tza = timezone(HOUR, 'A') - tzb = timezone(HOUR, 'B') - delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb) - self.assertEqual(delta, self.theclass.min - self.theclass.max) - - 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(tz=off42) - self.assertTrue(another.tzinfo is again.tzinfo) - self.assertEqual(another.utcoffset(), timedelta(minutes=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) - - # We don't know which time zone we're in, and don't have a tzinfo - # class to represent it, so seeing whether a tz argument actually - # does a conversion is tricky. - utc = FixedOffset(0, "utc", 0) - for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0), - timezone(timedelta(hours=15, minutes=58), "weirdtz"),]: - for dummy in range(3): - now = datetime.now(weirdtz) - self.assertTrue(now.tzinfo is weirdtz) - utcnow = datetime.utcnow().replace(tzinfo=utc) - now2 = utcnow.astimezone(weirdtz) - if abs(now - now2) < timedelta(seconds=30): - break - # Else the code is broken, or more than 30 seconds passed between - # calls; assuming the latter, just try again. - else: - # Three strikes and we're out. - self.fail("utcnow(), now(tz), or astimezone() may be broken") - - 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, tz=off42) - self.assertTrue(another.tzinfo is again.tzinfo) - self.assertEqual(another.utcoffset(), timedelta(minutes=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) - - # Try to make sure tz= actually does some conversion. - timestamp = 1000000000 - utcdatetime = datetime.utcfromtimestamp(timestamp) - # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take. - # But on some flavor of Mac, it's nowhere near that. So we can't have - # any idea here what time that actually is, we can only test that - # relative changes match. - utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero - tz = FixedOffset(utcoffset, "tz", 0) - expected = utcdatetime + utcoffset - got = datetime.fromtimestamp(timestamp, tz) - self.assertEqual(expected, got.replace(tzinfo=None)) - - 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. datetime adds a twist to the - # DST flag. - class DST(tzinfo): - def __init__(self, dstvalue): - if isinstance(dstvalue, int): - dstvalue = timedelta(minutes=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=0): - if isinstance(dstvalue, int): - dstvalue = timedelta(minutes=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 = timedelta(minutes=uofs) - def utcoffset(self, dt): - return self.uofs - - 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) - # Ensure tm_isdst is 0 regardless of what dst() says: DST - # is never in effect for a UTC time. - self.assertEqual(0, t.tm_isdst) - - # For naive datetime, utctimetuple == timetuple except for isdst - d = cls(1, 2, 3, 10, 20, 30, 40) - t = d.utctimetuple() - self.assertEqual(t[:-1], d.timetuple()[:-1]) - self.assertEqual(0, t.tm_isdst) - # Same if utcoffset is None - class NOFS(DST): - def utcoffset(self, dt): - return None - d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS()) - t = d.utctimetuple() - self.assertEqual(t[:-1], d.timetuple()[:-1]) - self.assertEqual(0, t.tm_isdst) - # Check that bad tzinfo is detected - class BOFS(DST): - def utcoffset(self, dt): - return "EST" - d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS()) - self.assertRaises(TypeError, d.utctimetuple) - - # Check that utctimetuple() is the same as - # astimezone(utc).timetuple() - d = cls(2010, 11, 13, 14, 15, 16, 171819) - for tz in [timezone.min, timezone.utc, timezone.max]: - dtz = d.replace(tzinfo=tz) - self.assertEqual(dtz.utctimetuple()[:-1], - dtz.astimezone(timezone.utc).timetuple()[:-1]) - # At the edges, UTC adjustment can produce years out-of-range - # for a datetime object. Ensure that an OverflowError is - # raised. - tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439)) - # That goes back 1 minute less than a full day. - self.assertRaises(OverflowError, tiny.utctimetuple) - - huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439)) - # That goes forward 1 minute less than a full day. - self.assertRaises(OverflowError, huge.utctimetuple) - # More overflow cases - tiny = cls.min.replace(tzinfo=timezone(MINUTE)) - self.assertRaises(OverflowError, tiny.utctimetuple) - huge = cls.max.replace(tzinfo=timezone(-MINUTE)) - self.assertRaises(OverflowError, huge.utctimetuple) - - 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(d.isoformat('\u1234'), datestr + '\u1234' + tailstr) - self.assertEqual(str(d), datestr + ' ' + tailstr) - - def test_replace(self): - cls = self.theclass - z100 = FixedOffset(100, "+100") - zm200 = FixedOffset(timedelta(minutes=-200), "-200") - args = [1, 2, 3, 4, 5, 6, 7, z100] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("year", 2), - ("month", 3), - ("day", 4), - ("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8), - ("tzinfo", zm200)): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Ensure we can get rid of a tzinfo. - self.assertEqual(base.tzname(), "+100") - base2 = base.replace(tzinfo=None) - self.assertTrue(base2.tzinfo is None) - self.assertTrue(base2.tzname() is None) - - # Ensure we can add one. - base3 = base2.replace(tzinfo=z100) - self.assertEqual(base, base3) - self.assertTrue(base.tzinfo is base3.tzinfo) - - # Out of bounds. - base = cls(2000, 2, 29) - self.assertRaises(ValueError, base.replace, year=2001) - - def test_more_astimezone(self): - # The inherited test_astimezone covered some trivial and error cases. - fnone = FixedOffset(None, "None") - f44m = FixedOffset(44, "44") - fm5h = FixedOffset(-timedelta(hours=5), "m300") - - dt = self.theclass.now(tz=f44m) - self.assertTrue(dt.tzinfo is f44m) - # Replacing with degenerate tzinfo raises an exception. - self.assertRaises(ValueError, dt.astimezone, fnone) - # Ditto with None tz. - self.assertRaises(TypeError, dt.astimezone, None) - # Replacing with same tzinfo makes no change. - x = dt.astimezone(dt.tzinfo) - self.assertTrue(x.tzinfo is f44m) - self.assertEqual(x.date(), dt.date()) - self.assertEqual(x.time(), dt.time()) - - # Replacing with different tzinfo does adjust. - got = dt.astimezone(fm5h) - self.assertTrue(got.tzinfo is fm5h) - self.assertEqual(got.utcoffset(), timedelta(hours=-5)) - expected = dt - dt.utcoffset() # in effect, convert to UTC - expected += fm5h.utcoffset(dt) # and from there to local time - expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo - self.assertEqual(got.date(), expected.date()) - self.assertEqual(got.time(), expected.time()) - self.assertEqual(got.timetz(), expected.timetz()) - self.assertTrue(got.tzinfo is expected.tzinfo) - self.assertEqual(got, expected) - - def test_aware_subtract(self): - cls = self.theclass - - # Ensure that utcoffset() is ignored when the operands have the - # same tzinfo member. - class OperandDependentOffset(tzinfo): - def utcoffset(self, t): - if t.minute < 10: - # d0 and d1 equal after adjustment - return timedelta(minutes=t.minute) - else: - # d2 off in the weeds - return timedelta(minutes=59) - - base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset()) - d0 = base.replace(minute=3) - d1 = base.replace(minute=9) - d2 = base.replace(minute=11) - for x in d0, d1, d2: - for y in d0, d1, d2: - got = x - y - expected = timedelta(minutes=x.minute - y.minute) - self.assertEqual(got, expected) - - # OTOH, if the tzinfo members are distinct, utcoffsets aren't - # ignored. - base = cls(8, 9, 10, 11, 12, 13, 14) - d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) - d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) - d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) - for x in d0, d1, d2: - for y in d0, d1, d2: - got = x - y - if (x is d0 or x is d1) and (y is d0 or y is d1): - expected = timedelta(0) - elif x is y is d2: - expected = timedelta(0) - elif x is d2: - expected = timedelta(minutes=(11-59)-0) - else: - assert y is d2 - expected = timedelta(minutes=0-(11-59)) - self.assertEqual(got, expected) - - def test_mixed_compare(self): - t1 = datetime(1, 2, 3, 4, 5, 6, 7) - t2 = datetime(1, 2, 3, 4, 5, 6, 7) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=None) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(None, "")) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertRaises(TypeError, lambda: t1 == t2) - - # In datetime w/ identical tzinfo objects, utcoffset is ignored. - class Varies(tzinfo): - def __init__(self): - self.offset = timedelta(minutes=22) - def utcoffset(self, t): - self.offset += timedelta(minutes=1) - return self.offset - - v = Varies() - t1 = t2.replace(tzinfo=v) - t2 = t2.replace(tzinfo=v) - self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) - self.assertEqual(t1, t2) - - # But if they're not identical, it isn't ignored. - t2 = t2.replace(tzinfo=Varies()) - self.assertTrue(t1 < t2) # t1's offset counter still going up - - def test_subclass_datetimetz(self): - - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop('extra') - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.hour + self.year - - args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1) - - dt1 = self.theclass(*args) - dt2 = C(*args, **{'extra': 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) - self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7) - -# Pain to set up DST-aware tzinfo classes. - -def first_sunday_on_or_after(dt): - days_to_go = 6 - dt.weekday() - if days_to_go: - dt += timedelta(days_to_go) - return dt - -ZERO = timedelta(0) -MINUTE = timedelta(minutes=1) -HOUR = timedelta(hours=1) -DAY = timedelta(days=1) -# In the US, DST starts at 2am (standard time) on the first Sunday in April. -DSTSTART = datetime(1, 4, 1, 2) -# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct, -# which is the first Sunday on or after Oct 25. Because we view 1:MM as -# being standard time on that day, there is no spelling in local time of -# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time). -DSTEND = datetime(1, 10, 25, 1) - -class USTimeZone(tzinfo): - - def __init__(self, hours, reprname, stdname, dstname): - self.stdoffset = timedelta(hours=hours) - self.reprname = reprname - self.stdname = stdname - self.dstname = dstname - - def __repr__(self): - return self.reprname - - def tzname(self, dt): - if self.dst(dt): - return self.dstname - else: - return self.stdname - - def utcoffset(self, dt): - return self.stdoffset + self.dst(dt) - - def dst(self, dt): - if dt is None or dt.tzinfo is None: - # An exception instead may be sensible here, in one or more of - # the cases. - return ZERO - assert dt.tzinfo is self - - # Find first Sunday in April. - start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) - assert start.weekday() == 6 and start.month == 4 and start.day <= 7 - - # Find last Sunday in October. - end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) - assert end.weekday() == 6 and end.month == 10 and end.day >= 25 - - # Can't compare naive to aware objects, so strip the timezone from - # dt first. - if start <= dt.replace(tzinfo=None) < end: - return HOUR - else: - return ZERO - -Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") -Central = USTimeZone(-6, "Central", "CST", "CDT") -Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") -Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") -utc_real = FixedOffset(0, "UTC", 0) -# For better test coverage, we want another flavor of UTC that's west of -# the Eastern and Pacific timezones. -utc_fake = FixedOffset(-12*60, "UTCfake", 0) - -class TestTimezoneConversions(unittest.TestCase): - # The DST switch times for 2002, in std time. - dston = datetime(2002, 4, 7, 2) - dstoff = datetime(2002, 10, 27, 1) - - theclass = datetime - - # Check a time that's inside DST. - def checkinside(self, dt, tz, utc, dston, dstoff): - self.assertEqual(dt.dst(), HOUR) - - # Conversion to our own timezone is always an identity. - self.assertEqual(dt.astimezone(tz), dt) - - asutc = dt.astimezone(utc) - there_and_back = asutc.astimezone(tz) - - # Conversion to UTC and back isn't always an identity here, - # because there are redundant spellings (in local time) of - # UTC time when DST begins: the clock jumps from 1:59:59 - # to 3:00:00, and a local time of 2:MM:SS doesn't really - # make sense then. The classes above treat 2:MM:SS as - # daylight time then (it's "after 2am"), really an alias - # for 1:MM:SS standard time. The latter form is what - # conversion back from UTC produces. - if dt.date() == dston.date() and dt.hour == 2: - # We're in the redundant hour, and coming back from - # UTC gives the 1:MM:SS standard-time spelling. - self.assertEqual(there_and_back + HOUR, dt) - # Although during was considered to be in daylight - # time, there_and_back is not. - self.assertEqual(there_and_back.dst(), ZERO) - # They're the same times in UTC. - self.assertEqual(there_and_back.astimezone(utc), - dt.astimezone(utc)) - else: - # We're not in the redundant hour. - self.assertEqual(dt, there_and_back) - - # Because we have a redundant spelling when DST begins, there is - # (unforunately) an hour when DST ends that can't be spelled at all in - # local time. When DST ends, the clock jumps from 1:59 back to 1:00 - # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be - # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be - # daylight time. The hour 1:MM daylight == 0:MM standard can't be - # expressed in local time. Nevertheless, we want conversion back - # from UTC to mimic the local clock's "repeat an hour" behavior. - nexthour_utc = asutc + HOUR - nexthour_tz = nexthour_utc.astimezone(tz) - if dt.date() == dstoff.date() and dt.hour == 0: - # We're in the hour before the last DST hour. The last DST hour - # is ineffable. We want the conversion back to repeat 1:MM. - self.assertEqual(nexthour_tz, dt.replace(hour=1)) - nexthour_utc += HOUR - nexthour_tz = nexthour_utc.astimezone(tz) - self.assertEqual(nexthour_tz, dt.replace(hour=1)) - else: - self.assertEqual(nexthour_tz - dt, HOUR) - - # Check a time that's outside DST. - def checkoutside(self, dt, tz, utc): - self.assertEqual(dt.dst(), ZERO) - - # Conversion to our own timezone is always an identity. - self.assertEqual(dt.astimezone(tz), dt) - - # Converting to UTC and back is an identity too. - asutc = dt.astimezone(utc) - there_and_back = asutc.astimezone(tz) - self.assertEqual(dt, there_and_back) - - def convert_between_tz_and_utc(self, tz, utc): - dston = self.dston.replace(tzinfo=tz) - # Because 1:MM on the day DST ends is taken as being standard time, - # there is no spelling in tz for the last hour of daylight time. - # For purposes of the test, the last hour of DST is 0:MM, which is - # taken as being daylight time (and 1:MM is taken as being standard - # time). - dstoff = self.dstoff.replace(tzinfo=tz) - for delta in (timedelta(weeks=13), - DAY, - HOUR, - timedelta(minutes=1), - timedelta(microseconds=1)): - - self.checkinside(dston, tz, utc, dston, dstoff) - for during in dston + delta, dstoff - delta: - self.checkinside(during, tz, utc, dston, dstoff) - - self.checkoutside(dstoff, tz, utc) - for outside in dston - delta, dstoff + delta: - self.checkoutside(outside, tz, utc) - - def test_easy(self): - # Despite the name of this test, the endcases are excruciating. - self.convert_between_tz_and_utc(Eastern, utc_real) - self.convert_between_tz_and_utc(Pacific, utc_real) - self.convert_between_tz_and_utc(Eastern, utc_fake) - self.convert_between_tz_and_utc(Pacific, utc_fake) - # The next is really dancing near the edge. It works because - # Pacific and Eastern are far enough apart that their "problem - # hours" don't overlap. - self.convert_between_tz_and_utc(Eastern, Pacific) - self.convert_between_tz_and_utc(Pacific, Eastern) - # OTOH, these fail! Don't enable them. The difficulty is that - # the edge case tests assume that every hour is representable in - # the "utc" class. This is always true for a fixed-offset tzinfo - # class (lke utc_real and utc_fake), but not for Eastern or Central. - # For these adjacent DST-aware time zones, the range of time offsets - # tested ends up creating hours in the one that aren't representable - # in the other. For the same reason, we would see failures in the - # Eastern vs Pacific tests too if we added 3*HOUR to the list of - # offset deltas in convert_between_tz_and_utc(). - # - # self.convert_between_tz_and_utc(Eastern, Central) # can't work - # self.convert_between_tz_and_utc(Central, Eastern) # can't work - - def test_tricky(self): - # 22:00 on day before daylight starts. - fourback = self.dston - timedelta(hours=4) - ninewest = FixedOffset(-9*60, "-0900", 0) - fourback = fourback.replace(tzinfo=ninewest) - # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after - # 2", we should get the 3 spelling. - # If we plug 22:00 the day before into Eastern, it "looks like std - # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4 - # to 22:00 lands on 2:00, which makes no sense in local time (the - # local clock jumps from 1 to 3). The point here is to make sure we - # get the 3 spelling. - expected = self.dston.replace(hour=3) - got = fourback.astimezone(Eastern).replace(tzinfo=None) - self.assertEqual(expected, got) - - # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that - # case we want the 1:00 spelling. - sixutc = self.dston.replace(hour=6, tzinfo=utc_real) - # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4, - # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST - # spelling. - expected = self.dston.replace(hour=1) - got = sixutc.astimezone(Eastern).replace(tzinfo=None) - self.assertEqual(expected, got) - - # Now on the day DST ends, we want "repeat an hour" behavior. - # UTC 4:MM 5:MM 6:MM 7:MM checking these - # EST 23:MM 0:MM 1:MM 2:MM - # EDT 0:MM 1:MM 2:MM 3:MM - # wall 0:MM 1:MM 1:MM 2:MM against these - for utc in utc_real, utc_fake: - for tz in Eastern, Pacific: - first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM - # Convert that to UTC. - first_std_hour -= tz.utcoffset(None) - # Adjust for possibly fake UTC. - asutc = first_std_hour + utc.utcoffset(None) - # First UTC hour to convert; this is 4:00 when utc=utc_real & - # tz=Eastern. - asutcbase = asutc.replace(tzinfo=utc) - for tzhour in (0, 1, 1, 2): - expectedbase = self.dstoff.replace(hour=tzhour) - for minute in 0, 30, 59: - expected = expectedbase.replace(minute=minute) - asutc = asutcbase.replace(minute=minute) - astz = asutc.astimezone(tz) - self.assertEqual(astz.replace(tzinfo=None), expected) - asutcbase += HOUR - - - def test_bogus_dst(self): - class ok(tzinfo): - def utcoffset(self, dt): return HOUR - def dst(self, dt): return HOUR - - now = self.theclass.now().replace(tzinfo=utc_real) - # Doesn't blow up. - now.astimezone(ok()) - - # Does blow up. - class notok(ok): - def dst(self, dt): return None - self.assertRaises(ValueError, now.astimezone, notok()) - - # Sometimes blow up. In the following, tzinfo.dst() - # implementation may return None or not None depending on - # whether DST is assumed to be in effect. In this situation, - # a ValueError should be raised by astimezone(). - class tricky_notok(ok): - def dst(self, dt): - if dt.year == 2000: - return None - else: - return 10*HOUR - dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real) - self.assertRaises(ValueError, dt.astimezone, tricky_notok()) - - def test_fromutc(self): - self.assertRaises(TypeError, Eastern.fromutc) # not enough args - now = datetime.utcnow().replace(tzinfo=utc_real) - self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo - now = now.replace(tzinfo=Eastern) # insert correct tzinfo - enow = Eastern.fromutc(now) # doesn't blow up - self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member - self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args - self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type - - # Always converts UTC to standard time. - class FauxUSTimeZone(USTimeZone): - def fromutc(self, dt): - return dt + self.stdoffset - FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT") - - # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM - # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM - # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM - - # Check around DST start. - start = self.dston.replace(hour=4, tzinfo=Eastern) - fstart = start.replace(tzinfo=FEastern) - for wall in 23, 0, 1, 3, 4, 5: - expected = start.replace(hour=wall) - if wall == 23: - expected -= timedelta(days=1) - got = Eastern.fromutc(start) - self.assertEqual(expected, got) - - expected = fstart + FEastern.stdoffset - got = FEastern.fromutc(fstart) - self.assertEqual(expected, got) - - # Ensure astimezone() calls fromutc() too. - got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) - self.assertEqual(expected, got) - - start += HOUR - fstart += HOUR - - # Check around DST end. - start = self.dstoff.replace(hour=4, tzinfo=Eastern) - fstart = start.replace(tzinfo=FEastern) - for wall in 0, 1, 1, 2, 3, 4: - expected = start.replace(hour=wall) - got = Eastern.fromutc(start) - self.assertEqual(expected, got) - - expected = fstart + FEastern.stdoffset - got = FEastern.fromutc(fstart) - self.assertEqual(expected, got) - - # Ensure astimezone() calls fromutc() too. - got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) - self.assertEqual(expected, got) - - start += HOUR - fstart += HOUR - - -############################################################################# -# oddballs - -class Oddballs(unittest.TestCase): - - def test_bug_1028306(self): - # Trying to compare a date to a datetime should act like a mixed- - # type comparison, despite that datetime is a subclass of date. - as_date = date.today() - as_datetime = datetime.combine(as_date, time()) - self.assertTrue(as_date != as_datetime) - self.assertTrue(as_datetime != as_date) - self.assertTrue(not as_date == as_datetime) - self.assertTrue(not as_datetime == as_date) - self.assertRaises(TypeError, lambda: as_date < as_datetime) - self.assertRaises(TypeError, lambda: as_datetime < as_date) - self.assertRaises(TypeError, lambda: as_date <= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime <= as_date) - self.assertRaises(TypeError, lambda: as_date > as_datetime) - self.assertRaises(TypeError, lambda: as_datetime > as_date) - self.assertRaises(TypeError, lambda: as_date >= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime >= as_date) - - # Neverthelss, comparison should work with the base-class (date) - # projection if use of a date method is forced. - self.assertEqual(as_date.__eq__(as_datetime), True) - different_day = (as_date.day + 1) % 20 + 1 - as_different = as_datetime.replace(day= different_day) - self.assertEqual(as_date.__eq__(as_different), False) - - # And date should compare with other subclasses of date. If a - # subclass wants to stop this, it's up to the subclass to do so. - date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) - self.assertEqual(as_date, date_sc) - self.assertEqual(date_sc, as_date) - - # Ditto for datetimes. - datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month, - as_date.day, 0, 0, 0) - self.assertEqual(as_datetime, datetime_sc) - self.assertEqual(datetime_sc, as_datetime) +import sys +from test.support import import_fresh_module, run_unittest +TESTS = 'test.datetimetester' +# XXX: import_fresh_module() is supposed to leave sys.module cache untouched, +# XXX: but it does not, so we have to save and restore it ourselves. +save_sys_modules = sys.modules.copy() +try: + pure_tests = import_fresh_module(TESTS, fresh=['datetime', '_strptime'], + blocked=['_datetime']) + fast_tests = import_fresh_module(TESTS, fresh=['datetime', + '_datetime', '_strptime']) +finally: + sys.modules.clear() + sys.modules.update(save_sys_modules) +test_modules = [pure_tests, fast_tests] +test_suffixes = ["_Pure", "_Fast"] + +for module, suffix in zip(test_modules, test_suffixes): + for name, cls in module.__dict__.items(): + if isinstance(cls, type) and issubclass(cls, unittest.TestCase): + name += suffix + cls.__name__ = name + globals()[name] = cls + def setUp(self, module=module, setup=cls.setUp): + self._save_sys_modules = sys.modules.copy() + sys.modules[TESTS] = module + sys.modules['datetime'] = module.datetime_module + sys.modules['_strptime'] = module._strptime + setup(self) + def tearDown(self, teardown=cls.tearDown): + teardown(self) + sys.modules.clear() + sys.modules.update(self._save_sys_modules) + cls.setUp = setUp + cls.tearDown = tearDown def test_main(): - support.run_unittest(__name__) + run_unittest(__name__) if __name__ == "__main__": test_main() diff --git a/Misc/NEWS b/Misc/NEWS index a6b05e6..c06ac1f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -473,6 +473,14 @@ C-API Library ------- +- Issue #7989: Added pure python implementation of the `datetime` + module. The C module is renamed to `_datetime` and if available, + overrides all classes defined in datetime with fast C impementation. + Python implementation is based on the original python prototype for + the datetime module by Tim Peters with minor modifications by the + PyPy project. The test suite now tests `datetime` module with and + without `_datetime` acceleration using the same test cases. + - Issue #7895: platform.mac_ver() no longer crashes after calling os.fork() - Issue #9323: Fixed a bug in trace.py that resulted in loosing the diff --git a/Modules/Setup.dist b/Modules/Setup.dist index 7676c74..bd7128b 100644 --- a/Modules/Setup.dist +++ b/Modules/Setup.dist @@ -170,7 +170,7 @@ _symtable symtablemodule.c #atexit atexitmodule.c # Register functions to be run at interpreter-shutdown #_elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI _elementtree.c # elementtree accelerator #_pickle _pickle.c # pickle accelerator -#datetime datetimemodule.c # date/time type +#_datetime _datetimemodule.c # datetime accelerator #_bisect _bisectmodule.c # Bisection algorithms #_heapq _heapqmodule.c # Heap queue algorithm diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c new file mode 100644 index 0000000..b2505d1 --- /dev/null +++ b/Modules/_datetimemodule.c @@ -0,0 +1,5488 @@ +/* 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 "timefuncs.h" + +/* Differentiate between building the core module and building extension + * modules. + */ +#ifndef Py_BUILD_CORE +#define Py_BUILD_CORE +#endif +#include "datetime.h" +#undef Py_BUILD_CORE + +/* 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 +#define MAXORDINAL 3652059 /* date(9999,12,31).toordinal() */ + +/* 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)) + +/* p is a pointer to a time or a datetime object; HASTZINFO(p) returns + * p->hastzinfo. + */ +#define HASTZINFO(p) (((_PyDateTime_BaseTZInfo *)(p))->hastzinfo) +#define GET_TIME_TZINFO(p) (HASTZINFO(p) ? \ + ((PyDateTime_Time *)(p))->tzinfo : Py_None) +#define GET_DT_TZINFO(p) (HASTZINFO(p) ? \ + ((PyDateTime_DateTime *)(p))->tzinfo : Py_None) +/* M is a char or int claiming to be a valid month. The macro is equivalent + * to the two-sided Python test + * 1 <= M <= 12 + */ +#define MONTH_IS_SANE(M) ((unsigned int)(M) - 1 < 12) + +/* Forward declarations. */ +static PyTypeObject PyDateTime_DateType; +static PyTypeObject PyDateTime_DateTimeType; +static PyTypeObject PyDateTime_DeltaType; +static PyTypeObject PyDateTime_TimeType; +static PyTypeObject PyDateTime_TZInfoType; +static PyTypeObject PyDateTime_TimeZoneType; + +/* --------------------------------------------------------------------------- + * 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; +} + +/* Round a double to the nearest long. |x| must be small enough to fit + * in a C long; this is not checked. + */ +static long +round_to_long(double x) +{ + if (x >= 0.0) + x = floor(x + 0.5); + else + x = ceil(x - 0.5); + return (long)x; +} + +/* Nearest integer to m / n for integers m and n. Half-integer results + * are rounded to even. + */ +static PyObject * +divide_nearest(PyObject *m, PyObject *n) +{ + PyObject *result; + PyObject *temp; + + temp = _PyLong_DivmodNear(m, n); + if (temp == NULL) + return NULL; + result = PyTuple_GET_ITEM(temp, 0); + Py_INCREF(result); + Py_DECREF(temp); + + return result; +} + +/* --------------------------------------------------------------------------- + * 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, MAX_DELTA_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 int +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; + if (ordinal < 1 || ordinal > MAXORDINAL) { + goto error; + } else { + ord_to_ymd(ordinal, y, m, d); + return 0; + } + } + } + assert(*m > 0); + assert(*d > 0); + if (MINYEAR <= *y && *y <= MAXYEAR) + return 0; + error: + PyErr_SetString(PyExc_OverflowError, + "date value out of range"); + return -1; + +} + +/* 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) +{ + return normalize_y_m_d(year, month, day); +} + +/* 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); +} + +/* --------------------------------------------------------------------------- + * Basic object allocation: tp_alloc implementations. These allocate + * Python objects of the right size and type, and do the Python object- + * initialization bit. If there's not enough memory, they return NULL after + * setting MemoryError. All data members remain uninitialized trash. + * + * We abuse the tp_alloc "nitems" argument to communicate whether a tzinfo + * member is needed. This is ugly, imprecise, and possibly insecure. + * tp_basicsize for the time and datetime types is set to the size of the + * struct that has room for the tzinfo member, so subclasses in Python will + * allocate enough space for a tzinfo member whether or not one is actually + * needed. That's the "ugly and imprecise" parts. The "possibly insecure" + * part is that PyType_GenericAlloc() (which subclasses in Python end up + * using) just happens today to effectively ignore the nitems argument + * when tp_itemsize is 0, which it is for these type objects. If that + * changes, perhaps the callers of tp_alloc slots in this file should + * be changed to force a 0 nitems argument unless the type being allocated + * is a base type implemented in this file (so that tp_alloc is time_alloc + * or datetime_alloc below, which know about the nitems abuse). + */ + +static PyObject * +time_alloc(PyTypeObject *type, Py_ssize_t aware) +{ + PyObject *self; + + self = (PyObject *) + PyObject_MALLOC(aware ? + sizeof(PyDateTime_Time) : + sizeof(_PyDateTime_BaseTime)); + if (self == NULL) + return (PyObject *)PyErr_NoMemory(); + PyObject_INIT(self, type); + return self; +} + +static PyObject * +datetime_alloc(PyTypeObject *type, Py_ssize_t aware) +{ + PyObject *self; + + self = (PyObject *) + PyObject_MALLOC(aware ? + sizeof(PyDateTime_DateTime) : + sizeof(_PyDateTime_BaseDateTime)); + if (self == NULL) + return (PyObject *)PyErr_NoMemory(); + PyObject_INIT(self, type); + return self; +} + +/* --------------------------------------------------------------------------- + * Helpers for setting object fields. These work on pointers to the + * appropriate base class. + */ + +/* For date and datetime. */ +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); +} + +/* --------------------------------------------------------------------------- + * Create various objects, mostly without range checking. + */ + +/* Create a date instance with no range checking. */ +static PyObject * +new_date_ex(int year, int month, int day, PyTypeObject *type) +{ + PyDateTime_Date *self; + + self = (PyDateTime_Date *) (type->tp_alloc(type, 0)); + if (self != NULL) + set_date_fields(self, year, month, day); + return (PyObject *) self; +} + +#define new_date(year, month, day) \ + new_date_ex(year, month, day, &PyDateTime_DateType) + +/* Create a datetime instance with no range checking. */ +static PyObject * +new_datetime_ex(int year, int month, int day, int hour, int minute, + int second, int usecond, PyObject *tzinfo, PyTypeObject *type) +{ + PyDateTime_DateTime *self; + char aware = tzinfo != Py_None; + + self = (PyDateTime_DateTime *) (type->tp_alloc(type, aware)); + if (self != NULL) { + self->hastzinfo = aware; + set_date_fields((PyDateTime_Date *)self, year, month, day); + DATE_SET_HOUR(self, hour); + DATE_SET_MINUTE(self, minute); + DATE_SET_SECOND(self, second); + DATE_SET_MICROSECOND(self, usecond); + if (aware) { + Py_INCREF(tzinfo); + self->tzinfo = tzinfo; + } + } + return (PyObject *)self; +} + +#define new_datetime(y, m, d, hh, mm, ss, us, tzinfo) \ + new_datetime_ex(y, m, d, hh, mm, ss, us, tzinfo, \ + &PyDateTime_DateTimeType) + +/* Create a time instance with no range checking. */ +static PyObject * +new_time_ex(int hour, int minute, int second, int usecond, + PyObject *tzinfo, PyTypeObject *type) +{ + PyDateTime_Time *self; + char aware = tzinfo != Py_None; + + self = (PyDateTime_Time *) (type->tp_alloc(type, aware)); + if (self != NULL) { + self->hastzinfo = aware; + self->hashcode = -1; + TIME_SET_HOUR(self, hour); + TIME_SET_MINUTE(self, minute); + TIME_SET_SECOND(self, second); + TIME_SET_MICROSECOND(self, usecond); + if (aware) { + Py_INCREF(tzinfo); + self->tzinfo = tzinfo; + } + } + return (PyObject *)self; +} + +#define new_time(hh, mm, ss, us, tzinfo) \ + new_time_ex(hh, mm, ss, us, tzinfo, &PyDateTime_TimeType) + +/* 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_ex(int days, int seconds, int microseconds, int normalize, + PyTypeObject *type) +{ + 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 = (PyDateTime_Delta *) (type->tp_alloc(type, 0)); + if (self != NULL) { + self->hashcode = -1; + SET_TD_DAYS(self, days); + SET_TD_SECONDS(self, seconds); + SET_TD_MICROSECONDS(self, microseconds); + } + return (PyObject *) self; +} + +#define new_delta(d, s, us, normalize) \ + new_delta_ex(d, s, us, normalize, &PyDateTime_DeltaType) + + +typedef struct +{ + PyObject_HEAD + PyObject *offset; + PyObject *name; +} PyDateTime_TimeZone; + +PyObject *PyDateTime_TimeZone_UTC; + +/* Create new timezone instance checking offset range. This + function does not check the name argument. Caller must assure + that offset is a timedelta instance and name is either NULL + or a unicode object. */ +static PyObject * +new_timezone(PyObject *offset, PyObject *name) +{ + PyDateTime_TimeZone *self; + PyTypeObject *type = &PyDateTime_TimeZoneType; + + assert(offset != NULL); + assert(PyDelta_Check(offset)); + assert(name == NULL || PyUnicode_Check(name)); + + if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) { + PyErr_Format(PyExc_ValueError, "offset must be a timedelta" + " representing a whole number of minutes"); + return NULL; + } + if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) || + GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) { + PyErr_Format(PyExc_ValueError, "offset must be a timedelta" + " strictly between -timedelta(hours=24) and" + " timedelta(hours=24)."); + return NULL; + } + + self = (PyDateTime_TimeZone *)(type->tp_alloc(type, 0)); + if (self == NULL) { + return NULL; + } + Py_INCREF(offset); + self->offset = offset; + Py_XINCREF(name); + self->name = name; + return (PyObject *)self; +} + +/* --------------------------------------------------------------------------- + * tzinfo helpers. + */ + +/* 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'", + Py_TYPE(p)->tp_name); + return -1; +} + +/* 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 (PyDateTime_Check(self) && HASTZINFO(self)) + tzinfo = ((PyDateTime_DateTime *)self)->tzinfo; + else if (PyTime_Check(self) && HASTZINFO(self)) + tzinfo = ((PyDateTime_Time *)self)->tzinfo; + + return tzinfo; +} + +/* Call getattr(tzinfo, name)(tzinfoarg), and check the result. tzinfo must + * be an instance of the tzinfo class. If the method returns None, this + * returns None. If the method doesn't return None or timedelta, TypeError is + * raised and this returns NULL. If it returns a timedelta and the value is + * out of range or isn't a whole number of minutes, ValueError is raised and + * this returns NULL. Else result is returned. + */ +static PyObject * +call_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg) +{ + PyObject *offset; + + assert(tzinfo != NULL); + assert(PyTZInfo_Check(tzinfo) || tzinfo == Py_None); + assert(tzinfoarg != NULL); + + if (tzinfo == Py_None) + Py_RETURN_NONE; + offset = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg); + if (offset == Py_None || offset == NULL) + return offset; + if (PyDelta_Check(offset)) { + if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) { + Py_DECREF(offset); + PyErr_Format(PyExc_ValueError, "offset must be a timedelta" + " representing a whole number of minutes"); + return NULL; + } + if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) || + GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) { + Py_DECREF(offset); + PyErr_Format(PyExc_ValueError, "offset must be a timedelta" + " strictly between -timedelta(hours=24) and" + " timedelta(hours=24)."); + return NULL; + } + } + else { + Py_DECREF(offset); + PyErr_Format(PyExc_TypeError, + "tzinfo.%s() must return None or " + "timedelta, not '%.200s'", + name, Py_TYPE(offset)->tp_name); + return NULL; + } + + return offset; +} + +/* 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 None or timedelta, TypeError is raised and this returns -1. + * If utcoffset() returns an invalid timedelta (out of range, or not a whole + * # of minutes), ValueError is raised and this returns -1. Else *none is + * set to 0 and the offset is returned (as int # of minutes east of UTC). + */ +static PyObject * +call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg) +{ + return call_tzinfo_method(tzinfo, "utcoffset", tzinfoarg); +} + +/* 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 None or timedelta, TypeError is raised and this + * returns -1. If dst() returns an invalid timedelta for a UTC offset, + * ValueError is raised and this returns -1. Else *none is set to 0 and + * the offset is returned (as an int # of minutes east of UTC). + */ +static PyObject * +call_dst(PyObject *tzinfo, PyObject *tzinfoarg) +{ + return call_tzinfo_method(tzinfo, "dst", tzinfoarg); +} + +/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be + * an instance of the tzinfo class or None. If tzinfo isn't None, and + * tzname() doesn't return None or a string, TypeError is raised and this + * returns NULL. If the result is a string, we ensure it is a Unicode + * string. + */ +static PyObject * +call_tzname(PyObject *tzinfo, PyObject *tzinfoarg) +{ + PyObject *result; + + assert(tzinfo != NULL); + assert(check_tzinfo_subclass(tzinfo) >= 0); + assert(tzinfoarg != NULL); + + if (tzinfo == Py_None) + Py_RETURN_NONE; + + result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg); + + if (result == NULL || result == Py_None) + return result; + + if (!PyUnicode_Check(result)) { + PyErr_Format(PyExc_TypeError, "tzinfo.tzname() must " + "return None or a string, not '%s'", + Py_TYPE(result)->tp_name); + Py_DECREF(result); + result = NULL; + } + + return result; +} + +/* 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(PyUnicode_Check(repr)); + assert(tzinfo); + if (tzinfo == Py_None) + return repr; + /* Get rid of the trailing ')'. */ + assert(PyUnicode_AS_UNICODE(repr)[PyUnicode_GET_SIZE(repr)-1] == ')'); + temp = PyUnicode_FromUnicode(PyUnicode_AS_UNICODE(repr), + PyUnicode_GET_SIZE(repr) - 1); + Py_DECREF(repr); + if (temp == NULL) + return NULL; + repr = PyUnicode_FromFormat("%U, tzinfo=%R)", temp, tzinfo); + Py_DECREF(temp); + return repr; +} + +/* --------------------------------------------------------------------------- + * String format helpers. + */ + +static PyObject * +format_ctime(PyDateTime_Date *date, int hours, int minutes, int seconds) +{ + static const char *DayNames[] = { + "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" + }; + static const char *MonthNames[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + int wday = weekday(GET_YEAR(date), GET_MONTH(date), GET_DAY(date)); + + return PyUnicode_FromFormat("%s %s %2d %02d:%02d:%02d %04d", + DayNames[wday], MonthNames[GET_MONTH(date)-1], + GET_DAY(date), hours, minutes, seconds, + GET_YEAR(date)); +} + +static PyObject *delta_negative(PyDateTime_Delta *self); + +/* 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, size_t buflen, const char *sep, + PyObject *tzinfo, PyObject *tzinfoarg) +{ + PyObject *offset; + int hours, minutes, seconds; + char sign; + + assert(buflen >= 1); + + offset = call_utcoffset(tzinfo, tzinfoarg); + if (offset == NULL) + return -1; + if (offset == Py_None) { + Py_DECREF(offset); + *buf = '\0'; + return 0; + } + /* Offset is normalized, so it is negative if days < 0 */ + if (GET_TD_DAYS(offset) < 0) { + PyObject *temp = offset; + sign = '-'; + offset = delta_negative((PyDateTime_Delta *)offset); + Py_DECREF(temp); + if (offset == NULL) + return -1; + } + else { + sign = '+'; + } + /* Offset is not negative here. */ + seconds = GET_TD_SECONDS(offset); + Py_DECREF(offset); + minutes = divmod(seconds, 60, &seconds); + hours = divmod(minutes, 60, &minutes); + assert(seconds == 0); + /* XXX ignore sub-minute data, curently not allowed. */ + PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes); + + return 0; +} + +static PyObject * +make_Zreplacement(PyObject *object, PyObject *tzinfoarg) +{ + PyObject *temp; + PyObject *tzinfo = get_tzinfo_member(object); + PyObject *Zreplacement = PyUnicode_FromStringAndSize(NULL, 0); + if (Zreplacement == NULL) + return NULL; + if (tzinfo == Py_None || tzinfo == NULL) + return Zreplacement; + + assert(tzinfoarg != NULL); + temp = call_tzname(tzinfo, tzinfoarg); + if (temp == NULL) + goto Error; + if (temp == Py_None) { + Py_DECREF(temp); + return Zreplacement; + } + + assert(PyUnicode_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) + return NULL; + if (!PyUnicode_Check(Zreplacement)) { + PyErr_SetString(PyExc_TypeError, + "tzname.replace() did not return a string"); + goto Error; + } + return Zreplacement; + + Error: + Py_DECREF(Zreplacement); + return NULL; +} + +static PyObject * +make_freplacement(PyObject *object) +{ + char freplacement[64]; + if (PyTime_Check(object)) + sprintf(freplacement, "%06d", TIME_GET_MICROSECOND(object)); + else if (PyDateTime_Check(object)) + sprintf(freplacement, "%06d", DATE_GET_MICROSECOND(object)); + else + sprintf(freplacement, "%06d", 0); + + return PyBytes_FromStringAndSize(freplacement, strlen(freplacement)); +} + +/* 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, %Z and %f format codes via a + * preprocessing step on the format string. + * tzinfoarg is the argument to pass to the object's tzinfo method, if + * needed. + */ +static PyObject * +wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, + PyObject *tzinfoarg) +{ + PyObject *result = NULL; /* guilty until proved innocent */ + + PyObject *zreplacement = NULL; /* py string, replacement for %z */ + PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ + PyObject *freplacement = NULL; /* py string, replacement for %f */ + + const char *pin; /* pointer to next char in input format */ + Py_ssize_t flen; /* length of 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 */ + size_t totalnew; /* number bytes total in output format buffer, + exclusive of trailing \0 */ + size_t usednew; /* number bytes used so far in output format buffer */ + + const char *ptoappend; /* ptr to string to append to output buffer */ + Py_ssize_t ntoappend; /* # of bytes to append to output buffer */ + + assert(object && format && timetuple); + assert(PyUnicode_Check(format)); + /* Convert the input format to a C string and size */ + pin = _PyUnicode_AsStringAndSize(format, &flen); + if (!pin) + return NULL; + + /* Give up if the year is before 1900. + * Python strftime() plays games with the year, and different + * games depending on whether envar PYTHON2K is set. This makes + * years before 1900 a nightmare, even if the platform strftime + * supports them (and not all do). + * We could get a lot farther here by avoiding Python's strftime + * wrapper and calling the C strftime() directly, but that isn't + * an option in the Python implementation of this module. + */ + { + long year; + PyObject *pyyear = PySequence_GetItem(timetuple, 0); + if (pyyear == NULL) return NULL; + assert(PyLong_Check(pyyear)); + year = PyLong_AsLong(pyyear); + Py_DECREF(pyyear); + if (year < 1900) { + PyErr_Format(PyExc_ValueError, "year=%ld is before " + "1900; the datetime strftime() " + "methods require year >= 1900", + year); + return NULL; + } + } + + /* Scan the input format, looking for %z/%Z/%f escapes, building + * a new format. Since computing the replacements for those codes + * is expensive, don't unless they're actually used. + */ + if (flen > INT_MAX - 1) { + PyErr_NoMemory(); + goto Done; + } + + totalnew = flen + 1; /* realistic if no %z/%Z */ + newfmt = PyBytes_FromStringAndSize(NULL, totalnew); + if (newfmt == NULL) goto Done; + pnew = PyBytes_AsString(newfmt); + usednew = 0; + + while ((ch = *pin++) != '\0') { + if (ch != '%') { + ptoappend = pin - 1; + 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 */ + char buf[100]; + PyObject *tzinfo = get_tzinfo_member(object); + zreplacement = PyBytes_FromStringAndSize("", 0); + if (zreplacement == NULL) goto Done; + if (tzinfo != Py_None && tzinfo != NULL) { + assert(tzinfoarg != NULL); + if (format_utcoffset(buf, + sizeof(buf), + "", + tzinfo, + tzinfoarg) < 0) + goto Done; + Py_DECREF(zreplacement); + zreplacement = + PyBytes_FromStringAndSize(buf, + strlen(buf)); + if (zreplacement == NULL) + goto Done; + } + } + assert(zreplacement != NULL); + ptoappend = PyBytes_AS_STRING(zreplacement); + ntoappend = PyBytes_GET_SIZE(zreplacement); + } + else if (ch == 'Z') { + /* format tzname */ + if (Zreplacement == NULL) { + Zreplacement = make_Zreplacement(object, + tzinfoarg); + if (Zreplacement == NULL) + goto Done; + } + assert(Zreplacement != NULL); + assert(PyUnicode_Check(Zreplacement)); + ptoappend = _PyUnicode_AsStringAndSize(Zreplacement, + &ntoappend); + ntoappend = Py_SIZE(Zreplacement); + } + else if (ch == 'f') { + /* format microseconds */ + if (freplacement == NULL) { + freplacement = make_freplacement(object); + if (freplacement == NULL) + goto Done; + } + assert(freplacement != NULL); + assert(PyBytes_Check(freplacement)); + ptoappend = PyBytes_AS_STRING(freplacement); + ntoappend = PyBytes_GET_SIZE(freplacement); + } + else { + /* percent followed by neither z nor Z */ + ptoappend = pin - 2; + ntoappend = 2; + } + + /* Append the ntoappend chars starting at ptoappend to + * the new format. + */ + if (ntoappend == 0) + continue; + assert(ptoappend != NULL); + assert(ntoappend > 0); + while (usednew + ntoappend > totalnew) { + size_t bigger = totalnew << 1; + if ((bigger >> 1) != totalnew) { /* overflow */ + PyErr_NoMemory(); + goto Done; + } + if (_PyBytes_Resize(&newfmt, bigger) < 0) + goto Done; + totalnew = bigger; + pnew = PyBytes_AsString(newfmt) + usednew; + } + memcpy(pnew, ptoappend, ntoappend); + pnew += ntoappend; + usednew += ntoappend; + assert(usednew <= totalnew); + } /* end while() */ + + if (_PyBytes_Resize(&newfmt, usednew) < 0) + goto Done; + { + PyObject *format; + PyObject *time = PyImport_ImportModuleNoBlock("time"); + if (time == NULL) + goto Done; + format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt)); + if (format != NULL) { + result = PyObject_CallMethod(time, "strftime", "OO", + format, timetuple, NULL); + Py_DECREF(format); + } + Py_DECREF(time); + } + Done: + Py_XDECREF(freplacement); + Py_XDECREF(zreplacement); + Py_XDECREF(Zreplacement); + Py_XDECREF(newfmt); + return result; +} + +/* --------------------------------------------------------------------------- + * 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(void) +{ + PyObject *result = NULL; + PyObject *time = PyImport_ImportModuleNoBlock("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_ImportModuleNoBlock("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 various reasons, we need to use tp_richcompare instead of tp_reserved. + * 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; +} + +/* Raises a "can't compare" TypeError and returns NULL. */ +static PyObject * +cmperror(PyObject *a, PyObject *b) +{ + PyErr_Format(PyExc_TypeError, + "can't compare %s to %s", + Py_TYPE(a)->tp_name, Py_TYPE(b)->tp_name); + return NULL; +} + +/* --------------------------------------------------------------------------- + * 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 */ + +/* --------------------------------------------------------------------------- + * 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 = PyLong_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 = PyLong_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 = PyLong_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_ex(PyObject *pyus, PyTypeObject *type) +{ + int us; + int s; + int d; + long temp; + + 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; + temp = PyLong_AsLong(num); + num = NULL; + if (temp == -1 && PyErr_Occurred()) + goto Done; + assert(0 <= temp && temp < 1000000); + us = (int)temp; + 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; + temp = PyLong_AsLong(num); + num = NULL; + if (temp == -1 && PyErr_Occurred()) + goto Done; + assert(0 <= temp && temp < 24*3600); + s = (int)temp; + + 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); + temp = PyLong_AsLong(num); + if (temp == -1 && PyErr_Occurred()) + goto Done; + d = (int)temp; + if ((long)d != temp) { + PyErr_SetString(PyExc_OverflowError, "normalized days too " + "large to fit in a C int"); + goto Done; + } + result = new_delta_ex(d, s, us, 0, type); + +Done: + Py_XDECREF(tuple); + Py_XDECREF(num); + return result; +} + +#define microseconds_to_delta(pymicros) \ + microseconds_to_delta_ex(pymicros, &PyDateTime_DeltaType) + +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 * +multiply_float_timedelta(PyObject *floatobj, PyDateTime_Delta *delta) +{ + PyObject *result = NULL; + PyObject *pyus_in = NULL, *temp, *pyus_out; + PyObject *ratio = NULL; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + ratio = PyObject_CallMethod(floatobj, "as_integer_ratio", NULL); + if (ratio == NULL) + goto error; + temp = PyNumber_Multiply(pyus_in, PyTuple_GET_ITEM(ratio, 0)); + Py_DECREF(pyus_in); + pyus_in = NULL; + if (temp == NULL) + goto error; + pyus_out = divide_nearest(temp, PyTuple_GET_ITEM(ratio, 1)); + Py_DECREF(temp); + if (pyus_out == NULL) + goto error; + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + error: + Py_XDECREF(pyus_in); + Py_XDECREF(ratio); + + 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 * +divide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *result; + + pyus_left = delta_to_microseconds(left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds(right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + result = PyNumber_FloorDivide(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + return result; +} + +static PyObject * +truedivide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *result; + + pyus_left = delta_to_microseconds(left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds(right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + result = PyNumber_TrueDivide(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + return result; +} + +static PyObject * +truedivide_timedelta_float(PyDateTime_Delta *delta, PyObject *f) +{ + PyObject *result = NULL; + PyObject *pyus_in = NULL, *temp, *pyus_out; + PyObject *ratio = NULL; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + ratio = PyObject_CallMethod(f, "as_integer_ratio", NULL); + if (ratio == NULL) + goto error; + temp = PyNumber_Multiply(pyus_in, PyTuple_GET_ITEM(ratio, 1)); + Py_DECREF(pyus_in); + pyus_in = NULL; + if (temp == NULL) + goto error; + pyus_out = divide_nearest(temp, PyTuple_GET_ITEM(ratio, 0)); + Py_DECREF(temp); + if (pyus_out == NULL) + goto error; + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + error: + Py_XDECREF(pyus_in); + Py_XDECREF(ratio); + + return result; +} + +static PyObject * +truedivide_timedelta_int(PyDateTime_Delta *delta, PyObject *i) +{ + PyObject *result; + PyObject *pyus_in, *pyus_out; + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + pyus_out = divide_nearest(pyus_in, i); + 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; +} + +static int +delta_cmp(PyObject *self, PyObject *other) +{ + int 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; +} + +static PyObject * +delta_richcompare(PyObject *self, PyObject *other, int op) +{ + if (PyDelta_Check(other)) { + int diff = delta_cmp(self, other); + return diff_to_bool(diff, op); + } + else { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } +} + +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 (PyLong_Check(right)) + result = multiply_int_timedelta(right, + (PyDateTime_Delta *) left); + else if (PyFloat_Check(right)) + result = multiply_float_timedelta(right, + (PyDateTime_Delta *) left); + } + else if (PyLong_Check(left)) + result = multiply_int_timedelta(left, + (PyDateTime_Delta *) right); + else if (PyFloat_Check(left)) + result = multiply_float_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 (PyLong_Check(right)) + result = divide_timedelta_int( + (PyDateTime_Delta *)left, + right); + else if (PyDelta_Check(right)) + result = divide_timedelta_timedelta( + (PyDateTime_Delta *)left, + (PyDateTime_Delta *)right); + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +static PyObject * +delta_truedivide(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left)) { + if (PyDelta_Check(right)) + result = truedivide_timedelta_timedelta( + (PyDateTime_Delta *)left, + (PyDateTime_Delta *)right); + else if (PyFloat_Check(right)) + result = truedivide_timedelta_float( + (PyDateTime_Delta *)left, right); + else if (PyLong_Check(right)) + result = truedivide_timedelta_int( + (PyDateTime_Delta *)left, right); + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +static PyObject * +delta_remainder(PyObject *left, PyObject *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *pyus_remainder; + PyObject *remainder; + + if (!PyDelta_Check(left) || !PyDelta_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + pyus_left = delta_to_microseconds((PyDateTime_Delta *)left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds((PyDateTime_Delta *)right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + pyus_remainder = PyNumber_Remainder(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + if (pyus_remainder == NULL) + return NULL; + + remainder = microseconds_to_delta(pyus_remainder); + Py_DECREF(pyus_remainder); + if (remainder == NULL) + return NULL; + + return remainder; +} + +static PyObject * +delta_divmod(PyObject *left, PyObject *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *divmod; + PyObject *delta; + PyObject *result; + + if (!PyDelta_Check(left) || !PyDelta_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + pyus_left = delta_to_microseconds((PyDateTime_Delta *)left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds((PyDateTime_Delta *)right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + divmod = PyNumber_Divmod(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + if (divmod == NULL) + return NULL; + + assert(PyTuple_Size(divmod) == 2); + delta = microseconds_to_delta(PyTuple_GET_ITEM(divmod, 1)); + if (delta == NULL) { + Py_DECREF(divmod); + return NULL; + } + result = PyTuple_Pack(2, PyTuple_GET_ITEM(divmod, 0), delta); + Py_DECREF(delta); + Py_DECREF(divmod); + 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 (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(PyLong_Check(factor)); + 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, Py_TYPE(num)->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 = PyLong_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 = PyLong_FromLong(round_to_long(leftover_us)); + if (temp == NULL) { + Py_DECREF(x); + goto Done; + } + y = PyNumber_Add(x, temp); + Py_DECREF(temp); + CLEANUP; + } + + self = microseconds_to_delta_ex(x, type); + Py_DECREF(x); +Done: + return self; + +#undef CLEANUP +} + +static int +delta_bool(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 PyUnicode_FromFormat("%s(%d, %d, %d)", + Py_TYPE(self)->tp_name, + GET_TD_DAYS(self), + GET_TD_SECONDS(self), + GET_TD_MICROSECONDS(self)); + if (GET_TD_SECONDS(self) != 0) + return PyUnicode_FromFormat("%s(%d, %d)", + Py_TYPE(self)->tp_name, + GET_TD_DAYS(self), + GET_TD_SECONDS(self)); + + return PyUnicode_FromFormat("%s(%d)", + Py_TYPE(self)->tp_name, + GET_TD_DAYS(self)); +} + +static PyObject * +delta_str(PyDateTime_Delta *self) +{ + int us = GET_TD_MICROSECONDS(self); + int seconds = GET_TD_SECONDS(self); + int minutes = divmod(seconds, 60, &seconds); + int hours = divmod(minutes, 60, &minutes); + int days = GET_TD_DAYS(self); + + if (days) { + if (us) + return PyUnicode_FromFormat("%d day%s, %d:%02d:%02d.%06d", + days, (days == 1 || days == -1) ? "" : "s", + hours, minutes, seconds, us); + else + return PyUnicode_FromFormat("%d day%s, %d:%02d:%02d", + days, (days == 1 || days == -1) ? "" : "s", + hours, minutes, seconds); + } else { + if (us) + return PyUnicode_FromFormat("%d:%02d:%02d.%06d", + hours, minutes, seconds, us); + else + return PyUnicode_FromFormat("%d:%02d:%02d", + hours, minutes, seconds); + } + +} + +/* Pickle support, a simple use of __reduce__. */ + +/* __getstate__ isn't exposed */ +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_total_seconds(PyObject *self) +{ + PyObject *total_seconds; + PyObject *total_microseconds; + PyObject *one_million; + + total_microseconds = delta_to_microseconds((PyDateTime_Delta *)self); + if (total_microseconds == NULL) + return NULL; + + one_million = PyLong_FromLong(1000000L); + if (one_million == NULL) { + Py_DECREF(total_microseconds); + return NULL; + } + + total_seconds = PyNumber_TrueDivide(total_microseconds, one_million); + + Py_DECREF(total_microseconds); + Py_DECREF(one_million); + return total_seconds; +} + +static PyObject * +delta_reduce(PyDateTime_Delta* self) +{ + return Py_BuildValue("ON", Py_TYPE(self), delta_getstate(self)); +} + +#define OFFSET(field) offsetof(PyDateTime_Delta, field) + +static PyMemberDef delta_members[] = { + + {"days", T_INT, OFFSET(days), READONLY, + PyDoc_STR("Number of days.")}, + + {"seconds", T_INT, OFFSET(seconds), READONLY, + PyDoc_STR("Number of seconds (>= 0 and less than 1 day).")}, + + {"microseconds", T_INT, OFFSET(microseconds), READONLY, + PyDoc_STR("Number of microseconds (>= 0 and less than 1 second).")}, + {NULL} +}; + +static PyMethodDef delta_methods[] = { + {"total_seconds", (PyCFunction)delta_total_seconds, METH_NOARGS, + PyDoc_STR("Total seconds in the duration.")}, + + {"__reduce__", (PyCFunction)delta_reduce, METH_NOARGS, + PyDoc_STR("__reduce__() -> (cls, 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_remainder, /* nb_remainder */ + delta_divmod, /* nb_divmod */ + 0, /* nb_power */ + (unaryfunc)delta_negative, /* nb_negative */ + (unaryfunc)delta_positive, /* nb_positive */ + (unaryfunc)delta_abs, /* nb_absolute */ + (inquiry)delta_bool, /* nb_bool */ + 0, /*nb_invert*/ + 0, /*nb_lshift*/ + 0, /*nb_rshift*/ + 0, /*nb_and*/ + 0, /*nb_xor*/ + 0, /*nb_or*/ + 0, /*nb_int*/ + 0, /*nb_reserved*/ + 0, /*nb_float*/ + 0, /*nb_inplace_add*/ + 0, /*nb_inplace_subtract*/ + 0, /*nb_inplace_multiply*/ + 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 */ + delta_truedivide, /* nb_true_divide */ + 0, /* nb_inplace_floor_divide */ + 0, /* nb_inplace_true_divide */ +}; + +static PyTypeObject PyDateTime_DeltaType = { + PyVarObject_HEAD_INIT(NULL, 0) + "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_reserved */ + (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_BASETYPE, /* tp_flags */ + delta_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 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 */ + 0, /* tp_free */ +}; + +/* + * PyDateTime_Date implementation. + */ + +/* Accessor properties. */ + +static PyObject * +date_year(PyDateTime_Date *self, void *unused) +{ + return PyLong_FromLong(GET_YEAR(self)); +} + +static PyObject * +date_month(PyDateTime_Date *self, void *unused) +{ + return PyLong_FromLong(GET_MONTH(self)); +} + +static PyObject * +date_day(PyDateTime_Date *self, void *unused) +{ + return PyLong_FromLong(GET_DAY(self)); +} + +static PyGetSetDef date_getset[] = { + {"year", (getter)date_year}, + {"month", (getter)date_month}, + {"day", (getter)date_day}, + {NULL} +}; + +/* Constructors. */ + +static char *date_kws[] = {"year", "month", "day", NULL}; + +static PyObject * +date_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + PyObject *state; + int year; + int month; + int day; + + /* Check for invocation from pickle with __getstate__ state */ + if (PyTuple_GET_SIZE(args) == 1 && + PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && + PyBytes_GET_SIZE(state) == _PyDateTime_DATE_DATASIZE && + MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) + { + PyDateTime_Date *me; + + me = (PyDateTime_Date *) (type->tp_alloc(type, 0)); + if (me != NULL) { + char *pdata = PyBytes_AS_STRING(state); + memcpy(me->data, pdata, _PyDateTime_DATE_DATASIZE); + me->hashcode = -1; + } + return (PyObject *)me; + } + + if (PyArg_ParseTupleAndKeywords(args, kw, "iii", date_kws, + &year, &month, &day)) { + if (check_date_args(year, month, day) < 0) + return NULL; + self = new_date_ex(year, month, day, type); + } + return self; +} + +/* Return new date from localtime(t). */ +static PyObject * +date_local_from_time_t(PyObject *cls, double ts) +{ + struct tm *tm; + time_t t; + PyObject *result = NULL; + + t = _PyTime_DoubleToTimet(ts); + if (t == (time_t)-1 && PyErr_Occurred()) + return 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, 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_Check(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_Check(left)) { + if (PyDate_Check(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) +{ + return PyUnicode_FromFormat("%s(%d, %d, %d)", + Py_TYPE(self)->tp_name, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); +} + +static PyObject * +date_isoformat(PyDateTime_Date *self) +{ + return PyUnicode_FromFormat("%04d-%02d-%02d", + GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); +} + +/* str() calls the appropriate isoformat() 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 *tuple; + PyObject *format; + static char *keywords[] = {"format", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "U:strftime", keywords, + &format)) + return NULL; + + tuple = PyObject_CallMethod((PyObject *)self, "timetuple", "()"); + if (tuple == NULL) + return NULL; + result = wrap_strftime((PyObject *)self, format, tuple, + (PyObject *)self); + Py_DECREF(tuple); + return result; +} + +static PyObject * +date_format(PyDateTime_Date *self, PyObject *args) +{ + PyObject *format; + + if (!PyArg_ParseTuple(args, "U:__format__", &format)) + return NULL; + + /* if the format is zero length, return str(self) */ + if (PyUnicode_GetSize(format) == 0) + return PyObject_Str((PyObject *)self); + + return PyObject_CallMethod((PyObject *)self, "strftime", "O", format); +} + +/* ISO methods. */ + +static PyObject * +date_isoweekday(PyDateTime_Date *self) +{ + int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); + + return PyLong_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. */ + +static PyObject * +date_richcompare(PyObject *self, PyObject *other, int op) +{ + if (PyDate_Check(other)) { + int diff = memcmp(((PyDateTime_Date *)self)->data, + ((PyDateTime_Date *)other)->data, + _PyDateTime_DATE_DATASIZE); + return diff_to_bool(diff, op); + } + else { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } +} + +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_replace(PyDateTime_Date *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int year = GET_YEAR(self); + int month = GET_MONTH(self); + int day = GET_DAY(self); + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iii:replace", date_kws, + &year, &month, &day)) + return NULL; + tuple = Py_BuildValue("iii", year, month, day); + if (tuple == NULL) + return NULL; + clone = date_new(Py_TYPE(self), tuple, NULL); + Py_DECREF(tuple); + return clone; +} + +/* + Borrowed from stringobject.c, originally it was string_hash() +*/ +static long +generic_hash(unsigned char *data, int len) +{ + register unsigned char *p; + register long x; + + p = (unsigned char *) data; + x = *p << 7; + while (--len >= 0) + x = (1000003*x) ^ *p++; + x ^= len; + if (x == -1) + x = -2; + + return x; +} + + +static PyObject *date_getstate(PyDateTime_Date *self); + +static long +date_hash(PyDateTime_Date *self) +{ + if (self->hashcode == -1) + self->hashcode = generic_hash( + (unsigned char *)self->data, _PyDateTime_DATE_DATASIZE); + + return self->hashcode; +} + +static PyObject * +date_toordinal(PyDateTime_Date *self) +{ + return PyLong_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 PyLong_FromLong(dow); +} + +/* Pickle support, a simple use of __reduce__. */ + +/* __getstate__ isn't exposed */ +static PyObject * +date_getstate(PyDateTime_Date *self) +{ + PyObject* field; + field = PyBytes_FromStringAndSize((char*)self->data, + _PyDateTime_DATE_DATASIZE); + return Py_BuildValue("(N)", field); +} + +static PyObject * +date_reduce(PyDateTime_Date *self, PyObject *arg) +{ + return Py_BuildValue("(ON)", Py_TYPE(self), date_getstate(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_VARARGS | METH_KEYWORDS, + PyDoc_STR("format -> strftime() style string.")}, + + {"__format__", (PyCFunction)date_format, METH_VARARGS, + PyDoc_STR("Formats self with strftime.")}, + + {"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")}, + + {"replace", (PyCFunction)date_replace, METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("Return date with new specified fields.")}, + + {"__reduce__", (PyCFunction)date_reduce, METH_NOARGS, + PyDoc_STR("__reduce__() -> (cls, state)")}, + + {NULL, NULL} +}; + +static char date_doc[] = +PyDoc_STR("date(year, month, day) --> date object"); + +static PyNumberMethods date_as_number = { + date_add, /* nb_add */ + date_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_bool */ +}; + +static PyTypeObject PyDateTime_DateType = { + PyVarObject_HEAD_INIT(NULL, 0) + "datetime.date", /* tp_name */ + sizeof(PyDateTime_Date), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + (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_BASETYPE, /* tp_flags */ + date_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 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 */ + 0, /* 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 + * datetime and time 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"); +} + + +static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date, + PyDateTime_Delta *delta, + int factor); +static PyObject *datetime_utcoffset(PyObject *self, PyObject *); +static PyObject *datetime_dst(PyObject *self, PyObject *); + +static PyObject * +tzinfo_fromutc(PyDateTime_TZInfo *self, PyObject *dt) +{ + PyObject *result = NULL; + PyObject *off = NULL, *dst = NULL; + PyDateTime_Delta *delta = NULL; + + if (!PyDateTime_Check(dt)) { + PyErr_SetString(PyExc_TypeError, + "fromutc: argument must be a datetime"); + return NULL; + } + if (GET_DT_TZINFO(dt) != (PyObject *)self) { + PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo " + "is not self"); + return NULL; + } + + off = datetime_utcoffset(dt, NULL); + if (off == NULL) + return NULL; + if (off == Py_None) { + PyErr_SetString(PyExc_ValueError, "fromutc: non-None " + "utcoffset() result required"); + goto Fail; + } + + dst = datetime_dst(dt, NULL); + if (dst == NULL) + goto Fail; + if (dst == Py_None) { + PyErr_SetString(PyExc_ValueError, "fromutc: non-None " + "dst() result required"); + goto Fail; + } + + delta = (PyDateTime_Delta *)delta_subtract(off, dst); + if (delta == NULL) + goto Fail; + result = add_datetime_timedelta((PyDateTime_DateTime *)dt, delta, 1); + if (result == NULL) + goto Fail; + + Py_DECREF(dst); + dst = call_dst(GET_DT_TZINFO(dt), result); + if (dst == NULL) + goto Fail; + if (dst == Py_None) + goto Inconsistent; + if (delta_bool(delta) != 0) { + PyObject *temp = result; + result = add_datetime_timedelta((PyDateTime_DateTime *)result, + (PyDateTime_Delta *)dst, 1); + Py_DECREF(temp); + if (result == NULL) + goto Fail; + } + Py_DECREF(delta); + Py_DECREF(dst); + Py_DECREF(off); + return result; + +Inconsistent: + PyErr_SetString(PyExc_ValueError, "fromutc: tz.dst() gave" + "inconsistent results; cannot convert"); + + /* fall thru to failure */ +Fail: + Py_XDECREF(off); + Py_XDECREF(dst); + Py_XDECREF(delta); + Py_XDECREF(result); + return NULL; +} + +/* + * Pickle support. This is solely so that tzinfo subclasses can use + * pickling -- tzinfo itself is supposed to be uninstantiable. + */ + +static PyObject * +tzinfo_reduce(PyObject *self) +{ + PyObject *args, *state, *tmp; + PyObject *getinitargs, *getstate; + + tmp = PyTuple_New(0); + if (tmp == NULL) + return NULL; + + getinitargs = PyObject_GetAttrString(self, "__getinitargs__"); + if (getinitargs != NULL) { + args = PyObject_CallObject(getinitargs, tmp); + Py_DECREF(getinitargs); + if (args == NULL) { + Py_DECREF(tmp); + return NULL; + } + } + else { + PyErr_Clear(); + args = tmp; + Py_INCREF(args); + } + + getstate = PyObject_GetAttrString(self, "__getstate__"); + if (getstate != NULL) { + state = PyObject_CallObject(getstate, tmp); + Py_DECREF(getstate); + if (state == NULL) { + Py_DECREF(args); + Py_DECREF(tmp); + return NULL; + } + } + else { + PyObject **dictptr; + PyErr_Clear(); + state = Py_None; + dictptr = _PyObject_GetDictPtr(self); + if (dictptr && *dictptr && PyDict_Size(*dictptr)) + state = *dictptr; + Py_INCREF(state); + } + + Py_DECREF(tmp); + + if (state == Py_None) { + Py_DECREF(state); + return Py_BuildValue("(ON)", Py_TYPE(self), args); + } + else + return Py_BuildValue("(ONN)", Py_TYPE(self), args, state); +} + +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 -> timedelta showing offset from UTC, negative " + "values indicating West of UTC")}, + + {"dst", (PyCFunction)tzinfo_dst, METH_O, + PyDoc_STR("datetime -> DST offset in minutes east of UTC.")}, + + {"fromutc", (PyCFunction)tzinfo_fromutc, METH_O, + PyDoc_STR("datetime in UTC -> datetime in local time.")}, + + {"__reduce__", (PyCFunction)tzinfo_reduce, METH_NOARGS, + PyDoc_STR("-> (cls, state)")}, + + {NULL, NULL} +}; + +static char tzinfo_doc[] = +PyDoc_STR("Abstract base class for time zone info objects."); + +static PyTypeObject PyDateTime_TZInfoType = { + PyVarObject_HEAD_INIT(NULL, 0) + "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_reserved */ + 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_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 */ +}; + +static char *timezone_kws[] = {"offset", "name", NULL}; + +static PyObject * +timezone_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *offset; + PyObject *name = NULL; + if (PyArg_ParseTupleAndKeywords(args, kw, "O!|O!:timezone", timezone_kws, + &PyDateTime_DeltaType, &offset, + &PyUnicode_Type, &name)) + return new_timezone(offset, name); + + return NULL; +} + +static void +timezone_dealloc(PyDateTime_TimeZone *self) +{ + Py_CLEAR(self->offset); + Py_CLEAR(self->name); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +timezone_richcompare(PyDateTime_TimeZone *self, + PyDateTime_TimeZone *other, int op) +{ + if (op != Py_EQ && op != Py_NE) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + return delta_richcompare(self->offset, other->offset, op); +} + +static long +timezone_hash(PyDateTime_TimeZone *self) +{ + return delta_hash((PyDateTime_Delta *)self->offset); +} + +/* Check argument type passed to tzname, utcoffset, or dst methods. + Returns 0 for good argument. Returns -1 and sets exception info + otherwise. + */ +static int +_timezone_check_argument(PyObject *dt, const char *meth) +{ + if (dt == Py_None || PyDateTime_Check(dt)) + return 0; + PyErr_Format(PyExc_TypeError, "%s(dt) argument must be a datetime instance" + " or None, not %.200s", meth, Py_TYPE(dt)->tp_name); + return -1; +} + +static PyObject * +timezone_repr(PyDateTime_TimeZone *self) +{ + /* Note that although timezone is not subclassable, it is convenient + to use Py_TYPE(self)->tp_name here. */ + const char *type_name = Py_TYPE(self)->tp_name; + + if (((PyObject *)self) == PyDateTime_TimeZone_UTC) + return PyUnicode_FromFormat("%s.utc", type_name); + + if (self->name == NULL) + return PyUnicode_FromFormat("%s(%R)", type_name, self->offset); + + return PyUnicode_FromFormat("%s(%R, %R)", type_name, self->offset, + self->name); +} + + +static PyObject * +timezone_str(PyDateTime_TimeZone *self) +{ + char buf[10]; + int hours, minutes, seconds; + PyObject *offset; + char sign; + + if (self->name != NULL) { + Py_INCREF(self->name); + return self->name; + } + /* Offset is normalized, so it is negative if days < 0 */ + if (GET_TD_DAYS(self->offset) < 0) { + sign = '-'; + offset = delta_negative((PyDateTime_Delta *)self->offset); + if (offset == NULL) + return NULL; + } + else { + sign = '+'; + offset = self->offset; + Py_INCREF(offset); + } + /* Offset is not negative here. */ + seconds = GET_TD_SECONDS(offset); + Py_DECREF(offset); + minutes = divmod(seconds, 60, &seconds); + hours = divmod(minutes, 60, &minutes); + assert(seconds == 0); + /* XXX ignore sub-minute data, curently not allowed. */ + PyOS_snprintf(buf, sizeof(buf), "UTC%c%02d:%02d", sign, hours, minutes); + + return PyUnicode_FromString(buf); +} + +static PyObject * +timezone_tzname(PyDateTime_TimeZone *self, PyObject *dt) +{ + if (_timezone_check_argument(dt, "tzname") == -1) + return NULL; + + return timezone_str(self); +} + +static PyObject * +timezone_utcoffset(PyDateTime_TimeZone *self, PyObject *dt) +{ + if (_timezone_check_argument(dt, "utcoffset") == -1) + return NULL; + + Py_INCREF(self->offset); + return self->offset; +} + +static PyObject * +timezone_dst(PyObject *self, PyObject *dt) +{ + if (_timezone_check_argument(dt, "dst") == -1) + return NULL; + + Py_RETURN_NONE; +} + +static PyObject * +timezone_fromutc(PyDateTime_TimeZone *self, PyDateTime_DateTime *dt) +{ + if (!PyDateTime_Check(dt)) { + PyErr_SetString(PyExc_TypeError, + "fromutc: argument must be a datetime"); + return NULL; + } + if (!HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) { + PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo " + "is not self"); + return NULL; + } + + return add_datetime_timedelta(dt, (PyDateTime_Delta *)self->offset, 1); +} + +static PyObject * +timezone_getinitargs(PyDateTime_TimeZone *self) +{ + if (self->name == NULL) + return Py_BuildValue("(O)", self->offset); + return Py_BuildValue("(OO)", self->offset, self->name); +} + +static PyMethodDef timezone_methods[] = { + {"tzname", (PyCFunction)timezone_tzname, METH_O, + PyDoc_STR("If name is specified when timezone is created, returns the name." + " Otherwise returns offset as 'UTC(+|-)HH:MM'.")}, + + {"utcoffset", (PyCFunction)timezone_utcoffset, METH_O, + PyDoc_STR("Return fixed offset.")}, + + {"dst", (PyCFunction)timezone_dst, METH_O, + PyDoc_STR("Return None.")}, + + {"fromutc", (PyCFunction)timezone_fromutc, METH_O, + PyDoc_STR("datetime in UTC -> datetime in local time.")}, + + {"__getinitargs__", (PyCFunction)timezone_getinitargs, METH_NOARGS, + PyDoc_STR("pickle support")}, + + {NULL, NULL} +}; + +static char timezone_doc[] = +PyDoc_STR("Fixed offset from UTC implementation of tzinfo."); + +static PyTypeObject PyDateTime_TimeZoneType = { + PyVarObject_HEAD_INIT(NULL, 0) + "datetime.timezone", /* tp_name */ + sizeof(PyDateTime_TimeZone), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)timezone_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + (reprfunc)timezone_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)timezone_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)timezone_str, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + timezone_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)timezone_richcompare,/* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + timezone_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyDateTime_TZInfoType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + timezone_new, /* tp_new */ +}; + +/* + * PyDateTime_Time implementation. + */ + +/* Accessor properties. + */ + +static PyObject * +time_hour(PyDateTime_Time *self, void *unused) +{ + return PyLong_FromLong(TIME_GET_HOUR(self)); +} + +static PyObject * +time_minute(PyDateTime_Time *self, void *unused) +{ + return PyLong_FromLong(TIME_GET_MINUTE(self)); +} + +/* The name time_second conflicted with some platform header file. */ +static PyObject * +py_time_second(PyDateTime_Time *self, void *unused) +{ + return PyLong_FromLong(TIME_GET_SECOND(self)); +} + +static PyObject * +time_microsecond(PyDateTime_Time *self, void *unused) +{ + return PyLong_FromLong(TIME_GET_MICROSECOND(self)); +} + +static PyObject * +time_tzinfo(PyDateTime_Time *self, void *unused) +{ + PyObject *result = HASTZINFO(self) ? self->tzinfo : Py_None; + Py_INCREF(result); + return result; +} + +static PyGetSetDef time_getset[] = { + {"hour", (getter)time_hour}, + {"minute", (getter)time_minute}, + {"second", (getter)py_time_second}, + {"microsecond", (getter)time_microsecond}, + {"tzinfo", (getter)time_tzinfo}, + {NULL} +}; + +/* + * Constructors. + */ + +static char *time_kws[] = {"hour", "minute", "second", "microsecond", + "tzinfo", NULL}; + +static PyObject * +time_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + PyObject *state; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + PyObject *tzinfo = Py_None; + + /* Check for invocation from pickle with __getstate__ state */ + if (PyTuple_GET_SIZE(args) >= 1 && + PyTuple_GET_SIZE(args) <= 2 && + PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && + PyBytes_GET_SIZE(state) == _PyDateTime_TIME_DATASIZE && + ((unsigned char) (PyBytes_AS_STRING(state)[0])) < 24) + { + PyDateTime_Time *me; + char aware; + + if (PyTuple_GET_SIZE(args) == 2) { + tzinfo = PyTuple_GET_ITEM(args, 1); + if (check_tzinfo_subclass(tzinfo) < 0) { + PyErr_SetString(PyExc_TypeError, "bad " + "tzinfo state arg"); + return NULL; + } + } + aware = (char)(tzinfo != Py_None); + me = (PyDateTime_Time *) (type->tp_alloc(type, aware)); + if (me != NULL) { + char *pdata = PyBytes_AS_STRING(state); + + memcpy(me->data, pdata, _PyDateTime_TIME_DATASIZE); + me->hashcode = -1; + me->hastzinfo = aware; + if (aware) { + Py_INCREF(tzinfo); + me->tzinfo = tzinfo; + } + } + return (PyObject *)me; + } + + if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO", time_kws, + &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_time_ex(hour, minute, second, usecond, tzinfo, + type); + } + return self; +} + +/* + * Destructor. + */ + +static void +time_dealloc(PyDateTime_Time *self) +{ + if (HASTZINFO(self)) { + Py_XDECREF(self->tzinfo); + } + Py_TYPE(self)->tp_free((PyObject *)self); +} + +/* + * Indirect access to tzinfo methods. + */ + +/* These are all METH_NOARGS, so don't need to check the arglist. */ +static PyObject * +time_utcoffset(PyObject *self, PyObject *unused) { + return call_utcoffset(GET_TIME_TZINFO(self), Py_None); +} + +static PyObject * +time_dst(PyObject *self, PyObject *unused) { + return call_dst(GET_TIME_TZINFO(self), Py_None); +} + +static PyObject * +time_tzname(PyDateTime_Time *self, PyObject *unused) { + return call_tzname(GET_TIME_TZINFO(self), Py_None); +} + +/* + * Various ways to turn a time into a string. + */ + +static PyObject * +time_repr(PyDateTime_Time *self) +{ + const char *type_name = Py_TYPE(self)->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); + PyObject *result = NULL; + + if (us) + result = PyUnicode_FromFormat("%s(%d, %d, %d, %d)", + type_name, h, m, s, us); + else if (s) + result = PyUnicode_FromFormat("%s(%d, %d, %d)", + type_name, h, m, s); + else + result = PyUnicode_FromFormat("%s(%d, %d)", type_name, h, m); + if (result != NULL && HASTZINFO(self)) + result = append_keyword_tzinfo(result, self->tzinfo); + return result; +} + +static PyObject * +time_str(PyDateTime_Time *self) +{ + return PyObject_CallMethod((PyObject *)self, "isoformat", "()"); +} + +static PyObject * +time_isoformat(PyDateTime_Time *self, PyObject *unused) +{ + char buf[100]; + PyObject *result; + int us = TIME_GET_MICROSECOND(self);; + + if (us) + result = PyUnicode_FromFormat("%02d:%02d:%02d.%06d", + TIME_GET_HOUR(self), + TIME_GET_MINUTE(self), + TIME_GET_SECOND(self), + us); + else + result = PyUnicode_FromFormat("%02d:%02d:%02d", + TIME_GET_HOUR(self), + TIME_GET_MINUTE(self), + TIME_GET_SECOND(self)); + + if (result == NULL || !HASTZINFO(self) || self->tzinfo == Py_None) + return result; + + /* We need to append the UTC offset. */ + if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, + Py_None) < 0) { + Py_DECREF(result); + return NULL; + } + PyUnicode_AppendAndDel(&result, PyUnicode_FromString(buf)); + return result; +} + +static PyObject * +time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw) +{ + PyObject *result; + PyObject *tuple; + PyObject *format; + static char *keywords[] = {"format", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "U:strftime", keywords, + &format)) + return NULL; + + /* Python's strftime does insane things with the year part of the + * timetuple. The year is forced to (the otherwise nonsensical) + * 1900 to worm around that. + */ + tuple = Py_BuildValue("iiiiiiiii", + 1900, 1, 1, /* year, month, day */ + TIME_GET_HOUR(self), + TIME_GET_MINUTE(self), + TIME_GET_SECOND(self), + 0, 1, -1); /* weekday, daynum, dst */ + if (tuple == NULL) + return NULL; + assert(PyTuple_Size(tuple) == 9); + result = wrap_strftime((PyObject *)self, format, tuple, + Py_None); + Py_DECREF(tuple); + return result; +} + +/* + * Miscellaneous methods. + */ + +static PyObject * +time_richcompare(PyObject *self, PyObject *other, int op) +{ + PyObject *result = NULL; + PyObject *offset1, *offset2; + int diff; + + if (! PyTime_Check(other)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + if (GET_TIME_TZINFO(self) == GET_TIME_TZINFO(other)) { + diff = memcmp(((PyDateTime_Time *)self)->data, + ((PyDateTime_Time *)other)->data, + _PyDateTime_TIME_DATASIZE); + return diff_to_bool(diff, op); + } + offset1 = time_utcoffset(self, NULL); + if (offset1 == NULL) + return NULL; + offset2 = time_utcoffset(other, NULL); + if (offset2 == NULL) + goto done; + /* 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 == Py_None at this point. + */ + if ((offset1 == offset2) || + (PyDelta_Check(offset1) && PyDelta_Check(offset2) && + delta_cmp(offset1, offset2) == 0)) { + diff = memcmp(((PyDateTime_Time *)self)->data, + ((PyDateTime_Time *)other)->data, + _PyDateTime_TIME_DATASIZE); + result = diff_to_bool(diff, op); + } + /* The hard case: both aware with different UTC offsets */ + else if (offset1 != Py_None && offset2 != Py_None) { + int offsecs1, offsecs2; + assert(offset1 != offset2); /* else last "if" handled it */ + offsecs1 = TIME_GET_HOUR(self) * 3600 + + TIME_GET_MINUTE(self) * 60 + + TIME_GET_SECOND(self) - + GET_TD_DAYS(offset1) * 86400 - + GET_TD_SECONDS(offset1); + offsecs2 = TIME_GET_HOUR(other) * 3600 + + TIME_GET_MINUTE(other) * 60 + + TIME_GET_SECOND(other) - + GET_TD_DAYS(offset2) * 86400 - + GET_TD_SECONDS(offset2); + diff = offsecs1 - offsecs2; + if (diff == 0) + diff = TIME_GET_MICROSECOND(self) - + TIME_GET_MICROSECOND(other); + result = diff_to_bool(diff, op); + } + else { + PyErr_SetString(PyExc_TypeError, + "can't compare offset-naive and " + "offset-aware times"); + } + done: + Py_DECREF(offset1); + Py_XDECREF(offset2); + return result; +} + +static long +time_hash(PyDateTime_Time *self) +{ + if (self->hashcode == -1) { + PyObject *offset; + + offset = time_utcoffset((PyObject *)self, NULL); + + if (offset == NULL) + return -1; + + /* Reduce this to a hash of another object. */ + if (offset == Py_None) + self->hashcode = generic_hash( + (unsigned char *)self->data, _PyDateTime_TIME_DATASIZE); + else { + PyObject *temp1, *temp2; + int seconds, microseconds; + assert(HASTZINFO(self)); + seconds = TIME_GET_HOUR(self) * 3600 + + TIME_GET_MINUTE(self) * 60 + + TIME_GET_SECOND(self); + microseconds = TIME_GET_MICROSECOND(self); + temp1 = new_delta(0, seconds, microseconds, 1); + if (temp1 == NULL) { + Py_DECREF(offset); + return -1; + } + temp2 = delta_subtract(temp1, offset); + Py_DECREF(temp1); + if (temp2 == NULL) { + Py_DECREF(offset); + return -1; + } + self->hashcode = PyObject_Hash(temp2); + Py_DECREF(temp2); + } + Py_DECREF(offset); + } + return self->hashcode; +} + +static PyObject * +time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int hh = TIME_GET_HOUR(self); + int mm = TIME_GET_MINUTE(self); + int ss = TIME_GET_SECOND(self); + int us = TIME_GET_MICROSECOND(self); + PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO:replace", + time_kws, + &hh, &mm, &ss, &us, &tzinfo)) + return NULL; + tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); + if (tuple == NULL) + return NULL; + clone = time_new(Py_TYPE(self), tuple, NULL); + Py_DECREF(tuple); + return clone; +} + +static int +time_bool(PyObject *self) +{ + PyObject *offset, *tzinfo; + int offsecs = 0; + + 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; + } + tzinfo = GET_TIME_TZINFO(self); + if (tzinfo != Py_None) { + offset = call_utcoffset(tzinfo, Py_None); + if (offset == NULL) + return -1; + offsecs = GET_TD_DAYS(offset)*86400 + GET_TD_SECONDS(offset); + Py_DECREF(offset); + } + return (TIME_GET_MINUTE(self)*60 - offsecs + TIME_GET_HOUR(self)*3600) != 0; +} + +/* Pickle support, a simple use of __reduce__. */ + +/* Let basestate be the non-tzinfo data string. + * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). + * So it's a tuple in any (non-error) case. + * __getstate__ isn't exposed. + */ +static PyObject * +time_getstate(PyDateTime_Time *self) +{ + PyObject *basestate; + PyObject *result = NULL; + + basestate = PyBytes_FromStringAndSize((char *)self->data, + _PyDateTime_TIME_DATASIZE); + if (basestate != NULL) { + if (! HASTZINFO(self) || self->tzinfo == Py_None) + result = PyTuple_Pack(1, basestate); + else + result = PyTuple_Pack(2, basestate, self->tzinfo); + Py_DECREF(basestate); + } + return result; +} + +static PyObject * +time_reduce(PyDateTime_Time *self, PyObject *arg) +{ + return Py_BuildValue("(ON)", Py_TYPE(self), time_getstate(self)); +} + +static PyMethodDef time_methods[] = { + + {"isoformat", (PyCFunction)time_isoformat, METH_NOARGS, + PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm]" + "[+HH:MM].")}, + + {"strftime", (PyCFunction)time_strftime, METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("format -> strftime() style string.")}, + + {"__format__", (PyCFunction)date_format, METH_VARARGS, + PyDoc_STR("Formats self with strftime.")}, + + {"utcoffset", (PyCFunction)time_utcoffset, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, + + {"tzname", (PyCFunction)time_tzname, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.tzname(self).")}, + + {"dst", (PyCFunction)time_dst, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.dst(self).")}, + + {"replace", (PyCFunction)time_replace, METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("Return time with new specified fields.")}, + + {"__reduce__", (PyCFunction)time_reduce, METH_NOARGS, + PyDoc_STR("__reduce__() -> (cls, state)")}, + + {NULL, NULL} +}; + +static char time_doc[] = +PyDoc_STR("time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> a time object\n\ +\n\ +All arguments are optional. tzinfo may be None, or an instance of\n\ +a tzinfo subclass. The remaining arguments may be ints or longs.\n"); + +static PyNumberMethods time_as_number = { + 0, /* nb_add */ + 0, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + (inquiry)time_bool, /* nb_bool */ +}; + +static PyTypeObject PyDateTime_TimeType = { + PyVarObject_HEAD_INIT(NULL, 0) + "datetime.time", /* tp_name */ + sizeof(PyDateTime_Time), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)time_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + (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_BASETYPE, /* tp_flags */ + time_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 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 */ + time_alloc, /* tp_alloc */ + time_new, /* tp_new */ + 0, /* tp_free */ +}; + +/* + * PyDateTime_DateTime implementation. + */ + +/* Accessor properties. Properties for day, month, and year are inherited + * from date. + */ + +static PyObject * +datetime_hour(PyDateTime_DateTime *self, void *unused) +{ + return PyLong_FromLong(DATE_GET_HOUR(self)); +} + +static PyObject * +datetime_minute(PyDateTime_DateTime *self, void *unused) +{ + return PyLong_FromLong(DATE_GET_MINUTE(self)); +} + +static PyObject * +datetime_second(PyDateTime_DateTime *self, void *unused) +{ + return PyLong_FromLong(DATE_GET_SECOND(self)); +} + +static PyObject * +datetime_microsecond(PyDateTime_DateTime *self, void *unused) +{ + return PyLong_FromLong(DATE_GET_MICROSECOND(self)); +} + +static PyObject * +datetime_tzinfo(PyDateTime_DateTime *self, void *unused) +{ + PyObject *result = HASTZINFO(self) ? self->tzinfo : Py_None; + Py_INCREF(result); + return result; +} + +static PyGetSetDef datetime_getset[] = { + {"hour", (getter)datetime_hour}, + {"minute", (getter)datetime_minute}, + {"second", (getter)datetime_second}, + {"microsecond", (getter)datetime_microsecond}, + {"tzinfo", (getter)datetime_tzinfo}, + {NULL} +}; + +/* + * Constructors. + */ + +static char *datetime_kws[] = { + "year", "month", "day", "hour", "minute", "second", + "microsecond", "tzinfo", NULL +}; + +static PyObject * +datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + PyObject *state; + int year; + int month; + int day; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + PyObject *tzinfo = Py_None; + + /* Check for invocation from pickle with __getstate__ state */ + if (PyTuple_GET_SIZE(args) >= 1 && + PyTuple_GET_SIZE(args) <= 2 && + PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && + PyBytes_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE && + MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) + { + PyDateTime_DateTime *me; + char aware; + + if (PyTuple_GET_SIZE(args) == 2) { + tzinfo = PyTuple_GET_ITEM(args, 1); + if (check_tzinfo_subclass(tzinfo) < 0) { + PyErr_SetString(PyExc_TypeError, "bad " + "tzinfo state arg"); + return NULL; + } + } + aware = (char)(tzinfo != Py_None); + me = (PyDateTime_DateTime *) (type->tp_alloc(type , aware)); + if (me != NULL) { + char *pdata = PyBytes_AS_STRING(state); + + memcpy(me->data, pdata, _PyDateTime_DATETIME_DATASIZE); + me->hashcode = -1; + me->hastzinfo = aware; + if (aware) { + Py_INCREF(tzinfo); + me->tzinfo = tzinfo; + } + } + return (PyObject *)me; + } + + if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO", datetime_kws, + &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_datetime_ex(year, month, day, + hour, minute, second, usecond, + tzinfo, type); + } + 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, + PyObject *tzinfo) +{ + struct tm *tm; + PyObject *result = NULL; + + tm = f(&timet); + if (tm) { + /* The platform localtime/gmtime may insert leap seconds, + * indicated by tm->tm_sec > 59. We don't care about them, + * except to the extent that passing them on to the datetime + * constructor would raise ValueError for a reason that + * made no sense to the user. + */ + if (tm->tm_sec > 59) + tm->tm_sec = 59; + result = PyObject_CallFunction(cls, "iiiiiiiO", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec, + us, + tzinfo); + } + 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, + PyObject *tzinfo) +{ + time_t timet; + double fraction; + int us; + + timet = _PyTime_DoubleToTimet(timestamp); + if (timet == (time_t)-1 && PyErr_Occurred()) + return NULL; + fraction = timestamp - (double)timet; + us = (int)round_to_long(fraction * 1e6); + if (us < 0) { + /* Truncation towards zero is not what we wanted + for negative numbers (Python's mod semantics) */ + timet -= 1; + us += 1000000; + } + /* If timestamp is less than one microsecond smaller than a + * full second, round up. Otherwise, ValueErrors are raised + * for some floats. */ + if (us == 1000000) { + timet += 1; + us = 0; + } + return datetime_from_timet_and_us(cls, f, timet, us, tzinfo); +} + +/* 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, PyObject *tzinfo) +{ +#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, + tzinfo); + +#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, tzinfo); +#endif /* ! HAVE_GETTIMEOFDAY */ +} + +/* Return best possible local time -- this isn't constrained by the + * precision of a timestamp. + */ +static PyObject * +datetime_now(PyObject *cls, PyObject *args, PyObject *kw) +{ + PyObject *self; + PyObject *tzinfo = Py_None; + static char *keywords[] = {"tz", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|O:now", keywords, + &tzinfo)) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + + self = datetime_best_possible(cls, + tzinfo == Py_None ? localtime : gmtime, + tzinfo); + if (self != NULL && tzinfo != Py_None) { + /* Convert UTC to tzinfo's zone. */ + PyObject *temp = self; + self = PyObject_CallMethod(tzinfo, "fromutc", "O", self); + Py_DECREF(temp); + } + return self; +} + +/* 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, Py_None); +} + +/* Return new local datetime from timestamp (Python timestamp -- a double). */ +static PyObject * +datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) +{ + PyObject *self; + double timestamp; + PyObject *tzinfo = Py_None; + static char *keywords[] = {"timestamp", "tz", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", + keywords, ×tamp, &tzinfo)) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + + self = datetime_from_timestamp(cls, + tzinfo == Py_None ? localtime : gmtime, + timestamp, + tzinfo); + if (self != NULL && tzinfo != Py_None) { + /* Convert UTC to tzinfo's zone. */ + PyObject *temp = self; + self = PyObject_CallMethod(tzinfo, "fromutc", "O", self); + Py_DECREF(temp); + } + return self; +} + +/* 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, + Py_None); + return result; +} + +/* Return new datetime from _strptime.strptime_datetime(). */ +static PyObject * +datetime_strptime(PyObject *cls, PyObject *args) +{ + static PyObject *module = NULL; + const Py_UNICODE *string, *format; + + if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format)) + return NULL; + + if (module == NULL) { + module = PyImport_ImportModuleNoBlock("_strptime"); + if (module == NULL) + return NULL; + } + return PyObject_CallMethod(module, "_strptime_datetime", "Ouu", + cls, string, format); +} + +/* Return new datetime from date/datetime and time 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)) { + PyObject *tzinfo = Py_None; + + if (HASTZINFO(time)) + tzinfo = ((PyDateTime_Time *)time)->tzinfo; + result = PyObject_CallFunction(cls, "iiiiiiiO", + 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), + tzinfo); + } + return result; +} + +/* + * Destructor. + */ + +static void +datetime_dealloc(PyDateTime_DateTime *self) +{ + if (HASTZINFO(self)) { + Py_XDECREF(self->tzinfo); + } + Py_TYPE(self)->tp_free((PyObject *)self); +} + +/* + * Indirect access to tzinfo methods. + */ + +/* These are all METH_NOARGS, so don't need to check the arglist. */ +static PyObject * +datetime_utcoffset(PyObject *self, PyObject *unused) { + return call_utcoffset(GET_DT_TZINFO(self), self); +} + +static PyObject * +datetime_dst(PyObject *self, PyObject *unused) { + return call_dst(GET_DT_TZINFO(self), self); +} + +static PyObject * +datetime_tzname(PyObject *self, PyObject *unused) { + return call_tzname(GET_DT_TZINFO(self), self); +} + +/* + * datetime arithmetic. + */ + +/* factor must be 1 (to add) or -1 (to subtract). The result inherits + * the tzinfo state of date. + */ +static PyObject * +add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta, + int factor) +{ + /* 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) * factor; + int hour = DATE_GET_HOUR(date); + int minute = DATE_GET_MINUTE(date); + int second = DATE_GET_SECOND(date) + GET_TD_SECONDS(delta) * factor; + int microsecond = DATE_GET_MICROSECOND(date) + + GET_TD_MICROSECONDS(delta) * factor; + + assert(factor == 1 || factor == -1); + if (normalize_datetime(&year, &month, &day, + &hour, &minute, &second, µsecond) < 0) + return NULL; + else + return new_datetime(year, month, day, + hour, minute, second, microsecond, + HASTZINFO(date) ? date->tzinfo : Py_None); +} + +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, + 1); + } + else if (PyDelta_Check(left)) { + /* delta + datetime */ + return add_datetime_timedelta((PyDateTime_DateTime *) right, + (PyDateTime_Delta *) left, + 1); + } + 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 */ + PyObject *offset1, *offset2, *offdiff = NULL; + int delta_d, delta_s, delta_us; + + if (GET_DT_TZINFO(left) == GET_DT_TZINFO(right)) { + offset2 = offset1 = Py_None; + Py_INCREF(offset1); + Py_INCREF(offset2); + } + else { + offset1 = datetime_utcoffset(left, NULL); + if (offset1 == NULL) + return NULL; + offset2 = datetime_utcoffset(right, NULL); + if (offset2 == NULL) { + Py_DECREF(offset1); + return NULL; + } + if ((offset1 != Py_None) != (offset2 != Py_None)) { + PyErr_SetString(PyExc_TypeError, + "can't subtract offset-naive and " + "offset-aware datetimes"); + Py_DECREF(offset1); + Py_DECREF(offset2); + return NULL; + } + } + if ((offset1 != offset2) && + delta_cmp(offset1, offset2) != 0) { + offdiff = delta_subtract(offset1, offset2); + if (offdiff == NULL) { + Py_DECREF(offset1); + Py_DECREF(offset2); + return NULL; + } + } + Py_DECREF(offset1); + Py_DECREF(offset2); + delta_d = ymd_to_ord(GET_YEAR(left), + GET_MONTH(left), + GET_DAY(left)) - + 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. + */ + 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)); + delta_us = DATE_GET_MICROSECOND(left) - + DATE_GET_MICROSECOND(right); + result = new_delta(delta_d, delta_s, delta_us, 1); + if (offdiff != NULL) { + PyObject *temp = result; + result = delta_subtract(result, offdiff); + Py_DECREF(temp); + Py_DECREF(offdiff); + } + } + else if (PyDelta_Check(right)) { + /* datetime - delta */ + result = add_datetime_timedelta( + (PyDateTime_DateTime *)left, + (PyDateTime_Delta *)right, + -1); + } + } + + 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) +{ + const char *type_name = Py_TYPE(self)->tp_name; + PyObject *baserepr; + + if (DATE_GET_MICROSECOND(self)) { + baserepr = PyUnicode_FromFormat( + "%s(%d, %d, %d, %d, %d, %d, %d)", + type_name, + 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)) { + baserepr = PyUnicode_FromFormat( + "%s(%d, %d, %d, %d, %d, %d)", + type_name, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); + } + else { + baserepr = PyUnicode_FromFormat( + "%s(%d, %d, %d, %d, %d)", + type_name, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self)); + } + if (baserepr == NULL || ! HASTZINFO(self)) + return baserepr; + return append_keyword_tzinfo(baserepr, self->tzinfo); +} + +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) +{ + int sep = 'T'; + static char *keywords[] = {"sep", NULL}; + char buffer[100]; + PyObject *result; + int us = DATE_GET_MICROSECOND(self); + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|C:isoformat", keywords, &sep)) + return NULL; + if (us) + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%06d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), us); + else + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); + + if (!result || !HASTZINFO(self)) + return result; + + /* We need to append the UTC offset. */ + if (format_utcoffset(buffer, sizeof(buffer), ":", self->tzinfo, + (PyObject *)self) < 0) { + Py_DECREF(result); + return NULL; + } + PyUnicode_AppendAndDel(&result, PyUnicode_FromString(buffer)); + return result; +} + +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. */ + +static PyObject * +datetime_richcompare(PyObject *self, PyObject *other, int op) +{ + PyObject *result = NULL; + PyObject *offset1, *offset2; + int diff; + + if (! PyDateTime_Check(other)) { + if (PyDate_Check(other)) { + /* Prevent invocation of date_richcompare. We want to + return NotImplemented here to give the other object + a chance. But since DateTime is a subclass of + Date, if the other object is a Date, it would + compute an ordering based on the date part alone, + and we don't want that. So force unequal or + uncomparable here in that case. */ + if (op == Py_EQ) + Py_RETURN_FALSE; + if (op == Py_NE) + Py_RETURN_TRUE; + return cmperror(self, other); + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + if (GET_DT_TZINFO(self) == GET_DT_TZINFO(other)) { + diff = memcmp(((PyDateTime_DateTime *)self)->data, + ((PyDateTime_DateTime *)other)->data, + _PyDateTime_DATETIME_DATASIZE); + return diff_to_bool(diff, op); + } + offset1 = datetime_utcoffset(self, NULL); + if (offset1 == NULL) + return NULL; + offset2 = datetime_utcoffset(other, NULL); + if (offset2 == NULL) + goto done; + /* 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 == Py_None at this point. + */ + if ((offset1 == offset2) || + (PyDelta_Check(offset1) && PyDelta_Check(offset2) && + delta_cmp(offset1, offset2) == 0)) { + diff = memcmp(((PyDateTime_DateTime *)self)->data, + ((PyDateTime_DateTime *)other)->data, + _PyDateTime_DATETIME_DATASIZE); + result = diff_to_bool(diff, op); + } + else if (offset1 != Py_None && offset2 != Py_None) { + PyDateTime_Delta *delta; + + assert(offset1 != offset2); /* else last "if" handled it */ + delta = (PyDateTime_Delta *)datetime_subtract((PyObject *)self, + other); + if (delta == NULL) + goto done; + diff = GET_TD_DAYS(delta); + if (diff == 0) + diff = GET_TD_SECONDS(delta) | + GET_TD_MICROSECONDS(delta); + Py_DECREF(delta); + result = diff_to_bool(diff, op); + } + else { + PyErr_SetString(PyExc_TypeError, + "can't compare offset-naive and " + "offset-aware datetimes"); + } + done: + Py_DECREF(offset1); + Py_XDECREF(offset2); + return result; +} + +static long +datetime_hash(PyDateTime_DateTime *self) +{ + if (self->hashcode == -1) { + PyObject *offset; + + offset = datetime_utcoffset((PyObject *)self, NULL); + + if (offset == NULL) + return -1; + + /* Reduce this to a hash of another object. */ + if (offset == Py_None) + self->hashcode = generic_hash( + (unsigned char *)self->data, _PyDateTime_DATETIME_DATASIZE); + else { + PyObject *temp1, *temp2; + int days, seconds; + + assert(HASTZINFO(self)); + days = ymd_to_ord(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self)); + seconds = DATE_GET_HOUR(self) * 3600 + + DATE_GET_MINUTE(self) * 60 + + DATE_GET_SECOND(self); + temp1 = new_delta(days, seconds, + DATE_GET_MICROSECOND(self), + 1); + if (temp1 == NULL) { + Py_DECREF(offset); + return -1; + } + temp2 = delta_subtract(temp1, offset); + Py_DECREF(temp1); + if (temp2 == NULL) { + Py_DECREF(offset); + return -1; + } + self->hashcode = PyObject_Hash(temp2); + Py_DECREF(temp2); + } + Py_DECREF(offset); + } + return self->hashcode; +} + +static PyObject * +datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) +{ + PyObject *clone; + PyObject *tuple; + int y = GET_YEAR(self); + int m = GET_MONTH(self); + int d = GET_DAY(self); + int hh = DATE_GET_HOUR(self); + int mm = DATE_GET_MINUTE(self); + int ss = DATE_GET_SECOND(self); + int us = DATE_GET_MICROSECOND(self); + PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO:replace", + datetime_kws, + &y, &m, &d, &hh, &mm, &ss, &us, + &tzinfo)) + return NULL; + tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo); + if (tuple == NULL) + return NULL; + clone = datetime_new(Py_TYPE(self), tuple, NULL); + Py_DECREF(tuple); + return clone; +} + +static PyObject * +datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) +{ + PyObject *result; + PyObject *offset; + PyObject *temp; + PyObject *tzinfo; + static char *keywords[] = {"tz", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:astimezone", keywords, + &PyDateTime_TZInfoType, &tzinfo)) + return NULL; + + if (!HASTZINFO(self) || self->tzinfo == Py_None) + goto NeedAware; + + /* Conversion to self's own time zone is a NOP. */ + if (self->tzinfo == tzinfo) { + Py_INCREF(self); + return (PyObject *)self; + } + + /* Convert self to UTC. */ + offset = datetime_utcoffset((PyObject *)self, NULL); + if (offset == NULL) + return NULL; + if (offset == Py_None) { + Py_DECREF(offset); + NeedAware: + PyErr_SetString(PyExc_ValueError, "astimezone() cannot be applied to " + "a naive datetime"); + return NULL; + } + + /* result = self - offset */ + result = add_datetime_timedelta(self, + (PyDateTime_Delta *)offset, -1); + Py_DECREF(offset); + if (result == NULL) + return NULL; + + /* Attach new tzinfo and let fromutc() do the rest. */ + temp = ((PyDateTime_DateTime *)result)->tzinfo; + ((PyDateTime_DateTime *)result)->tzinfo = tzinfo; + Py_INCREF(tzinfo); + Py_DECREF(temp); + + temp = result; + result = PyObject_CallMethod(tzinfo, "fromutc", "O", temp); + Py_DECREF(temp); + + return result; +} + +static PyObject * +datetime_timetuple(PyDateTime_DateTime *self) +{ + int dstflag = -1; + + if (HASTZINFO(self) && self->tzinfo != Py_None) { + PyObject * dst; + + dst = call_dst(self->tzinfo, (PyObject *)self); + if (dst == NULL) + return NULL; + + if (dst != Py_None) + dstflag = delta_bool((PyDateTime_Delta *)dst); + Py_DECREF(dst); + } + 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 * +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), + Py_None); +} + +static PyObject * +datetime_gettimetz(PyDateTime_DateTime *self) +{ + return new_time(DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self), + GET_DT_TZINFO(self)); +} + +static PyObject * +datetime_utctimetuple(PyDateTime_DateTime *self) +{ + int y, m, d, hh, mm, ss; + PyObject *tzinfo; + PyDateTime_DateTime *utcself; + + tzinfo = GET_DT_TZINFO(self); + if (tzinfo == Py_None) { + utcself = self; + Py_INCREF(utcself); + } + else { + PyObject *offset; + offset = call_utcoffset(tzinfo, (PyObject *)self); + if (offset == NULL) + return NULL; + if (offset == Py_None) { + Py_DECREF(offset); + utcself = self; + Py_INCREF(utcself); + } + else { + utcself = (PyDateTime_DateTime *)add_datetime_timedelta(self, + (PyDateTime_Delta *)offset, -1); + Py_DECREF(offset); + if (utcself == NULL) + return NULL; + } + } + y = GET_YEAR(utcself); + m = GET_MONTH(utcself); + d = GET_DAY(utcself); + hh = DATE_GET_HOUR(utcself); + mm = DATE_GET_MINUTE(utcself); + ss = DATE_GET_SECOND(utcself); + + Py_DECREF(utcself); + return build_struct_time(y, m, d, hh, mm, ss, 0); +} + +/* Pickle support, a simple use of __reduce__. */ + +/* Let basestate be the non-tzinfo data string. + * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). + * So it's a tuple in any (non-error) case. + * __getstate__ isn't exposed. + */ +static PyObject * +datetime_getstate(PyDateTime_DateTime *self) +{ + PyObject *basestate; + PyObject *result = NULL; + + basestate = PyBytes_FromStringAndSize((char *)self->data, + _PyDateTime_DATETIME_DATASIZE); + if (basestate != NULL) { + if (! HASTZINFO(self) || self->tzinfo == Py_None) + result = PyTuple_Pack(1, basestate); + else + result = PyTuple_Pack(2, basestate, self->tzinfo); + Py_DECREF(basestate); + } + return result; +} + +static PyObject * +datetime_reduce(PyDateTime_DateTime *self, PyObject *arg) +{ + return Py_BuildValue("(ON)", Py_TYPE(self), datetime_getstate(self)); +} + +static PyMethodDef datetime_methods[] = { + + /* Class methods: */ + + {"now", (PyCFunction)datetime_now, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + PyDoc_STR("[tz] -> new datetime with tz's 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_KEYWORDS | METH_CLASS, + PyDoc_STR("timestamp[, tz] -> tz's local time from POSIX timestamp.")}, + + {"utcfromtimestamp", (PyCFunction)datetime_utcfromtimestamp, + METH_VARARGS | METH_CLASS, + PyDoc_STR("timestamp -> UTC datetime from a POSIX timestamp " + "(like time.time()).")}, + + {"strptime", (PyCFunction)datetime_strptime, + METH_VARARGS | METH_CLASS, + PyDoc_STR("string, format -> new datetime parsed from a string " + "(like time.strptime()).")}, + + {"combine", (PyCFunction)datetime_combine, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + PyDoc_STR("date, time -> datetime with same date and time fields")}, + + /* Instance methods: */ + + {"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 time but with tzinfo=None.")}, + + {"timetz", (PyCFunction)datetime_gettimetz, METH_NOARGS, + PyDoc_STR("Return time object with same time and tzinfo.")}, + + {"ctime", (PyCFunction)datetime_ctime, METH_NOARGS, + PyDoc_STR("Return ctime() style string.")}, + + {"timetuple", (PyCFunction)datetime_timetuple, METH_NOARGS, + PyDoc_STR("Return time tuple, compatible with time.localtime().")}, + + {"utctimetuple", (PyCFunction)datetime_utctimetuple, METH_NOARGS, + PyDoc_STR("Return UTC time tuple, compatible with time.localtime().")}, + + {"isoformat", (PyCFunction)datetime_isoformat, METH_VARARGS | 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)datetime_utcoffset, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, + + {"tzname", (PyCFunction)datetime_tzname, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.tzname(self).")}, + + {"dst", (PyCFunction)datetime_dst, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.dst(self).")}, + + {"replace", (PyCFunction)datetime_replace, METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("Return datetime with new specified fields.")}, + + {"astimezone", (PyCFunction)datetime_astimezone, METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("tz -> convert to local time in new timezone tz\n")}, + + {"__reduce__", (PyCFunction)datetime_reduce, METH_NOARGS, + PyDoc_STR("__reduce__() -> (cls, state)")}, + + {NULL, NULL} +}; + +static char datetime_doc[] = +PyDoc_STR("datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])\n\ +\n\ +The year, month and day arguments are required. tzinfo may be None, or an\n\ +instance of a tzinfo subclass. The remaining arguments may be ints or longs.\n"); + +static PyNumberMethods datetime_as_number = { + datetime_add, /* nb_add */ + datetime_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_bool */ +}; + +static PyTypeObject PyDateTime_DateTimeType = { + PyVarObject_HEAD_INIT(NULL, 0) + "datetime.datetime", /* tp_name */ + sizeof(PyDateTime_DateTime), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)datetime_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + (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_BASETYPE, /* tp_flags */ + datetime_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 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 */ + datetime_alloc, /* tp_alloc */ + datetime_new, /* tp_new */ + 0, /* tp_free */ +}; + +/* --------------------------------------------------------------------------- + * Module methods and initialization. + */ + +static PyMethodDef module_methods[] = { + {NULL, NULL} +}; + +/* C API. Clients get at this via PyDateTime_IMPORT, defined in + * datetime.h. + */ +static PyDateTime_CAPI CAPI = { + &PyDateTime_DateType, + &PyDateTime_DateTimeType, + &PyDateTime_TimeType, + &PyDateTime_DeltaType, + &PyDateTime_TZInfoType, + new_date_ex, + new_datetime_ex, + new_time_ex, + new_delta_ex, + datetime_fromtimestamp, + date_fromtimestamp +}; + + + +static struct PyModuleDef datetimemodule = { + PyModuleDef_HEAD_INIT, + "_datetime", + "Fast implementation of the datetime type.", + -1, + module_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit__datetime(void) +{ + PyObject *m; /* a module object */ + PyObject *d; /* its dict */ + PyObject *x; + PyObject *delta; + + m = PyModule_Create(&datetimemodule); + if (m == NULL) + return NULL; + + if (PyType_Ready(&PyDateTime_DateType) < 0) + return NULL; + if (PyType_Ready(&PyDateTime_DateTimeType) < 0) + return NULL; + if (PyType_Ready(&PyDateTime_DeltaType) < 0) + return NULL; + if (PyType_Ready(&PyDateTime_TimeType) < 0) + return NULL; + if (PyType_Ready(&PyDateTime_TZInfoType) < 0) + return NULL; + if (PyType_Ready(&PyDateTime_TimeZoneType) < 0) + return NULL; + + /* timedelta values */ + d = PyDateTime_DeltaType.tp_dict; + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return NULL; + Py_DECREF(x); + + x = new_delta(-MAX_DELTA_DAYS, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return NULL; + 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 NULL; + 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 NULL; + Py_DECREF(x); + + x = new_date(MAXYEAR, 12, 31); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return NULL; + Py_DECREF(x); + + x = new_delta(1, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return NULL; + Py_DECREF(x); + + /* time values */ + d = PyDateTime_TimeType.tp_dict; + + x = new_time(0, 0, 0, 0, Py_None); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return NULL; + Py_DECREF(x); + + x = new_time(23, 59, 59, 999999, Py_None); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return NULL; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return NULL; + Py_DECREF(x); + + /* datetime values */ + d = PyDateTime_DateTimeType.tp_dict; + + x = new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return NULL; + Py_DECREF(x); + + x = new_datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999, Py_None); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return NULL; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return NULL; + Py_DECREF(x); + + /* timezone values */ + d = PyDateTime_TimeZoneType.tp_dict; + + delta = new_delta(0, 0, 0, 0); + if (delta == NULL) + return NULL; + x = new_timezone(delta, NULL); + Py_DECREF(delta); + if (x == NULL || PyDict_SetItemString(d, "utc", x) < 0) + return NULL; + PyDateTime_TimeZone_UTC = x; + + delta = new_delta(-1, 60, 0, 1); /* -23:59 */ + if (delta == NULL) + return NULL; + x = new_timezone(delta, NULL); + Py_DECREF(delta); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return NULL; + Py_DECREF(x); + + delta = new_delta(0, (23 * 60 + 59) * 60, 0, 0); /* +23:59 */ + if (delta == NULL) + return NULL; + x = new_timezone(delta, NULL); + Py_DECREF(delta); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return NULL; + Py_DECREF(x); + + /* 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_TimeType); + PyModule_AddObject(m, "time", (PyObject *) &PyDateTime_TimeType); + + Py_INCREF(&PyDateTime_DeltaType); + PyModule_AddObject(m, "timedelta", (PyObject *) &PyDateTime_DeltaType); + + Py_INCREF(&PyDateTime_TZInfoType); + PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); + + Py_INCREF(&PyDateTime_TimeZoneType); + PyModule_AddObject(m, "timezone", (PyObject *) &PyDateTime_TimeZoneType); + + x = PyCapsule_New(&CAPI, PyDateTime_CAPSULE_NAME, NULL); + if (x == NULL) + return NULL; + PyModule_AddObject(m, "datetime_CAPI", x); + + /* 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 = PyLong_FromLong(1); + us_per_ms = PyLong_FromLong(1000); + us_per_second = PyLong_FromLong(1000000); + us_per_minute = PyLong_FromLong(60000000); + seconds_per_day = PyLong_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 NULL; + + /* 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 NULL; + return m; +} + +/* --------------------------------------------------------------------------- +Some time zone algebra. For a datetime x, let + x.n = x stripped of its timezone -- its naive time. + x.o = x.utcoffset(), and assuming that doesn't raise an exception or + return None + x.d = x.dst(), and assuming that doesn't raise an exception or + return None + x.s = x's standard offset, x.o - x.d + +Now some derived rules, where k is a duration (timedelta). + +1. x.o = x.s + x.d + This follows from the definition of x.s. + +2. If x and y have the same tzinfo member, x.s = y.s. + This is actually a requirement, an assumption we need to make about + sane tzinfo classes. + +3. The naive UTC time corresponding to x is x.n - x.o. + This is again a requirement for a sane tzinfo class. + +4. (x+k).s = x.s + This follows from #2, and that datimetimetz+timedelta preserves tzinfo. + +5. (x+k).n = x.n + k + Again follows from how arithmetic is defined. + +Now we can explain tz.fromutc(x). Let's assume it's an interesting case +(meaning that the various tzinfo methods exist, and don't blow up or return +None when called). + +The function wants to return a datetime y with timezone tz, equivalent to x. +x is already in UTC. + +By #3, we want + + y.n - y.o = x.n [1] + +The algorithm starts by attaching tz to x.n, and calling that y. So +x.n = y.n at the start. Then it wants to add a duration k to y, so that [1] +becomes true; in effect, we want to solve [2] for k: + + (y+k).n - (y+k).o = x.n [2] + +By #1, this is the same as + + (y+k).n - ((y+k).s + (y+k).d) = x.n [3] + +By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start. +Substituting that into [3], + + x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving + k - (y+k).s - (y+k).d = 0; rearranging, + k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so + k = y.s - (y+k).d + +On the RHS, (y+k).d can't be computed directly, but y.s can be, and we +approximate k by ignoring the (y+k).d term at first. Note that k can't be +very large, since all offset-returning methods return a duration of magnitude +less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must +be 0, so ignoring it has no consequence then. + +In any case, the new value is + + z = y + y.s [4] + +It's helpful to step back at look at [4] from a higher level: it's simply +mapping from UTC to tz's standard time. + +At this point, if + + z.n - z.o = x.n [5] + +we have an equivalent time, and are almost done. The insecurity here is +at the start of daylight time. Picture US Eastern for concreteness. The wall +time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good +sense then. The docs ask that an Eastern tzinfo class consider such a time to +be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST +on the day DST starts. We want to return the 1:MM EST spelling because that's +the only spelling that makes sense on the local wall clock. + +In fact, if [5] holds at this point, we do have the standard-time spelling, +but that takes a bit of proof. We first prove a stronger result. What's the +difference between the LHS and RHS of [5]? Let + + diff = x.n - (z.n - z.o) [6] + +Now + z.n = by [4] + (y + y.s).n = by #5 + y.n + y.s = since y.n = x.n + x.n + y.s = since z and y are have the same tzinfo member, + y.s = z.s by #2 + x.n + z.s + +Plugging that back into [6] gives + + diff = + x.n - ((x.n + z.s) - z.o) = expanding + x.n - x.n - z.s + z.o = cancelling + - z.s + z.o = by #2 + z.d + +So diff = z.d. + +If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time +spelling we wanted in the endcase described above. We're done. Contrarily, +if z.d = 0, then we have a UTC equivalent, and are also done. + +If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to +add to z (in effect, z is in tz's standard time, and we need to shift the +local clock into tz's daylight time). + +Let + + z' = z + z.d = z + diff [7] + +and we can again ask whether + + z'.n - z'.o = x.n [8] + +If so, we're done. If not, the tzinfo class is insane, according to the +assumptions we've made. This also requires a bit of proof. As before, let's +compute the difference between the LHS and RHS of [8] (and skipping some of +the justifications for the kinds of substitutions we've done several times +already): + + diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7] + x.n - (z.n + diff - z'.o) = replacing diff via [6] + x.n - (z.n + x.n - (z.n - z.o) - z'.o) = + x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n + - z.n + z.n - z.o + z'.o = cancel z.n + - z.o + z'.o = #1 twice + -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo + z'.d - z.d + +So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal, +we've found the UTC-equivalent so are done. In fact, we stop with [7] and +return z', not bothering to compute z'.d. + +How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by +a dst() offset, and starting *from* a time already in DST (we know z.d != 0), +would have to change the result dst() returns: we start in DST, and moving +a little further into it takes us out of DST. + +There isn't a sane case where this can happen. The closest it gets is at +the end of DST, where there's an hour in UTC with no spelling in a hybrid +tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During +that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM +UTC) because the docs insist on that, but 0:MM is taken as being in daylight +time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local +clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in +standard time. Since that's what the local clock *does*, we want to map both +UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous +in local time, but so it goes -- it's the way the local clock works. + +When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0, +so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going. +z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8] +(correctly) concludes that z' is not UTC-equivalent to x. + +Because we know z.d said z was in daylight time (else [5] would have held and +we would have stopped then), and we know z.d != z'.d (else [8] would have held +and we would have stopped then), and there are only 2 possible values dst() can +return in Eastern, it follows that z'.d must be 0 (which it is in the example, +but the reasoning doesn't depend on the example -- it depends on there being +two possible dst() outcomes, one zero and the other non-zero). Therefore +z' must be in standard time, and is the spelling we want in this case. + +Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is +concerned (because it takes z' as being in standard time rather than the +daylight time we intend here), but returning it gives the real-life "local +clock repeats an hour" behavior when mapping the "unspellable" UTC hour into +tz. + +When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with +the 1:MM standard time spelling we want. + +So how can this break? One of the assumptions must be violated. Two +possibilities: + +1) [2] effectively says that y.s is invariant across all y belong to a given + time zone. This isn't true if, for political reasons or continental drift, + a region decides to change its base offset from UTC. + +2) There may be versions of "double daylight" time where the tail end of + the analysis gives up a step too early. I haven't thought about that + enough to say. + +In any case, it's clear that the default fromutc() is strong enough to handle +"almost all" time zones: so long as the standard offset is invariant, it +doesn't matter if daylight time transition points change from year to year, or +if daylight time is skipped in some years; it doesn't matter how large or +small dst() may get within its bounds; and it doesn't even matter if some +perverse time zone returns a negative dst()). So a breaking case must be +pretty bizarre, and a tzinfo subclass can override fromutc() if it is. +--------------------------------------------------------------------------- */ diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c deleted file mode 100644 index bd25d1e..0000000 --- a/Modules/datetimemodule.c +++ /dev/null @@ -1,5488 +0,0 @@ -/* 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 "timefuncs.h" - -/* Differentiate between building the core module and building extension - * modules. - */ -#ifndef Py_BUILD_CORE -#define Py_BUILD_CORE -#endif -#include "datetime.h" -#undef Py_BUILD_CORE - -/* 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 -#define MAXORDINAL 3652059 /* date(9999,12,31).toordinal() */ - -/* 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)) - -/* p is a pointer to a time or a datetime object; HASTZINFO(p) returns - * p->hastzinfo. - */ -#define HASTZINFO(p) (((_PyDateTime_BaseTZInfo *)(p))->hastzinfo) -#define GET_TIME_TZINFO(p) (HASTZINFO(p) ? \ - ((PyDateTime_Time *)(p))->tzinfo : Py_None) -#define GET_DT_TZINFO(p) (HASTZINFO(p) ? \ - ((PyDateTime_DateTime *)(p))->tzinfo : Py_None) -/* M is a char or int claiming to be a valid month. The macro is equivalent - * to the two-sided Python test - * 1 <= M <= 12 - */ -#define MONTH_IS_SANE(M) ((unsigned int)(M) - 1 < 12) - -/* Forward declarations. */ -static PyTypeObject PyDateTime_DateType; -static PyTypeObject PyDateTime_DateTimeType; -static PyTypeObject PyDateTime_DeltaType; -static PyTypeObject PyDateTime_TimeType; -static PyTypeObject PyDateTime_TZInfoType; -static PyTypeObject PyDateTime_TimeZoneType; - -/* --------------------------------------------------------------------------- - * 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; -} - -/* Round a double to the nearest long. |x| must be small enough to fit - * in a C long; this is not checked. - */ -static long -round_to_long(double x) -{ - if (x >= 0.0) - x = floor(x + 0.5); - else - x = ceil(x - 0.5); - return (long)x; -} - -/* Nearest integer to m / n for integers m and n. Half-integer results - * are rounded to even. - */ -static PyObject * -divide_nearest(PyObject *m, PyObject *n) -{ - PyObject *result; - PyObject *temp; - - temp = _PyLong_DivmodNear(m, n); - if (temp == NULL) - return NULL; - result = PyTuple_GET_ITEM(temp, 0); - Py_INCREF(result); - Py_DECREF(temp); - - return result; -} - -/* --------------------------------------------------------------------------- - * 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, MAX_DELTA_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 int -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; - if (ordinal < 1 || ordinal > MAXORDINAL) { - goto error; - } else { - ord_to_ymd(ordinal, y, m, d); - return 0; - } - } - } - assert(*m > 0); - assert(*d > 0); - if (MINYEAR <= *y && *y <= MAXYEAR) - return 0; - error: - PyErr_SetString(PyExc_OverflowError, - "date value out of range"); - return -1; - -} - -/* 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) -{ - return normalize_y_m_d(year, month, day); -} - -/* 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); -} - -/* --------------------------------------------------------------------------- - * Basic object allocation: tp_alloc implementations. These allocate - * Python objects of the right size and type, and do the Python object- - * initialization bit. If there's not enough memory, they return NULL after - * setting MemoryError. All data members remain uninitialized trash. - * - * We abuse the tp_alloc "nitems" argument to communicate whether a tzinfo - * member is needed. This is ugly, imprecise, and possibly insecure. - * tp_basicsize for the time and datetime types is set to the size of the - * struct that has room for the tzinfo member, so subclasses in Python will - * allocate enough space for a tzinfo member whether or not one is actually - * needed. That's the "ugly and imprecise" parts. The "possibly insecure" - * part is that PyType_GenericAlloc() (which subclasses in Python end up - * using) just happens today to effectively ignore the nitems argument - * when tp_itemsize is 0, which it is for these type objects. If that - * changes, perhaps the callers of tp_alloc slots in this file should - * be changed to force a 0 nitems argument unless the type being allocated - * is a base type implemented in this file (so that tp_alloc is time_alloc - * or datetime_alloc below, which know about the nitems abuse). - */ - -static PyObject * -time_alloc(PyTypeObject *type, Py_ssize_t aware) -{ - PyObject *self; - - self = (PyObject *) - PyObject_MALLOC(aware ? - sizeof(PyDateTime_Time) : - sizeof(_PyDateTime_BaseTime)); - if (self == NULL) - return (PyObject *)PyErr_NoMemory(); - PyObject_INIT(self, type); - return self; -} - -static PyObject * -datetime_alloc(PyTypeObject *type, Py_ssize_t aware) -{ - PyObject *self; - - self = (PyObject *) - PyObject_MALLOC(aware ? - sizeof(PyDateTime_DateTime) : - sizeof(_PyDateTime_BaseDateTime)); - if (self == NULL) - return (PyObject *)PyErr_NoMemory(); - PyObject_INIT(self, type); - return self; -} - -/* --------------------------------------------------------------------------- - * Helpers for setting object fields. These work on pointers to the - * appropriate base class. - */ - -/* For date and datetime. */ -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); -} - -/* --------------------------------------------------------------------------- - * Create various objects, mostly without range checking. - */ - -/* Create a date instance with no range checking. */ -static PyObject * -new_date_ex(int year, int month, int day, PyTypeObject *type) -{ - PyDateTime_Date *self; - - self = (PyDateTime_Date *) (type->tp_alloc(type, 0)); - if (self != NULL) - set_date_fields(self, year, month, day); - return (PyObject *) self; -} - -#define new_date(year, month, day) \ - new_date_ex(year, month, day, &PyDateTime_DateType) - -/* Create a datetime instance with no range checking. */ -static PyObject * -new_datetime_ex(int year, int month, int day, int hour, int minute, - int second, int usecond, PyObject *tzinfo, PyTypeObject *type) -{ - PyDateTime_DateTime *self; - char aware = tzinfo != Py_None; - - self = (PyDateTime_DateTime *) (type->tp_alloc(type, aware)); - if (self != NULL) { - self->hastzinfo = aware; - set_date_fields((PyDateTime_Date *)self, year, month, day); - DATE_SET_HOUR(self, hour); - DATE_SET_MINUTE(self, minute); - DATE_SET_SECOND(self, second); - DATE_SET_MICROSECOND(self, usecond); - if (aware) { - Py_INCREF(tzinfo); - self->tzinfo = tzinfo; - } - } - return (PyObject *)self; -} - -#define new_datetime(y, m, d, hh, mm, ss, us, tzinfo) \ - new_datetime_ex(y, m, d, hh, mm, ss, us, tzinfo, \ - &PyDateTime_DateTimeType) - -/* Create a time instance with no range checking. */ -static PyObject * -new_time_ex(int hour, int minute, int second, int usecond, - PyObject *tzinfo, PyTypeObject *type) -{ - PyDateTime_Time *self; - char aware = tzinfo != Py_None; - - self = (PyDateTime_Time *) (type->tp_alloc(type, aware)); - if (self != NULL) { - self->hastzinfo = aware; - self->hashcode = -1; - TIME_SET_HOUR(self, hour); - TIME_SET_MINUTE(self, minute); - TIME_SET_SECOND(self, second); - TIME_SET_MICROSECOND(self, usecond); - if (aware) { - Py_INCREF(tzinfo); - self->tzinfo = tzinfo; - } - } - return (PyObject *)self; -} - -#define new_time(hh, mm, ss, us, tzinfo) \ - new_time_ex(hh, mm, ss, us, tzinfo, &PyDateTime_TimeType) - -/* 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_ex(int days, int seconds, int microseconds, int normalize, - PyTypeObject *type) -{ - 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 = (PyDateTime_Delta *) (type->tp_alloc(type, 0)); - if (self != NULL) { - self->hashcode = -1; - SET_TD_DAYS(self, days); - SET_TD_SECONDS(self, seconds); - SET_TD_MICROSECONDS(self, microseconds); - } - return (PyObject *) self; -} - -#define new_delta(d, s, us, normalize) \ - new_delta_ex(d, s, us, normalize, &PyDateTime_DeltaType) - - -typedef struct -{ - PyObject_HEAD - PyObject *offset; - PyObject *name; -} PyDateTime_TimeZone; - -PyObject *PyDateTime_TimeZone_UTC; - -/* Create new timezone instance checking offset range. This - function does not check the name argument. Caller must assure - that offset is a timedelta instance and name is either NULL - or a unicode object. */ -static PyObject * -new_timezone(PyObject *offset, PyObject *name) -{ - PyDateTime_TimeZone *self; - PyTypeObject *type = &PyDateTime_TimeZoneType; - - assert(offset != NULL); - assert(PyDelta_Check(offset)); - assert(name == NULL || PyUnicode_Check(name)); - - if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) { - PyErr_Format(PyExc_ValueError, "offset must be a timedelta" - " representing a whole number of minutes"); - return NULL; - } - if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) || - GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) { - PyErr_Format(PyExc_ValueError, "offset must be a timedelta" - " strictly between -timedelta(hours=24) and" - " timedelta(hours=24)."); - return NULL; - } - - self = (PyDateTime_TimeZone *)(type->tp_alloc(type, 0)); - if (self == NULL) { - return NULL; - } - Py_INCREF(offset); - self->offset = offset; - Py_XINCREF(name); - self->name = name; - return (PyObject *)self; -} - -/* --------------------------------------------------------------------------- - * tzinfo helpers. - */ - -/* 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'", - Py_TYPE(p)->tp_name); - return -1; -} - -/* 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 (PyDateTime_Check(self) && HASTZINFO(self)) - tzinfo = ((PyDateTime_DateTime *)self)->tzinfo; - else if (PyTime_Check(self) && HASTZINFO(self)) - tzinfo = ((PyDateTime_Time *)self)->tzinfo; - - return tzinfo; -} - -/* Call getattr(tzinfo, name)(tzinfoarg), and check the result. tzinfo must - * be an instance of the tzinfo class. If the method returns None, this - * returns None. If the method doesn't return None or timedelta, TypeError is - * raised and this returns NULL. If it returns a timedelta and the value is - * out of range or isn't a whole number of minutes, ValueError is raised and - * this returns NULL. Else result is returned. - */ -static PyObject * -call_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg) -{ - PyObject *offset; - - assert(tzinfo != NULL); - assert(PyTZInfo_Check(tzinfo) || tzinfo == Py_None); - assert(tzinfoarg != NULL); - - if (tzinfo == Py_None) - Py_RETURN_NONE; - offset = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg); - if (offset == Py_None || offset == NULL) - return offset; - if (PyDelta_Check(offset)) { - if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) { - Py_DECREF(offset); - PyErr_Format(PyExc_ValueError, "offset must be a timedelta" - " representing a whole number of minutes"); - return NULL; - } - if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) || - GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) { - Py_DECREF(offset); - PyErr_Format(PyExc_ValueError, "offset must be a timedelta" - " strictly between -timedelta(hours=24) and" - " timedelta(hours=24)."); - return NULL; - } - } - else { - Py_DECREF(offset); - PyErr_Format(PyExc_TypeError, - "tzinfo.%s() must return None or " - "timedelta, not '%.200s'", - name, Py_TYPE(offset)->tp_name); - return NULL; - } - - return offset; -} - -/* 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 None or timedelta, TypeError is raised and this returns -1. - * If utcoffset() returns an invalid timedelta (out of range, or not a whole - * # of minutes), ValueError is raised and this returns -1. Else *none is - * set to 0 and the offset is returned (as int # of minutes east of UTC). - */ -static PyObject * -call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg) -{ - return call_tzinfo_method(tzinfo, "utcoffset", tzinfoarg); -} - -/* 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 None or timedelta, TypeError is raised and this - * returns -1. If dst() returns an invalid timedelta for a UTC offset, - * ValueError is raised and this returns -1. Else *none is set to 0 and - * the offset is returned (as an int # of minutes east of UTC). - */ -static PyObject * -call_dst(PyObject *tzinfo, PyObject *tzinfoarg) -{ - return call_tzinfo_method(tzinfo, "dst", tzinfoarg); -} - -/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be - * an instance of the tzinfo class or None. If tzinfo isn't None, and - * tzname() doesn't return None or a string, TypeError is raised and this - * returns NULL. If the result is a string, we ensure it is a Unicode - * string. - */ -static PyObject * -call_tzname(PyObject *tzinfo, PyObject *tzinfoarg) -{ - PyObject *result; - - assert(tzinfo != NULL); - assert(check_tzinfo_subclass(tzinfo) >= 0); - assert(tzinfoarg != NULL); - - if (tzinfo == Py_None) - Py_RETURN_NONE; - - result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg); - - if (result == NULL || result == Py_None) - return result; - - if (!PyUnicode_Check(result)) { - PyErr_Format(PyExc_TypeError, "tzinfo.tzname() must " - "return None or a string, not '%s'", - Py_TYPE(result)->tp_name); - Py_DECREF(result); - result = NULL; - } - - return result; -} - -/* 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(PyUnicode_Check(repr)); - assert(tzinfo); - if (tzinfo == Py_None) - return repr; - /* Get rid of the trailing ')'. */ - assert(PyUnicode_AS_UNICODE(repr)[PyUnicode_GET_SIZE(repr)-1] == ')'); - temp = PyUnicode_FromUnicode(PyUnicode_AS_UNICODE(repr), - PyUnicode_GET_SIZE(repr) - 1); - Py_DECREF(repr); - if (temp == NULL) - return NULL; - repr = PyUnicode_FromFormat("%U, tzinfo=%R)", temp, tzinfo); - Py_DECREF(temp); - return repr; -} - -/* --------------------------------------------------------------------------- - * String format helpers. - */ - -static PyObject * -format_ctime(PyDateTime_Date *date, int hours, int minutes, int seconds) -{ - static const char *DayNames[] = { - "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" - }; - static const char *MonthNames[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - - int wday = weekday(GET_YEAR(date), GET_MONTH(date), GET_DAY(date)); - - return PyUnicode_FromFormat("%s %s %2d %02d:%02d:%02d %04d", - DayNames[wday], MonthNames[GET_MONTH(date)-1], - GET_DAY(date), hours, minutes, seconds, - GET_YEAR(date)); -} - -static PyObject *delta_negative(PyDateTime_Delta *self); - -/* 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, size_t buflen, const char *sep, - PyObject *tzinfo, PyObject *tzinfoarg) -{ - PyObject *offset; - int hours, minutes, seconds; - char sign; - - assert(buflen >= 1); - - offset = call_utcoffset(tzinfo, tzinfoarg); - if (offset == NULL) - return -1; - if (offset == Py_None) { - Py_DECREF(offset); - *buf = '\0'; - return 0; - } - /* Offset is normalized, so it is negative if days < 0 */ - if (GET_TD_DAYS(offset) < 0) { - PyObject *temp = offset; - sign = '-'; - offset = delta_negative((PyDateTime_Delta *)offset); - Py_DECREF(temp); - if (offset == NULL) - return -1; - } - else { - sign = '+'; - } - /* Offset is not negative here. */ - seconds = GET_TD_SECONDS(offset); - Py_DECREF(offset); - minutes = divmod(seconds, 60, &seconds); - hours = divmod(minutes, 60, &minutes); - assert(seconds == 0); - /* XXX ignore sub-minute data, curently not allowed. */ - PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes); - - return 0; -} - -static PyObject * -make_Zreplacement(PyObject *object, PyObject *tzinfoarg) -{ - PyObject *temp; - PyObject *tzinfo = get_tzinfo_member(object); - PyObject *Zreplacement = PyUnicode_FromStringAndSize(NULL, 0); - if (Zreplacement == NULL) - return NULL; - if (tzinfo == Py_None || tzinfo == NULL) - return Zreplacement; - - assert(tzinfoarg != NULL); - temp = call_tzname(tzinfo, tzinfoarg); - if (temp == NULL) - goto Error; - if (temp == Py_None) { - Py_DECREF(temp); - return Zreplacement; - } - - assert(PyUnicode_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) - return NULL; - if (!PyUnicode_Check(Zreplacement)) { - PyErr_SetString(PyExc_TypeError, - "tzname.replace() did not return a string"); - goto Error; - } - return Zreplacement; - - Error: - Py_DECREF(Zreplacement); - return NULL; -} - -static PyObject * -make_freplacement(PyObject *object) -{ - char freplacement[64]; - if (PyTime_Check(object)) - sprintf(freplacement, "%06d", TIME_GET_MICROSECOND(object)); - else if (PyDateTime_Check(object)) - sprintf(freplacement, "%06d", DATE_GET_MICROSECOND(object)); - else - sprintf(freplacement, "%06d", 0); - - return PyBytes_FromStringAndSize(freplacement, strlen(freplacement)); -} - -/* 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, %Z and %f format codes via a - * preprocessing step on the format string. - * tzinfoarg is the argument to pass to the object's tzinfo method, if - * needed. - */ -static PyObject * -wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, - PyObject *tzinfoarg) -{ - PyObject *result = NULL; /* guilty until proved innocent */ - - PyObject *zreplacement = NULL; /* py string, replacement for %z */ - PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ - PyObject *freplacement = NULL; /* py string, replacement for %f */ - - const char *pin; /* pointer to next char in input format */ - Py_ssize_t flen; /* length of 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 */ - size_t totalnew; /* number bytes total in output format buffer, - exclusive of trailing \0 */ - size_t usednew; /* number bytes used so far in output format buffer */ - - const char *ptoappend; /* ptr to string to append to output buffer */ - Py_ssize_t ntoappend; /* # of bytes to append to output buffer */ - - assert(object && format && timetuple); - assert(PyUnicode_Check(format)); - /* Convert the input format to a C string and size */ - pin = _PyUnicode_AsStringAndSize(format, &flen); - if (!pin) - return NULL; - - /* Give up if the year is before 1900. - * Python strftime() plays games with the year, and different - * games depending on whether envar PYTHON2K is set. This makes - * years before 1900 a nightmare, even if the platform strftime - * supports them (and not all do). - * We could get a lot farther here by avoiding Python's strftime - * wrapper and calling the C strftime() directly, but that isn't - * an option in the Python implementation of this module. - */ - { - long year; - PyObject *pyyear = PySequence_GetItem(timetuple, 0); - if (pyyear == NULL) return NULL; - assert(PyLong_Check(pyyear)); - year = PyLong_AsLong(pyyear); - Py_DECREF(pyyear); - if (year < 1900) { - PyErr_Format(PyExc_ValueError, "year=%ld is before " - "1900; the datetime strftime() " - "methods require year >= 1900", - year); - return NULL; - } - } - - /* Scan the input format, looking for %z/%Z/%f escapes, building - * a new format. Since computing the replacements for those codes - * is expensive, don't unless they're actually used. - */ - if (flen > INT_MAX - 1) { - PyErr_NoMemory(); - goto Done; - } - - totalnew = flen + 1; /* realistic if no %z/%Z */ - newfmt = PyBytes_FromStringAndSize(NULL, totalnew); - if (newfmt == NULL) goto Done; - pnew = PyBytes_AsString(newfmt); - usednew = 0; - - while ((ch = *pin++) != '\0') { - if (ch != '%') { - ptoappend = pin - 1; - 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 */ - char buf[100]; - PyObject *tzinfo = get_tzinfo_member(object); - zreplacement = PyBytes_FromStringAndSize("", 0); - if (zreplacement == NULL) goto Done; - if (tzinfo != Py_None && tzinfo != NULL) { - assert(tzinfoarg != NULL); - if (format_utcoffset(buf, - sizeof(buf), - "", - tzinfo, - tzinfoarg) < 0) - goto Done; - Py_DECREF(zreplacement); - zreplacement = - PyBytes_FromStringAndSize(buf, - strlen(buf)); - if (zreplacement == NULL) - goto Done; - } - } - assert(zreplacement != NULL); - ptoappend = PyBytes_AS_STRING(zreplacement); - ntoappend = PyBytes_GET_SIZE(zreplacement); - } - else if (ch == 'Z') { - /* format tzname */ - if (Zreplacement == NULL) { - Zreplacement = make_Zreplacement(object, - tzinfoarg); - if (Zreplacement == NULL) - goto Done; - } - assert(Zreplacement != NULL); - assert(PyUnicode_Check(Zreplacement)); - ptoappend = _PyUnicode_AsStringAndSize(Zreplacement, - &ntoappend); - ntoappend = Py_SIZE(Zreplacement); - } - else if (ch == 'f') { - /* format microseconds */ - if (freplacement == NULL) { - freplacement = make_freplacement(object); - if (freplacement == NULL) - goto Done; - } - assert(freplacement != NULL); - assert(PyBytes_Check(freplacement)); - ptoappend = PyBytes_AS_STRING(freplacement); - ntoappend = PyBytes_GET_SIZE(freplacement); - } - else { - /* percent followed by neither z nor Z */ - ptoappend = pin - 2; - ntoappend = 2; - } - - /* Append the ntoappend chars starting at ptoappend to - * the new format. - */ - if (ntoappend == 0) - continue; - assert(ptoappend != NULL); - assert(ntoappend > 0); - while (usednew + ntoappend > totalnew) { - size_t bigger = totalnew << 1; - if ((bigger >> 1) != totalnew) { /* overflow */ - PyErr_NoMemory(); - goto Done; - } - if (_PyBytes_Resize(&newfmt, bigger) < 0) - goto Done; - totalnew = bigger; - pnew = PyBytes_AsString(newfmt) + usednew; - } - memcpy(pnew, ptoappend, ntoappend); - pnew += ntoappend; - usednew += ntoappend; - assert(usednew <= totalnew); - } /* end while() */ - - if (_PyBytes_Resize(&newfmt, usednew) < 0) - goto Done; - { - PyObject *format; - PyObject *time = PyImport_ImportModuleNoBlock("time"); - if (time == NULL) - goto Done; - format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt)); - if (format != NULL) { - result = PyObject_CallMethod(time, "strftime", "OO", - format, timetuple, NULL); - Py_DECREF(format); - } - Py_DECREF(time); - } - Done: - Py_XDECREF(freplacement); - Py_XDECREF(zreplacement); - Py_XDECREF(Zreplacement); - Py_XDECREF(newfmt); - return result; -} - -/* --------------------------------------------------------------------------- - * 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(void) -{ - PyObject *result = NULL; - PyObject *time = PyImport_ImportModuleNoBlock("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_ImportModuleNoBlock("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 various reasons, we need to use tp_richcompare instead of tp_reserved. - * 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; -} - -/* Raises a "can't compare" TypeError and returns NULL. */ -static PyObject * -cmperror(PyObject *a, PyObject *b) -{ - PyErr_Format(PyExc_TypeError, - "can't compare %s to %s", - Py_TYPE(a)->tp_name, Py_TYPE(b)->tp_name); - return NULL; -} - -/* --------------------------------------------------------------------------- - * 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 */ - -/* --------------------------------------------------------------------------- - * 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 = PyLong_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 = PyLong_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 = PyLong_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_ex(PyObject *pyus, PyTypeObject *type) -{ - int us; - int s; - int d; - long temp; - - 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; - temp = PyLong_AsLong(num); - num = NULL; - if (temp == -1 && PyErr_Occurred()) - goto Done; - assert(0 <= temp && temp < 1000000); - us = (int)temp; - 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; - temp = PyLong_AsLong(num); - num = NULL; - if (temp == -1 && PyErr_Occurred()) - goto Done; - assert(0 <= temp && temp < 24*3600); - s = (int)temp; - - 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); - temp = PyLong_AsLong(num); - if (temp == -1 && PyErr_Occurred()) - goto Done; - d = (int)temp; - if ((long)d != temp) { - PyErr_SetString(PyExc_OverflowError, "normalized days too " - "large to fit in a C int"); - goto Done; - } - result = new_delta_ex(d, s, us, 0, type); - -Done: - Py_XDECREF(tuple); - Py_XDECREF(num); - return result; -} - -#define microseconds_to_delta(pymicros) \ - microseconds_to_delta_ex(pymicros, &PyDateTime_DeltaType) - -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 * -multiply_float_timedelta(PyObject *floatobj, PyDateTime_Delta *delta) -{ - PyObject *result = NULL; - PyObject *pyus_in = NULL, *temp, *pyus_out; - PyObject *ratio = NULL; - - pyus_in = delta_to_microseconds(delta); - if (pyus_in == NULL) - return NULL; - ratio = PyObject_CallMethod(floatobj, "as_integer_ratio", NULL); - if (ratio == NULL) - goto error; - temp = PyNumber_Multiply(pyus_in, PyTuple_GET_ITEM(ratio, 0)); - Py_DECREF(pyus_in); - pyus_in = NULL; - if (temp == NULL) - goto error; - pyus_out = divide_nearest(temp, PyTuple_GET_ITEM(ratio, 1)); - Py_DECREF(temp); - if (pyus_out == NULL) - goto error; - result = microseconds_to_delta(pyus_out); - Py_DECREF(pyus_out); - error: - Py_XDECREF(pyus_in); - Py_XDECREF(ratio); - - 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 * -divide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) -{ - PyObject *pyus_left; - PyObject *pyus_right; - PyObject *result; - - pyus_left = delta_to_microseconds(left); - if (pyus_left == NULL) - return NULL; - - pyus_right = delta_to_microseconds(right); - if (pyus_right == NULL) { - Py_DECREF(pyus_left); - return NULL; - } - - result = PyNumber_FloorDivide(pyus_left, pyus_right); - Py_DECREF(pyus_left); - Py_DECREF(pyus_right); - return result; -} - -static PyObject * -truedivide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) -{ - PyObject *pyus_left; - PyObject *pyus_right; - PyObject *result; - - pyus_left = delta_to_microseconds(left); - if (pyus_left == NULL) - return NULL; - - pyus_right = delta_to_microseconds(right); - if (pyus_right == NULL) { - Py_DECREF(pyus_left); - return NULL; - } - - result = PyNumber_TrueDivide(pyus_left, pyus_right); - Py_DECREF(pyus_left); - Py_DECREF(pyus_right); - return result; -} - -static PyObject * -truedivide_timedelta_float(PyDateTime_Delta *delta, PyObject *f) -{ - PyObject *result = NULL; - PyObject *pyus_in = NULL, *temp, *pyus_out; - PyObject *ratio = NULL; - - pyus_in = delta_to_microseconds(delta); - if (pyus_in == NULL) - return NULL; - ratio = PyObject_CallMethod(f, "as_integer_ratio", NULL); - if (ratio == NULL) - goto error; - temp = PyNumber_Multiply(pyus_in, PyTuple_GET_ITEM(ratio, 1)); - Py_DECREF(pyus_in); - pyus_in = NULL; - if (temp == NULL) - goto error; - pyus_out = divide_nearest(temp, PyTuple_GET_ITEM(ratio, 0)); - Py_DECREF(temp); - if (pyus_out == NULL) - goto error; - result = microseconds_to_delta(pyus_out); - Py_DECREF(pyus_out); - error: - Py_XDECREF(pyus_in); - Py_XDECREF(ratio); - - return result; -} - -static PyObject * -truedivide_timedelta_int(PyDateTime_Delta *delta, PyObject *i) -{ - PyObject *result; - PyObject *pyus_in, *pyus_out; - pyus_in = delta_to_microseconds(delta); - if (pyus_in == NULL) - return NULL; - pyus_out = divide_nearest(pyus_in, i); - 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; -} - -static int -delta_cmp(PyObject *self, PyObject *other) -{ - int 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; -} - -static PyObject * -delta_richcompare(PyObject *self, PyObject *other, int op) -{ - if (PyDelta_Check(other)) { - int diff = delta_cmp(self, other); - return diff_to_bool(diff, op); - } - else { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } -} - -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 (PyLong_Check(right)) - result = multiply_int_timedelta(right, - (PyDateTime_Delta *) left); - else if (PyFloat_Check(right)) - result = multiply_float_timedelta(right, - (PyDateTime_Delta *) left); - } - else if (PyLong_Check(left)) - result = multiply_int_timedelta(left, - (PyDateTime_Delta *) right); - else if (PyFloat_Check(left)) - result = multiply_float_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 (PyLong_Check(right)) - result = divide_timedelta_int( - (PyDateTime_Delta *)left, - right); - else if (PyDelta_Check(right)) - result = divide_timedelta_timedelta( - (PyDateTime_Delta *)left, - (PyDateTime_Delta *)right); - } - - if (result == Py_NotImplemented) - Py_INCREF(result); - return result; -} - -static PyObject * -delta_truedivide(PyObject *left, PyObject *right) -{ - PyObject *result = Py_NotImplemented; - - if (PyDelta_Check(left)) { - if (PyDelta_Check(right)) - result = truedivide_timedelta_timedelta( - (PyDateTime_Delta *)left, - (PyDateTime_Delta *)right); - else if (PyFloat_Check(right)) - result = truedivide_timedelta_float( - (PyDateTime_Delta *)left, right); - else if (PyLong_Check(right)) - result = truedivide_timedelta_int( - (PyDateTime_Delta *)left, right); - } - - if (result == Py_NotImplemented) - Py_INCREF(result); - return result; -} - -static PyObject * -delta_remainder(PyObject *left, PyObject *right) -{ - PyObject *pyus_left; - PyObject *pyus_right; - PyObject *pyus_remainder; - PyObject *remainder; - - if (!PyDelta_Check(left) || !PyDelta_Check(right)) { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - pyus_left = delta_to_microseconds((PyDateTime_Delta *)left); - if (pyus_left == NULL) - return NULL; - - pyus_right = delta_to_microseconds((PyDateTime_Delta *)right); - if (pyus_right == NULL) { - Py_DECREF(pyus_left); - return NULL; - } - - pyus_remainder = PyNumber_Remainder(pyus_left, pyus_right); - Py_DECREF(pyus_left); - Py_DECREF(pyus_right); - if (pyus_remainder == NULL) - return NULL; - - remainder = microseconds_to_delta(pyus_remainder); - Py_DECREF(pyus_remainder); - if (remainder == NULL) - return NULL; - - return remainder; -} - -static PyObject * -delta_divmod(PyObject *left, PyObject *right) -{ - PyObject *pyus_left; - PyObject *pyus_right; - PyObject *divmod; - PyObject *delta; - PyObject *result; - - if (!PyDelta_Check(left) || !PyDelta_Check(right)) { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - pyus_left = delta_to_microseconds((PyDateTime_Delta *)left); - if (pyus_left == NULL) - return NULL; - - pyus_right = delta_to_microseconds((PyDateTime_Delta *)right); - if (pyus_right == NULL) { - Py_DECREF(pyus_left); - return NULL; - } - - divmod = PyNumber_Divmod(pyus_left, pyus_right); - Py_DECREF(pyus_left); - Py_DECREF(pyus_right); - if (divmod == NULL) - return NULL; - - assert(PyTuple_Size(divmod) == 2); - delta = microseconds_to_delta(PyTuple_GET_ITEM(divmod, 1)); - if (delta == NULL) { - Py_DECREF(divmod); - return NULL; - } - result = PyTuple_Pack(2, PyTuple_GET_ITEM(divmod, 0), delta); - Py_DECREF(delta); - Py_DECREF(divmod); - 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 (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(PyLong_Check(factor)); - 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, Py_TYPE(num)->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 = PyLong_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 = PyLong_FromLong(round_to_long(leftover_us)); - if (temp == NULL) { - Py_DECREF(x); - goto Done; - } - y = PyNumber_Add(x, temp); - Py_DECREF(temp); - CLEANUP; - } - - self = microseconds_to_delta_ex(x, type); - Py_DECREF(x); -Done: - return self; - -#undef CLEANUP -} - -static int -delta_bool(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 PyUnicode_FromFormat("%s(%d, %d, %d)", - Py_TYPE(self)->tp_name, - GET_TD_DAYS(self), - GET_TD_SECONDS(self), - GET_TD_MICROSECONDS(self)); - if (GET_TD_SECONDS(self) != 0) - return PyUnicode_FromFormat("%s(%d, %d)", - Py_TYPE(self)->tp_name, - GET_TD_DAYS(self), - GET_TD_SECONDS(self)); - - return PyUnicode_FromFormat("%s(%d)", - Py_TYPE(self)->tp_name, - GET_TD_DAYS(self)); -} - -static PyObject * -delta_str(PyDateTime_Delta *self) -{ - int us = GET_TD_MICROSECONDS(self); - int seconds = GET_TD_SECONDS(self); - int minutes = divmod(seconds, 60, &seconds); - int hours = divmod(minutes, 60, &minutes); - int days = GET_TD_DAYS(self); - - if (days) { - if (us) - return PyUnicode_FromFormat("%d day%s, %d:%02d:%02d.%06d", - days, (days == 1 || days == -1) ? "" : "s", - hours, minutes, seconds, us); - else - return PyUnicode_FromFormat("%d day%s, %d:%02d:%02d", - days, (days == 1 || days == -1) ? "" : "s", - hours, minutes, seconds); - } else { - if (us) - return PyUnicode_FromFormat("%d:%02d:%02d.%06d", - hours, minutes, seconds, us); - else - return PyUnicode_FromFormat("%d:%02d:%02d", - hours, minutes, seconds); - } - -} - -/* Pickle support, a simple use of __reduce__. */ - -/* __getstate__ isn't exposed */ -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_total_seconds(PyObject *self) -{ - PyObject *total_seconds; - PyObject *total_microseconds; - PyObject *one_million; - - total_microseconds = delta_to_microseconds((PyDateTime_Delta *)self); - if (total_microseconds == NULL) - return NULL; - - one_million = PyLong_FromLong(1000000L); - if (one_million == NULL) { - Py_DECREF(total_microseconds); - return NULL; - } - - total_seconds = PyNumber_TrueDivide(total_microseconds, one_million); - - Py_DECREF(total_microseconds); - Py_DECREF(one_million); - return total_seconds; -} - -static PyObject * -delta_reduce(PyDateTime_Delta* self) -{ - return Py_BuildValue("ON", Py_TYPE(self), delta_getstate(self)); -} - -#define OFFSET(field) offsetof(PyDateTime_Delta, field) - -static PyMemberDef delta_members[] = { - - {"days", T_INT, OFFSET(days), READONLY, - PyDoc_STR("Number of days.")}, - - {"seconds", T_INT, OFFSET(seconds), READONLY, - PyDoc_STR("Number of seconds (>= 0 and less than 1 day).")}, - - {"microseconds", T_INT, OFFSET(microseconds), READONLY, - PyDoc_STR("Number of microseconds (>= 0 and less than 1 second).")}, - {NULL} -}; - -static PyMethodDef delta_methods[] = { - {"total_seconds", (PyCFunction)delta_total_seconds, METH_NOARGS, - PyDoc_STR("Total seconds in the duration.")}, - - {"__reduce__", (PyCFunction)delta_reduce, METH_NOARGS, - PyDoc_STR("__reduce__() -> (cls, 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_remainder, /* nb_remainder */ - delta_divmod, /* nb_divmod */ - 0, /* nb_power */ - (unaryfunc)delta_negative, /* nb_negative */ - (unaryfunc)delta_positive, /* nb_positive */ - (unaryfunc)delta_abs, /* nb_absolute */ - (inquiry)delta_bool, /* nb_bool */ - 0, /*nb_invert*/ - 0, /*nb_lshift*/ - 0, /*nb_rshift*/ - 0, /*nb_and*/ - 0, /*nb_xor*/ - 0, /*nb_or*/ - 0, /*nb_int*/ - 0, /*nb_reserved*/ - 0, /*nb_float*/ - 0, /*nb_inplace_add*/ - 0, /*nb_inplace_subtract*/ - 0, /*nb_inplace_multiply*/ - 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 */ - delta_truedivide, /* nb_true_divide */ - 0, /* nb_inplace_floor_divide */ - 0, /* nb_inplace_true_divide */ -}; - -static PyTypeObject PyDateTime_DeltaType = { - PyVarObject_HEAD_INIT(NULL, 0) - "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_reserved */ - (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_BASETYPE, /* tp_flags */ - delta_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 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 */ - 0, /* tp_free */ -}; - -/* - * PyDateTime_Date implementation. - */ - -/* Accessor properties. */ - -static PyObject * -date_year(PyDateTime_Date *self, void *unused) -{ - return PyLong_FromLong(GET_YEAR(self)); -} - -static PyObject * -date_month(PyDateTime_Date *self, void *unused) -{ - return PyLong_FromLong(GET_MONTH(self)); -} - -static PyObject * -date_day(PyDateTime_Date *self, void *unused) -{ - return PyLong_FromLong(GET_DAY(self)); -} - -static PyGetSetDef date_getset[] = { - {"year", (getter)date_year}, - {"month", (getter)date_month}, - {"day", (getter)date_day}, - {NULL} -}; - -/* Constructors. */ - -static char *date_kws[] = {"year", "month", "day", NULL}; - -static PyObject * -date_new(PyTypeObject *type, PyObject *args, PyObject *kw) -{ - PyObject *self = NULL; - PyObject *state; - int year; - int month; - int day; - - /* Check for invocation from pickle with __getstate__ state */ - if (PyTuple_GET_SIZE(args) == 1 && - PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && - PyBytes_GET_SIZE(state) == _PyDateTime_DATE_DATASIZE && - MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) - { - PyDateTime_Date *me; - - me = (PyDateTime_Date *) (type->tp_alloc(type, 0)); - if (me != NULL) { - char *pdata = PyBytes_AS_STRING(state); - memcpy(me->data, pdata, _PyDateTime_DATE_DATASIZE); - me->hashcode = -1; - } - return (PyObject *)me; - } - - if (PyArg_ParseTupleAndKeywords(args, kw, "iii", date_kws, - &year, &month, &day)) { - if (check_date_args(year, month, day) < 0) - return NULL; - self = new_date_ex(year, month, day, type); - } - return self; -} - -/* Return new date from localtime(t). */ -static PyObject * -date_local_from_time_t(PyObject *cls, double ts) -{ - struct tm *tm; - time_t t; - PyObject *result = NULL; - - t = _PyTime_DoubleToTimet(ts); - if (t == (time_t)-1 && PyErr_Occurred()) - return 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, 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_Check(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_Check(left)) { - if (PyDate_Check(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) -{ - return PyUnicode_FromFormat("%s(%d, %d, %d)", - Py_TYPE(self)->tp_name, - GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); -} - -static PyObject * -date_isoformat(PyDateTime_Date *self) -{ - return PyUnicode_FromFormat("%04d-%02d-%02d", - GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); -} - -/* str() calls the appropriate isoformat() 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 *tuple; - PyObject *format; - static char *keywords[] = {"format", NULL}; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "U:strftime", keywords, - &format)) - return NULL; - - tuple = PyObject_CallMethod((PyObject *)self, "timetuple", "()"); - if (tuple == NULL) - return NULL; - result = wrap_strftime((PyObject *)self, format, tuple, - (PyObject *)self); - Py_DECREF(tuple); - return result; -} - -static PyObject * -date_format(PyDateTime_Date *self, PyObject *args) -{ - PyObject *format; - - if (!PyArg_ParseTuple(args, "U:__format__", &format)) - return NULL; - - /* if the format is zero length, return str(self) */ - if (PyUnicode_GetSize(format) == 0) - return PyObject_Str((PyObject *)self); - - return PyObject_CallMethod((PyObject *)self, "strftime", "O", format); -} - -/* ISO methods. */ - -static PyObject * -date_isoweekday(PyDateTime_Date *self) -{ - int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); - - return PyLong_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. */ - -static PyObject * -date_richcompare(PyObject *self, PyObject *other, int op) -{ - if (PyDate_Check(other)) { - int diff = memcmp(((PyDateTime_Date *)self)->data, - ((PyDateTime_Date *)other)->data, - _PyDateTime_DATE_DATASIZE); - return diff_to_bool(diff, op); - } - else { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } -} - -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_replace(PyDateTime_Date *self, PyObject *args, PyObject *kw) -{ - PyObject *clone; - PyObject *tuple; - int year = GET_YEAR(self); - int month = GET_MONTH(self); - int day = GET_DAY(self); - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iii:replace", date_kws, - &year, &month, &day)) - return NULL; - tuple = Py_BuildValue("iii", year, month, day); - if (tuple == NULL) - return NULL; - clone = date_new(Py_TYPE(self), tuple, NULL); - Py_DECREF(tuple); - return clone; -} - -/* - Borrowed from stringobject.c, originally it was string_hash() -*/ -static long -generic_hash(unsigned char *data, int len) -{ - register unsigned char *p; - register long x; - - p = (unsigned char *) data; - x = *p << 7; - while (--len >= 0) - x = (1000003*x) ^ *p++; - x ^= len; - if (x == -1) - x = -2; - - return x; -} - - -static PyObject *date_getstate(PyDateTime_Date *self); - -static long -date_hash(PyDateTime_Date *self) -{ - if (self->hashcode == -1) - self->hashcode = generic_hash( - (unsigned char *)self->data, _PyDateTime_DATE_DATASIZE); - - return self->hashcode; -} - -static PyObject * -date_toordinal(PyDateTime_Date *self) -{ - return PyLong_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 PyLong_FromLong(dow); -} - -/* Pickle support, a simple use of __reduce__. */ - -/* __getstate__ isn't exposed */ -static PyObject * -date_getstate(PyDateTime_Date *self) -{ - PyObject* field; - field = PyBytes_FromStringAndSize((char*)self->data, - _PyDateTime_DATE_DATASIZE); - return Py_BuildValue("(N)", field); -} - -static PyObject * -date_reduce(PyDateTime_Date *self, PyObject *arg) -{ - return Py_BuildValue("(ON)", Py_TYPE(self), date_getstate(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_VARARGS | METH_KEYWORDS, - PyDoc_STR("format -> strftime() style string.")}, - - {"__format__", (PyCFunction)date_format, METH_VARARGS, - PyDoc_STR("Formats self with strftime.")}, - - {"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")}, - - {"replace", (PyCFunction)date_replace, METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return date with new specified fields.")}, - - {"__reduce__", (PyCFunction)date_reduce, METH_NOARGS, - PyDoc_STR("__reduce__() -> (cls, state)")}, - - {NULL, NULL} -}; - -static char date_doc[] = -PyDoc_STR("date(year, month, day) --> date object"); - -static PyNumberMethods date_as_number = { - date_add, /* nb_add */ - date_subtract, /* nb_subtract */ - 0, /* nb_multiply */ - 0, /* nb_remainder */ - 0, /* nb_divmod */ - 0, /* nb_power */ - 0, /* nb_negative */ - 0, /* nb_positive */ - 0, /* nb_absolute */ - 0, /* nb_bool */ -}; - -static PyTypeObject PyDateTime_DateType = { - PyVarObject_HEAD_INIT(NULL, 0) - "datetime.date", /* tp_name */ - sizeof(PyDateTime_Date), /* tp_basicsize */ - 0, /* tp_itemsize */ - 0, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - (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_BASETYPE, /* tp_flags */ - date_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 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 */ - 0, /* 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 - * datetime and time 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"); -} - - -static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date, - PyDateTime_Delta *delta, - int factor); -static PyObject *datetime_utcoffset(PyObject *self, PyObject *); -static PyObject *datetime_dst(PyObject *self, PyObject *); - -static PyObject * -tzinfo_fromutc(PyDateTime_TZInfo *self, PyObject *dt) -{ - PyObject *result = NULL; - PyObject *off = NULL, *dst = NULL; - PyDateTime_Delta *delta = NULL; - - if (!PyDateTime_Check(dt)) { - PyErr_SetString(PyExc_TypeError, - "fromutc: argument must be a datetime"); - return NULL; - } - if (GET_DT_TZINFO(dt) != (PyObject *)self) { - PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo " - "is not self"); - return NULL; - } - - off = datetime_utcoffset(dt, NULL); - if (off == NULL) - return NULL; - if (off == Py_None) { - PyErr_SetString(PyExc_ValueError, "fromutc: non-None " - "utcoffset() result required"); - goto Fail; - } - - dst = datetime_dst(dt, NULL); - if (dst == NULL) - goto Fail; - if (dst == Py_None) { - PyErr_SetString(PyExc_ValueError, "fromutc: non-None " - "dst() result required"); - goto Fail; - } - - delta = (PyDateTime_Delta *)delta_subtract(off, dst); - if (delta == NULL) - goto Fail; - result = add_datetime_timedelta((PyDateTime_DateTime *)dt, delta, 1); - if (result == NULL) - goto Fail; - - Py_DECREF(dst); - dst = call_dst(GET_DT_TZINFO(dt), result); - if (dst == NULL) - goto Fail; - if (dst == Py_None) - goto Inconsistent; - if (delta_bool(delta) != 0) { - PyObject *temp = result; - result = add_datetime_timedelta((PyDateTime_DateTime *)result, - (PyDateTime_Delta *)dst, 1); - Py_DECREF(temp); - if (result == NULL) - goto Fail; - } - Py_DECREF(delta); - Py_DECREF(dst); - Py_DECREF(off); - return result; - -Inconsistent: - PyErr_SetString(PyExc_ValueError, "fromutc: tz.dst() gave" - "inconsistent results; cannot convert"); - - /* fall thru to failure */ -Fail: - Py_XDECREF(off); - Py_XDECREF(dst); - Py_XDECREF(delta); - Py_XDECREF(result); - return NULL; -} - -/* - * Pickle support. This is solely so that tzinfo subclasses can use - * pickling -- tzinfo itself is supposed to be uninstantiable. - */ - -static PyObject * -tzinfo_reduce(PyObject *self) -{ - PyObject *args, *state, *tmp; - PyObject *getinitargs, *getstate; - - tmp = PyTuple_New(0); - if (tmp == NULL) - return NULL; - - getinitargs = PyObject_GetAttrString(self, "__getinitargs__"); - if (getinitargs != NULL) { - args = PyObject_CallObject(getinitargs, tmp); - Py_DECREF(getinitargs); - if (args == NULL) { - Py_DECREF(tmp); - return NULL; - } - } - else { - PyErr_Clear(); - args = tmp; - Py_INCREF(args); - } - - getstate = PyObject_GetAttrString(self, "__getstate__"); - if (getstate != NULL) { - state = PyObject_CallObject(getstate, tmp); - Py_DECREF(getstate); - if (state == NULL) { - Py_DECREF(args); - Py_DECREF(tmp); - return NULL; - } - } - else { - PyObject **dictptr; - PyErr_Clear(); - state = Py_None; - dictptr = _PyObject_GetDictPtr(self); - if (dictptr && *dictptr && PyDict_Size(*dictptr)) - state = *dictptr; - Py_INCREF(state); - } - - Py_DECREF(tmp); - - if (state == Py_None) { - Py_DECREF(state); - return Py_BuildValue("(ON)", Py_TYPE(self), args); - } - else - return Py_BuildValue("(ONN)", Py_TYPE(self), args, state); -} - -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 -> timedelta showing offset from UTC, negative " - "values indicating West of UTC")}, - - {"dst", (PyCFunction)tzinfo_dst, METH_O, - PyDoc_STR("datetime -> DST offset in minutes east of UTC.")}, - - {"fromutc", (PyCFunction)tzinfo_fromutc, METH_O, - PyDoc_STR("datetime in UTC -> datetime in local time.")}, - - {"__reduce__", (PyCFunction)tzinfo_reduce, METH_NOARGS, - PyDoc_STR("-> (cls, state)")}, - - {NULL, NULL} -}; - -static char tzinfo_doc[] = -PyDoc_STR("Abstract base class for time zone info objects."); - -static PyTypeObject PyDateTime_TZInfoType = { - PyVarObject_HEAD_INIT(NULL, 0) - "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_reserved */ - 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_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 */ -}; - -static char *timezone_kws[] = {"offset", "name", NULL}; - -static PyObject * -timezone_new(PyTypeObject *type, PyObject *args, PyObject *kw) -{ - PyObject *offset; - PyObject *name = NULL; - if (PyArg_ParseTupleAndKeywords(args, kw, "O!|O!:timezone", timezone_kws, - &PyDateTime_DeltaType, &offset, - &PyUnicode_Type, &name)) - return new_timezone(offset, name); - - return NULL; -} - -static void -timezone_dealloc(PyDateTime_TimeZone *self) -{ - Py_CLEAR(self->offset); - Py_CLEAR(self->name); - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyObject * -timezone_richcompare(PyDateTime_TimeZone *self, - PyDateTime_TimeZone *other, int op) -{ - if (op != Py_EQ && op != Py_NE) { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - return delta_richcompare(self->offset, other->offset, op); -} - -static long -timezone_hash(PyDateTime_TimeZone *self) -{ - return delta_hash((PyDateTime_Delta *)self->offset); -} - -/* Check argument type passed to tzname, utcoffset, or dst methods. - Returns 0 for good argument. Returns -1 and sets exception info - otherwise. - */ -static int -_timezone_check_argument(PyObject *dt, const char *meth) -{ - if (dt == Py_None || PyDateTime_Check(dt)) - return 0; - PyErr_Format(PyExc_TypeError, "%s(dt) argument must be a datetime instance" - " or None, not %.200s", meth, Py_TYPE(dt)->tp_name); - return -1; -} - -static PyObject * -timezone_repr(PyDateTime_TimeZone *self) -{ - /* Note that although timezone is not subclassable, it is convenient - to use Py_TYPE(self)->tp_name here. */ - const char *type_name = Py_TYPE(self)->tp_name; - - if (((PyObject *)self) == PyDateTime_TimeZone_UTC) - return PyUnicode_FromFormat("%s.utc", type_name); - - if (self->name == NULL) - return PyUnicode_FromFormat("%s(%R)", type_name, self->offset); - - return PyUnicode_FromFormat("%s(%R, %R)", type_name, self->offset, - self->name); -} - - -static PyObject * -timezone_str(PyDateTime_TimeZone *self) -{ - char buf[10]; - int hours, minutes, seconds; - PyObject *offset; - char sign; - - if (self->name != NULL) { - Py_INCREF(self->name); - return self->name; - } - /* Offset is normalized, so it is negative if days < 0 */ - if (GET_TD_DAYS(self->offset) < 0) { - sign = '-'; - offset = delta_negative((PyDateTime_Delta *)self->offset); - if (offset == NULL) - return NULL; - } - else { - sign = '+'; - offset = self->offset; - Py_INCREF(offset); - } - /* Offset is not negative here. */ - seconds = GET_TD_SECONDS(offset); - Py_DECREF(offset); - minutes = divmod(seconds, 60, &seconds); - hours = divmod(minutes, 60, &minutes); - assert(seconds == 0); - /* XXX ignore sub-minute data, curently not allowed. */ - PyOS_snprintf(buf, sizeof(buf), "UTC%c%02d:%02d", sign, hours, minutes); - - return PyUnicode_FromString(buf); -} - -static PyObject * -timezone_tzname(PyDateTime_TimeZone *self, PyObject *dt) -{ - if (_timezone_check_argument(dt, "tzname") == -1) - return NULL; - - return timezone_str(self); -} - -static PyObject * -timezone_utcoffset(PyDateTime_TimeZone *self, PyObject *dt) -{ - if (_timezone_check_argument(dt, "utcoffset") == -1) - return NULL; - - Py_INCREF(self->offset); - return self->offset; -} - -static PyObject * -timezone_dst(PyObject *self, PyObject *dt) -{ - if (_timezone_check_argument(dt, "dst") == -1) - return NULL; - - Py_RETURN_NONE; -} - -static PyObject * -timezone_fromutc(PyDateTime_TimeZone *self, PyDateTime_DateTime *dt) -{ - if (!PyDateTime_Check(dt)) { - PyErr_SetString(PyExc_TypeError, - "fromutc: argument must be a datetime"); - return NULL; - } - if (!HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) { - PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo " - "is not self"); - return NULL; - } - - return add_datetime_timedelta(dt, (PyDateTime_Delta *)self->offset, 1); -} - -static PyObject * -timezone_getinitargs(PyDateTime_TimeZone *self) -{ - if (self->name == NULL) - return Py_BuildValue("(O)", self->offset); - return Py_BuildValue("(OO)", self->offset, self->name); -} - -static PyMethodDef timezone_methods[] = { - {"tzname", (PyCFunction)timezone_tzname, METH_O, - PyDoc_STR("If name is specified when timezone is created, returns the name." - " Otherwise returns offset as 'UTC(+|-)HH:MM'.")}, - - {"utcoffset", (PyCFunction)timezone_utcoffset, METH_O, - PyDoc_STR("Return fixed offset.")}, - - {"dst", (PyCFunction)timezone_dst, METH_O, - PyDoc_STR("Return None.")}, - - {"fromutc", (PyCFunction)timezone_fromutc, METH_O, - PyDoc_STR("datetime in UTC -> datetime in local time.")}, - - {"__getinitargs__", (PyCFunction)timezone_getinitargs, METH_NOARGS, - PyDoc_STR("pickle support")}, - - {NULL, NULL} -}; - -static char timezone_doc[] = -PyDoc_STR("Fixed offset from UTC implementation of tzinfo."); - -static PyTypeObject PyDateTime_TimeZoneType = { - PyVarObject_HEAD_INIT(NULL, 0) - "datetime.timezone", /* tp_name */ - sizeof(PyDateTime_TimeZone), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)timezone_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - (reprfunc)timezone_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - (hashfunc)timezone_hash, /* tp_hash */ - 0, /* tp_call */ - (reprfunc)timezone_str, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - timezone_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - (richcmpfunc)timezone_richcompare,/* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - timezone_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - &PyDateTime_TZInfoType, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - timezone_new, /* tp_new */ -}; - -/* - * PyDateTime_Time implementation. - */ - -/* Accessor properties. - */ - -static PyObject * -time_hour(PyDateTime_Time *self, void *unused) -{ - return PyLong_FromLong(TIME_GET_HOUR(self)); -} - -static PyObject * -time_minute(PyDateTime_Time *self, void *unused) -{ - return PyLong_FromLong(TIME_GET_MINUTE(self)); -} - -/* The name time_second conflicted with some platform header file. */ -static PyObject * -py_time_second(PyDateTime_Time *self, void *unused) -{ - return PyLong_FromLong(TIME_GET_SECOND(self)); -} - -static PyObject * -time_microsecond(PyDateTime_Time *self, void *unused) -{ - return PyLong_FromLong(TIME_GET_MICROSECOND(self)); -} - -static PyObject * -time_tzinfo(PyDateTime_Time *self, void *unused) -{ - PyObject *result = HASTZINFO(self) ? self->tzinfo : Py_None; - Py_INCREF(result); - return result; -} - -static PyGetSetDef time_getset[] = { - {"hour", (getter)time_hour}, - {"minute", (getter)time_minute}, - {"second", (getter)py_time_second}, - {"microsecond", (getter)time_microsecond}, - {"tzinfo", (getter)time_tzinfo}, - {NULL} -}; - -/* - * Constructors. - */ - -static char *time_kws[] = {"hour", "minute", "second", "microsecond", - "tzinfo", NULL}; - -static PyObject * -time_new(PyTypeObject *type, PyObject *args, PyObject *kw) -{ - PyObject *self = NULL; - PyObject *state; - int hour = 0; - int minute = 0; - int second = 0; - int usecond = 0; - PyObject *tzinfo = Py_None; - - /* Check for invocation from pickle with __getstate__ state */ - if (PyTuple_GET_SIZE(args) >= 1 && - PyTuple_GET_SIZE(args) <= 2 && - PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && - PyBytes_GET_SIZE(state) == _PyDateTime_TIME_DATASIZE && - ((unsigned char) (PyBytes_AS_STRING(state)[0])) < 24) - { - PyDateTime_Time *me; - char aware; - - if (PyTuple_GET_SIZE(args) == 2) { - tzinfo = PyTuple_GET_ITEM(args, 1); - if (check_tzinfo_subclass(tzinfo) < 0) { - PyErr_SetString(PyExc_TypeError, "bad " - "tzinfo state arg"); - return NULL; - } - } - aware = (char)(tzinfo != Py_None); - me = (PyDateTime_Time *) (type->tp_alloc(type, aware)); - if (me != NULL) { - char *pdata = PyBytes_AS_STRING(state); - - memcpy(me->data, pdata, _PyDateTime_TIME_DATASIZE); - me->hashcode = -1; - me->hastzinfo = aware; - if (aware) { - Py_INCREF(tzinfo); - me->tzinfo = tzinfo; - } - } - return (PyObject *)me; - } - - if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO", time_kws, - &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_time_ex(hour, minute, second, usecond, tzinfo, - type); - } - return self; -} - -/* - * Destructor. - */ - -static void -time_dealloc(PyDateTime_Time *self) -{ - if (HASTZINFO(self)) { - Py_XDECREF(self->tzinfo); - } - Py_TYPE(self)->tp_free((PyObject *)self); -} - -/* - * Indirect access to tzinfo methods. - */ - -/* These are all METH_NOARGS, so don't need to check the arglist. */ -static PyObject * -time_utcoffset(PyObject *self, PyObject *unused) { - return call_utcoffset(GET_TIME_TZINFO(self), Py_None); -} - -static PyObject * -time_dst(PyObject *self, PyObject *unused) { - return call_dst(GET_TIME_TZINFO(self), Py_None); -} - -static PyObject * -time_tzname(PyDateTime_Time *self, PyObject *unused) { - return call_tzname(GET_TIME_TZINFO(self), Py_None); -} - -/* - * Various ways to turn a time into a string. - */ - -static PyObject * -time_repr(PyDateTime_Time *self) -{ - const char *type_name = Py_TYPE(self)->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); - PyObject *result = NULL; - - if (us) - result = PyUnicode_FromFormat("%s(%d, %d, %d, %d)", - type_name, h, m, s, us); - else if (s) - result = PyUnicode_FromFormat("%s(%d, %d, %d)", - type_name, h, m, s); - else - result = PyUnicode_FromFormat("%s(%d, %d)", type_name, h, m); - if (result != NULL && HASTZINFO(self)) - result = append_keyword_tzinfo(result, self->tzinfo); - return result; -} - -static PyObject * -time_str(PyDateTime_Time *self) -{ - return PyObject_CallMethod((PyObject *)self, "isoformat", "()"); -} - -static PyObject * -time_isoformat(PyDateTime_Time *self, PyObject *unused) -{ - char buf[100]; - PyObject *result; - int us = TIME_GET_MICROSECOND(self);; - - if (us) - result = PyUnicode_FromFormat("%02d:%02d:%02d.%06d", - TIME_GET_HOUR(self), - TIME_GET_MINUTE(self), - TIME_GET_SECOND(self), - us); - else - result = PyUnicode_FromFormat("%02d:%02d:%02d", - TIME_GET_HOUR(self), - TIME_GET_MINUTE(self), - TIME_GET_SECOND(self)); - - if (result == NULL || !HASTZINFO(self) || self->tzinfo == Py_None) - return result; - - /* We need to append the UTC offset. */ - if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, - Py_None) < 0) { - Py_DECREF(result); - return NULL; - } - PyUnicode_AppendAndDel(&result, PyUnicode_FromString(buf)); - return result; -} - -static PyObject * -time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw) -{ - PyObject *result; - PyObject *tuple; - PyObject *format; - static char *keywords[] = {"format", NULL}; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "U:strftime", keywords, - &format)) - return NULL; - - /* Python's strftime does insane things with the year part of the - * timetuple. The year is forced to (the otherwise nonsensical) - * 1900 to worm around that. - */ - tuple = Py_BuildValue("iiiiiiiii", - 1900, 1, 1, /* year, month, day */ - TIME_GET_HOUR(self), - TIME_GET_MINUTE(self), - TIME_GET_SECOND(self), - 0, 1, -1); /* weekday, daynum, dst */ - if (tuple == NULL) - return NULL; - assert(PyTuple_Size(tuple) == 9); - result = wrap_strftime((PyObject *)self, format, tuple, - Py_None); - Py_DECREF(tuple); - return result; -} - -/* - * Miscellaneous methods. - */ - -static PyObject * -time_richcompare(PyObject *self, PyObject *other, int op) -{ - PyObject *result = NULL; - PyObject *offset1, *offset2; - int diff; - - if (! PyTime_Check(other)) { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - if (GET_TIME_TZINFO(self) == GET_TIME_TZINFO(other)) { - diff = memcmp(((PyDateTime_Time *)self)->data, - ((PyDateTime_Time *)other)->data, - _PyDateTime_TIME_DATASIZE); - return diff_to_bool(diff, op); - } - offset1 = time_utcoffset(self, NULL); - if (offset1 == NULL) - return NULL; - offset2 = time_utcoffset(other, NULL); - if (offset2 == NULL) - goto done; - /* 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 == Py_None at this point. - */ - if ((offset1 == offset2) || - (PyDelta_Check(offset1) && PyDelta_Check(offset2) && - delta_cmp(offset1, offset2) == 0)) { - diff = memcmp(((PyDateTime_Time *)self)->data, - ((PyDateTime_Time *)other)->data, - _PyDateTime_TIME_DATASIZE); - result = diff_to_bool(diff, op); - } - /* The hard case: both aware with different UTC offsets */ - else if (offset1 != Py_None && offset2 != Py_None) { - int offsecs1, offsecs2; - assert(offset1 != offset2); /* else last "if" handled it */ - offsecs1 = TIME_GET_HOUR(self) * 3600 + - TIME_GET_MINUTE(self) * 60 + - TIME_GET_SECOND(self) - - GET_TD_DAYS(offset1) * 86400 - - GET_TD_SECONDS(offset1); - offsecs2 = TIME_GET_HOUR(other) * 3600 + - TIME_GET_MINUTE(other) * 60 + - TIME_GET_SECOND(other) - - GET_TD_DAYS(offset2) * 86400 - - GET_TD_SECONDS(offset2); - diff = offsecs1 - offsecs2; - if (diff == 0) - diff = TIME_GET_MICROSECOND(self) - - TIME_GET_MICROSECOND(other); - result = diff_to_bool(diff, op); - } - else { - PyErr_SetString(PyExc_TypeError, - "can't compare offset-naive and " - "offset-aware times"); - } - done: - Py_DECREF(offset1); - Py_XDECREF(offset2); - return result; -} - -static long -time_hash(PyDateTime_Time *self) -{ - if (self->hashcode == -1) { - PyObject *offset; - - offset = time_utcoffset((PyObject *)self, NULL); - - if (offset == NULL) - return -1; - - /* Reduce this to a hash of another object. */ - if (offset == Py_None) - self->hashcode = generic_hash( - (unsigned char *)self->data, _PyDateTime_TIME_DATASIZE); - else { - PyObject *temp1, *temp2; - int seconds, microseconds; - assert(HASTZINFO(self)); - seconds = TIME_GET_HOUR(self) * 3600 + - TIME_GET_MINUTE(self) * 60 + - TIME_GET_SECOND(self); - microseconds = TIME_GET_MICROSECOND(self); - temp1 = new_delta(0, seconds, microseconds, 1); - if (temp1 == NULL) { - Py_DECREF(offset); - return -1; - } - temp2 = delta_subtract(temp1, offset); - Py_DECREF(temp1); - if (temp2 == NULL) { - Py_DECREF(offset); - return -1; - } - self->hashcode = PyObject_Hash(temp2); - Py_DECREF(temp2); - } - Py_DECREF(offset); - } - return self->hashcode; -} - -static PyObject * -time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) -{ - PyObject *clone; - PyObject *tuple; - int hh = TIME_GET_HOUR(self); - int mm = TIME_GET_MINUTE(self); - int ss = TIME_GET_SECOND(self); - int us = TIME_GET_MICROSECOND(self); - PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO:replace", - time_kws, - &hh, &mm, &ss, &us, &tzinfo)) - return NULL; - tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); - if (tuple == NULL) - return NULL; - clone = time_new(Py_TYPE(self), tuple, NULL); - Py_DECREF(tuple); - return clone; -} - -static int -time_bool(PyObject *self) -{ - PyObject *offset, *tzinfo; - int offsecs = 0; - - 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; - } - tzinfo = GET_TIME_TZINFO(self); - if (tzinfo != Py_None) { - offset = call_utcoffset(tzinfo, Py_None); - if (offset == NULL) - return -1; - offsecs = GET_TD_DAYS(offset)*86400 + GET_TD_SECONDS(offset); - Py_DECREF(offset); - } - return (TIME_GET_MINUTE(self)*60 - offsecs + TIME_GET_HOUR(self)*3600) != 0; -} - -/* Pickle support, a simple use of __reduce__. */ - -/* Let basestate be the non-tzinfo data string. - * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). - * So it's a tuple in any (non-error) case. - * __getstate__ isn't exposed. - */ -static PyObject * -time_getstate(PyDateTime_Time *self) -{ - PyObject *basestate; - PyObject *result = NULL; - - basestate = PyBytes_FromStringAndSize((char *)self->data, - _PyDateTime_TIME_DATASIZE); - if (basestate != NULL) { - if (! HASTZINFO(self) || self->tzinfo == Py_None) - result = PyTuple_Pack(1, basestate); - else - result = PyTuple_Pack(2, basestate, self->tzinfo); - Py_DECREF(basestate); - } - return result; -} - -static PyObject * -time_reduce(PyDateTime_Time *self, PyObject *arg) -{ - return Py_BuildValue("(ON)", Py_TYPE(self), time_getstate(self)); -} - -static PyMethodDef time_methods[] = { - - {"isoformat", (PyCFunction)time_isoformat, METH_NOARGS, - PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm]" - "[+HH:MM].")}, - - {"strftime", (PyCFunction)time_strftime, METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("format -> strftime() style string.")}, - - {"__format__", (PyCFunction)date_format, METH_VARARGS, - PyDoc_STR("Formats self with strftime.")}, - - {"utcoffset", (PyCFunction)time_utcoffset, METH_NOARGS, - PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, - - {"tzname", (PyCFunction)time_tzname, METH_NOARGS, - PyDoc_STR("Return self.tzinfo.tzname(self).")}, - - {"dst", (PyCFunction)time_dst, METH_NOARGS, - PyDoc_STR("Return self.tzinfo.dst(self).")}, - - {"replace", (PyCFunction)time_replace, METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return time with new specified fields.")}, - - {"__reduce__", (PyCFunction)time_reduce, METH_NOARGS, - PyDoc_STR("__reduce__() -> (cls, state)")}, - - {NULL, NULL} -}; - -static char time_doc[] = -PyDoc_STR("time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> a time object\n\ -\n\ -All arguments are optional. tzinfo may be None, or an instance of\n\ -a tzinfo subclass. The remaining arguments may be ints or longs.\n"); - -static PyNumberMethods time_as_number = { - 0, /* nb_add */ - 0, /* nb_subtract */ - 0, /* nb_multiply */ - 0, /* nb_remainder */ - 0, /* nb_divmod */ - 0, /* nb_power */ - 0, /* nb_negative */ - 0, /* nb_positive */ - 0, /* nb_absolute */ - (inquiry)time_bool, /* nb_bool */ -}; - -static PyTypeObject PyDateTime_TimeType = { - PyVarObject_HEAD_INIT(NULL, 0) - "datetime.time", /* tp_name */ - sizeof(PyDateTime_Time), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)time_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - (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_BASETYPE, /* tp_flags */ - time_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 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 */ - time_alloc, /* tp_alloc */ - time_new, /* tp_new */ - 0, /* tp_free */ -}; - -/* - * PyDateTime_DateTime implementation. - */ - -/* Accessor properties. Properties for day, month, and year are inherited - * from date. - */ - -static PyObject * -datetime_hour(PyDateTime_DateTime *self, void *unused) -{ - return PyLong_FromLong(DATE_GET_HOUR(self)); -} - -static PyObject * -datetime_minute(PyDateTime_DateTime *self, void *unused) -{ - return PyLong_FromLong(DATE_GET_MINUTE(self)); -} - -static PyObject * -datetime_second(PyDateTime_DateTime *self, void *unused) -{ - return PyLong_FromLong(DATE_GET_SECOND(self)); -} - -static PyObject * -datetime_microsecond(PyDateTime_DateTime *self, void *unused) -{ - return PyLong_FromLong(DATE_GET_MICROSECOND(self)); -} - -static PyObject * -datetime_tzinfo(PyDateTime_DateTime *self, void *unused) -{ - PyObject *result = HASTZINFO(self) ? self->tzinfo : Py_None; - Py_INCREF(result); - return result; -} - -static PyGetSetDef datetime_getset[] = { - {"hour", (getter)datetime_hour}, - {"minute", (getter)datetime_minute}, - {"second", (getter)datetime_second}, - {"microsecond", (getter)datetime_microsecond}, - {"tzinfo", (getter)datetime_tzinfo}, - {NULL} -}; - -/* - * Constructors. - */ - -static char *datetime_kws[] = { - "year", "month", "day", "hour", "minute", "second", - "microsecond", "tzinfo", NULL -}; - -static PyObject * -datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) -{ - PyObject *self = NULL; - PyObject *state; - int year; - int month; - int day; - int hour = 0; - int minute = 0; - int second = 0; - int usecond = 0; - PyObject *tzinfo = Py_None; - - /* Check for invocation from pickle with __getstate__ state */ - if (PyTuple_GET_SIZE(args) >= 1 && - PyTuple_GET_SIZE(args) <= 2 && - PyBytes_Check(state = PyTuple_GET_ITEM(args, 0)) && - PyBytes_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE && - MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) - { - PyDateTime_DateTime *me; - char aware; - - if (PyTuple_GET_SIZE(args) == 2) { - tzinfo = PyTuple_GET_ITEM(args, 1); - if (check_tzinfo_subclass(tzinfo) < 0) { - PyErr_SetString(PyExc_TypeError, "bad " - "tzinfo state arg"); - return NULL; - } - } - aware = (char)(tzinfo != Py_None); - me = (PyDateTime_DateTime *) (type->tp_alloc(type , aware)); - if (me != NULL) { - char *pdata = PyBytes_AS_STRING(state); - - memcpy(me->data, pdata, _PyDateTime_DATETIME_DATASIZE); - me->hashcode = -1; - me->hastzinfo = aware; - if (aware) { - Py_INCREF(tzinfo); - me->tzinfo = tzinfo; - } - } - return (PyObject *)me; - } - - if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO", datetime_kws, - &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_datetime_ex(year, month, day, - hour, minute, second, usecond, - tzinfo, type); - } - 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, - PyObject *tzinfo) -{ - struct tm *tm; - PyObject *result = NULL; - - tm = f(&timet); - if (tm) { - /* The platform localtime/gmtime may insert leap seconds, - * indicated by tm->tm_sec > 59. We don't care about them, - * except to the extent that passing them on to the datetime - * constructor would raise ValueError for a reason that - * made no sense to the user. - */ - if (tm->tm_sec > 59) - tm->tm_sec = 59; - result = PyObject_CallFunction(cls, "iiiiiiiO", - tm->tm_year + 1900, - tm->tm_mon + 1, - tm->tm_mday, - tm->tm_hour, - tm->tm_min, - tm->tm_sec, - us, - tzinfo); - } - 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, - PyObject *tzinfo) -{ - time_t timet; - double fraction; - int us; - - timet = _PyTime_DoubleToTimet(timestamp); - if (timet == (time_t)-1 && PyErr_Occurred()) - return NULL; - fraction = timestamp - (double)timet; - us = (int)round_to_long(fraction * 1e6); - if (us < 0) { - /* Truncation towards zero is not what we wanted - for negative numbers (Python's mod semantics) */ - timet -= 1; - us += 1000000; - } - /* If timestamp is less than one microsecond smaller than a - * full second, round up. Otherwise, ValueErrors are raised - * for some floats. */ - if (us == 1000000) { - timet += 1; - us = 0; - } - return datetime_from_timet_and_us(cls, f, timet, us, tzinfo); -} - -/* 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, PyObject *tzinfo) -{ -#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, - tzinfo); - -#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, tzinfo); -#endif /* ! HAVE_GETTIMEOFDAY */ -} - -/* Return best possible local time -- this isn't constrained by the - * precision of a timestamp. - */ -static PyObject * -datetime_now(PyObject *cls, PyObject *args, PyObject *kw) -{ - PyObject *self; - PyObject *tzinfo = Py_None; - static char *keywords[] = {"tz", NULL}; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|O:now", keywords, - &tzinfo)) - return NULL; - if (check_tzinfo_subclass(tzinfo) < 0) - return NULL; - - self = datetime_best_possible(cls, - tzinfo == Py_None ? localtime : gmtime, - tzinfo); - if (self != NULL && tzinfo != Py_None) { - /* Convert UTC to tzinfo's zone. */ - PyObject *temp = self; - self = PyObject_CallMethod(tzinfo, "fromutc", "O", self); - Py_DECREF(temp); - } - return self; -} - -/* 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, Py_None); -} - -/* Return new local datetime from timestamp (Python timestamp -- a double). */ -static PyObject * -datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) -{ - PyObject *self; - double timestamp; - PyObject *tzinfo = Py_None; - static char *keywords[] = {"timestamp", "tz", NULL}; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", - keywords, ×tamp, &tzinfo)) - return NULL; - if (check_tzinfo_subclass(tzinfo) < 0) - return NULL; - - self = datetime_from_timestamp(cls, - tzinfo == Py_None ? localtime : gmtime, - timestamp, - tzinfo); - if (self != NULL && tzinfo != Py_None) { - /* Convert UTC to tzinfo's zone. */ - PyObject *temp = self; - self = PyObject_CallMethod(tzinfo, "fromutc", "O", self); - Py_DECREF(temp); - } - return self; -} - -/* 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, - Py_None); - return result; -} - -/* Return new datetime from _strptime.strptime_datetime(). */ -static PyObject * -datetime_strptime(PyObject *cls, PyObject *args) -{ - static PyObject *module = NULL; - const Py_UNICODE *string, *format; - - if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format)) - return NULL; - - if (module == NULL) { - module = PyImport_ImportModuleNoBlock("_strptime"); - if (module == NULL) - return NULL; - } - return PyObject_CallMethod(module, "_strptime_datetime", "Ouu", - cls, string, format); -} - -/* Return new datetime from date/datetime and time 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)) { - PyObject *tzinfo = Py_None; - - if (HASTZINFO(time)) - tzinfo = ((PyDateTime_Time *)time)->tzinfo; - result = PyObject_CallFunction(cls, "iiiiiiiO", - 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), - tzinfo); - } - return result; -} - -/* - * Destructor. - */ - -static void -datetime_dealloc(PyDateTime_DateTime *self) -{ - if (HASTZINFO(self)) { - Py_XDECREF(self->tzinfo); - } - Py_TYPE(self)->tp_free((PyObject *)self); -} - -/* - * Indirect access to tzinfo methods. - */ - -/* These are all METH_NOARGS, so don't need to check the arglist. */ -static PyObject * -datetime_utcoffset(PyObject *self, PyObject *unused) { - return call_utcoffset(GET_DT_TZINFO(self), self); -} - -static PyObject * -datetime_dst(PyObject *self, PyObject *unused) { - return call_dst(GET_DT_TZINFO(self), self); -} - -static PyObject * -datetime_tzname(PyObject *self, PyObject *unused) { - return call_tzname(GET_DT_TZINFO(self), self); -} - -/* - * datetime arithmetic. - */ - -/* factor must be 1 (to add) or -1 (to subtract). The result inherits - * the tzinfo state of date. - */ -static PyObject * -add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta, - int factor) -{ - /* 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) * factor; - int hour = DATE_GET_HOUR(date); - int minute = DATE_GET_MINUTE(date); - int second = DATE_GET_SECOND(date) + GET_TD_SECONDS(delta) * factor; - int microsecond = DATE_GET_MICROSECOND(date) + - GET_TD_MICROSECONDS(delta) * factor; - - assert(factor == 1 || factor == -1); - if (normalize_datetime(&year, &month, &day, - &hour, &minute, &second, µsecond) < 0) - return NULL; - else - return new_datetime(year, month, day, - hour, minute, second, microsecond, - HASTZINFO(date) ? date->tzinfo : Py_None); -} - -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, - 1); - } - else if (PyDelta_Check(left)) { - /* delta + datetime */ - return add_datetime_timedelta((PyDateTime_DateTime *) right, - (PyDateTime_Delta *) left, - 1); - } - 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 */ - PyObject *offset1, *offset2, *offdiff = NULL; - int delta_d, delta_s, delta_us; - - if (GET_DT_TZINFO(left) == GET_DT_TZINFO(right)) { - offset2 = offset1 = Py_None; - Py_INCREF(offset1); - Py_INCREF(offset2); - } - else { - offset1 = datetime_utcoffset(left, NULL); - if (offset1 == NULL) - return NULL; - offset2 = datetime_utcoffset(right, NULL); - if (offset2 == NULL) { - Py_DECREF(offset1); - return NULL; - } - if ((offset1 != Py_None) != (offset2 != Py_None)) { - PyErr_SetString(PyExc_TypeError, - "can't subtract offset-naive and " - "offset-aware datetimes"); - Py_DECREF(offset1); - Py_DECREF(offset2); - return NULL; - } - } - if ((offset1 != offset2) && - delta_cmp(offset1, offset2) != 0) { - offdiff = delta_subtract(offset1, offset2); - if (offdiff == NULL) { - Py_DECREF(offset1); - Py_DECREF(offset2); - return NULL; - } - } - Py_DECREF(offset1); - Py_DECREF(offset2); - delta_d = ymd_to_ord(GET_YEAR(left), - GET_MONTH(left), - GET_DAY(left)) - - 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. - */ - 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)); - delta_us = DATE_GET_MICROSECOND(left) - - DATE_GET_MICROSECOND(right); - result = new_delta(delta_d, delta_s, delta_us, 1); - if (offdiff != NULL) { - PyObject *temp = result; - result = delta_subtract(result, offdiff); - Py_DECREF(temp); - Py_DECREF(offdiff); - } - } - else if (PyDelta_Check(right)) { - /* datetime - delta */ - result = add_datetime_timedelta( - (PyDateTime_DateTime *)left, - (PyDateTime_Delta *)right, - -1); - } - } - - 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) -{ - const char *type_name = Py_TYPE(self)->tp_name; - PyObject *baserepr; - - if (DATE_GET_MICROSECOND(self)) { - baserepr = PyUnicode_FromFormat( - "%s(%d, %d, %d, %d, %d, %d, %d)", - type_name, - 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)) { - baserepr = PyUnicode_FromFormat( - "%s(%d, %d, %d, %d, %d, %d)", - type_name, - GET_YEAR(self), GET_MONTH(self), GET_DAY(self), - DATE_GET_HOUR(self), DATE_GET_MINUTE(self), - DATE_GET_SECOND(self)); - } - else { - baserepr = PyUnicode_FromFormat( - "%s(%d, %d, %d, %d, %d)", - type_name, - GET_YEAR(self), GET_MONTH(self), GET_DAY(self), - DATE_GET_HOUR(self), DATE_GET_MINUTE(self)); - } - if (baserepr == NULL || ! HASTZINFO(self)) - return baserepr; - return append_keyword_tzinfo(baserepr, self->tzinfo); -} - -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) -{ - int sep = 'T'; - static char *keywords[] = {"sep", NULL}; - char buffer[100]; - PyObject *result; - int us = DATE_GET_MICROSECOND(self); - - if (!PyArg_ParseTupleAndKeywords(args, kw, "|C:isoformat", keywords, &sep)) - return NULL; - if (us) - result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%06d", - GET_YEAR(self), GET_MONTH(self), - GET_DAY(self), (int)sep, - DATE_GET_HOUR(self), DATE_GET_MINUTE(self), - DATE_GET_SECOND(self), us); - else - result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d", - GET_YEAR(self), GET_MONTH(self), - GET_DAY(self), (int)sep, - DATE_GET_HOUR(self), DATE_GET_MINUTE(self), - DATE_GET_SECOND(self)); - - if (!result || !HASTZINFO(self)) - return result; - - /* We need to append the UTC offset. */ - if (format_utcoffset(buffer, sizeof(buffer), ":", self->tzinfo, - (PyObject *)self) < 0) { - Py_DECREF(result); - return NULL; - } - PyUnicode_AppendAndDel(&result, PyUnicode_FromString(buffer)); - return result; -} - -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. */ - -static PyObject * -datetime_richcompare(PyObject *self, PyObject *other, int op) -{ - PyObject *result = NULL; - PyObject *offset1, *offset2; - int diff; - - if (! PyDateTime_Check(other)) { - if (PyDate_Check(other)) { - /* Prevent invocation of date_richcompare. We want to - return NotImplemented here to give the other object - a chance. But since DateTime is a subclass of - Date, if the other object is a Date, it would - compute an ordering based on the date part alone, - and we don't want that. So force unequal or - uncomparable here in that case. */ - if (op == Py_EQ) - Py_RETURN_FALSE; - if (op == Py_NE) - Py_RETURN_TRUE; - return cmperror(self, other); - } - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - if (GET_DT_TZINFO(self) == GET_DT_TZINFO(other)) { - diff = memcmp(((PyDateTime_DateTime *)self)->data, - ((PyDateTime_DateTime *)other)->data, - _PyDateTime_DATETIME_DATASIZE); - return diff_to_bool(diff, op); - } - offset1 = datetime_utcoffset(self, NULL); - if (offset1 == NULL) - return NULL; - offset2 = datetime_utcoffset(other, NULL); - if (offset2 == NULL) - goto done; - /* 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 == Py_None at this point. - */ - if ((offset1 == offset2) || - (PyDelta_Check(offset1) && PyDelta_Check(offset2) && - delta_cmp(offset1, offset2) == 0)) { - diff = memcmp(((PyDateTime_DateTime *)self)->data, - ((PyDateTime_DateTime *)other)->data, - _PyDateTime_DATETIME_DATASIZE); - result = diff_to_bool(diff, op); - } - else if (offset1 != Py_None && offset2 != Py_None) { - PyDateTime_Delta *delta; - - assert(offset1 != offset2); /* else last "if" handled it */ - delta = (PyDateTime_Delta *)datetime_subtract((PyObject *)self, - other); - if (delta == NULL) - goto done; - diff = GET_TD_DAYS(delta); - if (diff == 0) - diff = GET_TD_SECONDS(delta) | - GET_TD_MICROSECONDS(delta); - Py_DECREF(delta); - result = diff_to_bool(diff, op); - } - else { - PyErr_SetString(PyExc_TypeError, - "can't compare offset-naive and " - "offset-aware datetimes"); - } - done: - Py_DECREF(offset1); - Py_XDECREF(offset2); - return result; -} - -static long -datetime_hash(PyDateTime_DateTime *self) -{ - if (self->hashcode == -1) { - PyObject *offset; - - offset = datetime_utcoffset((PyObject *)self, NULL); - - if (offset == NULL) - return -1; - - /* Reduce this to a hash of another object. */ - if (offset == Py_None) - self->hashcode = generic_hash( - (unsigned char *)self->data, _PyDateTime_DATETIME_DATASIZE); - else { - PyObject *temp1, *temp2; - int days, seconds; - - assert(HASTZINFO(self)); - days = ymd_to_ord(GET_YEAR(self), - GET_MONTH(self), - GET_DAY(self)); - seconds = DATE_GET_HOUR(self) * 3600 + - DATE_GET_MINUTE(self) * 60 + - DATE_GET_SECOND(self); - temp1 = new_delta(days, seconds, - DATE_GET_MICROSECOND(self), - 1); - if (temp1 == NULL) { - Py_DECREF(offset); - return -1; - } - temp2 = delta_subtract(temp1, offset); - Py_DECREF(temp1); - if (temp2 == NULL) { - Py_DECREF(offset); - return -1; - } - self->hashcode = PyObject_Hash(temp2); - Py_DECREF(temp2); - } - Py_DECREF(offset); - } - return self->hashcode; -} - -static PyObject * -datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) -{ - PyObject *clone; - PyObject *tuple; - int y = GET_YEAR(self); - int m = GET_MONTH(self); - int d = GET_DAY(self); - int hh = DATE_GET_HOUR(self); - int mm = DATE_GET_MINUTE(self); - int ss = DATE_GET_SECOND(self); - int us = DATE_GET_MICROSECOND(self); - PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO:replace", - datetime_kws, - &y, &m, &d, &hh, &mm, &ss, &us, - &tzinfo)) - return NULL; - tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo); - if (tuple == NULL) - return NULL; - clone = datetime_new(Py_TYPE(self), tuple, NULL); - Py_DECREF(tuple); - return clone; -} - -static PyObject * -datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) -{ - PyObject *result; - PyObject *offset; - PyObject *temp; - PyObject *tzinfo; - static char *keywords[] = {"tz", NULL}; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:astimezone", keywords, - &PyDateTime_TZInfoType, &tzinfo)) - return NULL; - - if (!HASTZINFO(self) || self->tzinfo == Py_None) - goto NeedAware; - - /* Conversion to self's own time zone is a NOP. */ - if (self->tzinfo == tzinfo) { - Py_INCREF(self); - return (PyObject *)self; - } - - /* Convert self to UTC. */ - offset = datetime_utcoffset((PyObject *)self, NULL); - if (offset == NULL) - return NULL; - if (offset == Py_None) { - Py_DECREF(offset); - NeedAware: - PyErr_SetString(PyExc_ValueError, "astimezone() cannot be applied to " - "a naive datetime"); - return NULL; - } - - /* result = self - offset */ - result = add_datetime_timedelta(self, - (PyDateTime_Delta *)offset, -1); - Py_DECREF(offset); - if (result == NULL) - return NULL; - - /* Attach new tzinfo and let fromutc() do the rest. */ - temp = ((PyDateTime_DateTime *)result)->tzinfo; - ((PyDateTime_DateTime *)result)->tzinfo = tzinfo; - Py_INCREF(tzinfo); - Py_DECREF(temp); - - temp = result; - result = PyObject_CallMethod(tzinfo, "fromutc", "O", temp); - Py_DECREF(temp); - - return result; -} - -static PyObject * -datetime_timetuple(PyDateTime_DateTime *self) -{ - int dstflag = -1; - - if (HASTZINFO(self) && self->tzinfo != Py_None) { - PyObject * dst; - - dst = call_dst(self->tzinfo, (PyObject *)self); - if (dst == NULL) - return NULL; - - if (dst != Py_None) - dstflag = delta_bool((PyDateTime_Delta *)dst); - Py_DECREF(dst); - } - 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 * -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), - Py_None); -} - -static PyObject * -datetime_gettimetz(PyDateTime_DateTime *self) -{ - return new_time(DATE_GET_HOUR(self), - DATE_GET_MINUTE(self), - DATE_GET_SECOND(self), - DATE_GET_MICROSECOND(self), - GET_DT_TZINFO(self)); -} - -static PyObject * -datetime_utctimetuple(PyDateTime_DateTime *self) -{ - int y, m, d, hh, mm, ss; - PyObject *tzinfo; - PyDateTime_DateTime *utcself; - - tzinfo = GET_DT_TZINFO(self); - if (tzinfo == Py_None) { - utcself = self; - Py_INCREF(utcself); - } - else { - PyObject *offset; - offset = call_utcoffset(tzinfo, (PyObject *)self); - if (offset == NULL) - return NULL; - if (offset == Py_None) { - Py_DECREF(offset); - utcself = self; - Py_INCREF(utcself); - } - else { - utcself = (PyDateTime_DateTime *)add_datetime_timedelta(self, - (PyDateTime_Delta *)offset, -1); - Py_DECREF(offset); - if (utcself == NULL) - return NULL; - } - } - y = GET_YEAR(utcself); - m = GET_MONTH(utcself); - d = GET_DAY(utcself); - hh = DATE_GET_HOUR(utcself); - mm = DATE_GET_MINUTE(utcself); - ss = DATE_GET_SECOND(utcself); - - Py_DECREF(utcself); - return build_struct_time(y, m, d, hh, mm, ss, 0); -} - -/* Pickle support, a simple use of __reduce__. */ - -/* Let basestate be the non-tzinfo data string. - * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). - * So it's a tuple in any (non-error) case. - * __getstate__ isn't exposed. - */ -static PyObject * -datetime_getstate(PyDateTime_DateTime *self) -{ - PyObject *basestate; - PyObject *result = NULL; - - basestate = PyBytes_FromStringAndSize((char *)self->data, - _PyDateTime_DATETIME_DATASIZE); - if (basestate != NULL) { - if (! HASTZINFO(self) || self->tzinfo == Py_None) - result = PyTuple_Pack(1, basestate); - else - result = PyTuple_Pack(2, basestate, self->tzinfo); - Py_DECREF(basestate); - } - return result; -} - -static PyObject * -datetime_reduce(PyDateTime_DateTime *self, PyObject *arg) -{ - return Py_BuildValue("(ON)", Py_TYPE(self), datetime_getstate(self)); -} - -static PyMethodDef datetime_methods[] = { - - /* Class methods: */ - - {"now", (PyCFunction)datetime_now, - METH_VARARGS | METH_KEYWORDS | METH_CLASS, - PyDoc_STR("[tz] -> new datetime with tz's 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_KEYWORDS | METH_CLASS, - PyDoc_STR("timestamp[, tz] -> tz's local time from POSIX timestamp.")}, - - {"utcfromtimestamp", (PyCFunction)datetime_utcfromtimestamp, - METH_VARARGS | METH_CLASS, - PyDoc_STR("timestamp -> UTC datetime from a POSIX timestamp " - "(like time.time()).")}, - - {"strptime", (PyCFunction)datetime_strptime, - METH_VARARGS | METH_CLASS, - PyDoc_STR("string, format -> new datetime parsed from a string " - "(like time.strptime()).")}, - - {"combine", (PyCFunction)datetime_combine, - METH_VARARGS | METH_KEYWORDS | METH_CLASS, - PyDoc_STR("date, time -> datetime with same date and time fields")}, - - /* Instance methods: */ - - {"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 time but with tzinfo=None.")}, - - {"timetz", (PyCFunction)datetime_gettimetz, METH_NOARGS, - PyDoc_STR("Return time object with same time and tzinfo.")}, - - {"ctime", (PyCFunction)datetime_ctime, METH_NOARGS, - PyDoc_STR("Return ctime() style string.")}, - - {"timetuple", (PyCFunction)datetime_timetuple, METH_NOARGS, - PyDoc_STR("Return time tuple, compatible with time.localtime().")}, - - {"utctimetuple", (PyCFunction)datetime_utctimetuple, METH_NOARGS, - PyDoc_STR("Return UTC time tuple, compatible with time.localtime().")}, - - {"isoformat", (PyCFunction)datetime_isoformat, METH_VARARGS | 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)datetime_utcoffset, METH_NOARGS, - PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, - - {"tzname", (PyCFunction)datetime_tzname, METH_NOARGS, - PyDoc_STR("Return self.tzinfo.tzname(self).")}, - - {"dst", (PyCFunction)datetime_dst, METH_NOARGS, - PyDoc_STR("Return self.tzinfo.dst(self).")}, - - {"replace", (PyCFunction)datetime_replace, METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return datetime with new specified fields.")}, - - {"astimezone", (PyCFunction)datetime_astimezone, METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("tz -> convert to local time in new timezone tz\n")}, - - {"__reduce__", (PyCFunction)datetime_reduce, METH_NOARGS, - PyDoc_STR("__reduce__() -> (cls, state)")}, - - {NULL, NULL} -}; - -static char datetime_doc[] = -PyDoc_STR("datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])\n\ -\n\ -The year, month and day arguments are required. tzinfo may be None, or an\n\ -instance of a tzinfo subclass. The remaining arguments may be ints or longs.\n"); - -static PyNumberMethods datetime_as_number = { - datetime_add, /* nb_add */ - datetime_subtract, /* nb_subtract */ - 0, /* nb_multiply */ - 0, /* nb_remainder */ - 0, /* nb_divmod */ - 0, /* nb_power */ - 0, /* nb_negative */ - 0, /* nb_positive */ - 0, /* nb_absolute */ - 0, /* nb_bool */ -}; - -static PyTypeObject PyDateTime_DateTimeType = { - PyVarObject_HEAD_INIT(NULL, 0) - "datetime.datetime", /* tp_name */ - sizeof(PyDateTime_DateTime), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)datetime_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - (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_BASETYPE, /* tp_flags */ - datetime_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 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 */ - datetime_alloc, /* tp_alloc */ - datetime_new, /* tp_new */ - 0, /* tp_free */ -}; - -/* --------------------------------------------------------------------------- - * Module methods and initialization. - */ - -static PyMethodDef module_methods[] = { - {NULL, NULL} -}; - -/* C API. Clients get at this via PyDateTime_IMPORT, defined in - * datetime.h. - */ -static PyDateTime_CAPI CAPI = { - &PyDateTime_DateType, - &PyDateTime_DateTimeType, - &PyDateTime_TimeType, - &PyDateTime_DeltaType, - &PyDateTime_TZInfoType, - new_date_ex, - new_datetime_ex, - new_time_ex, - new_delta_ex, - datetime_fromtimestamp, - date_fromtimestamp -}; - - - -static struct PyModuleDef datetimemodule = { - PyModuleDef_HEAD_INIT, - "datetime", - "Fast implementation of the datetime type.", - -1, - module_methods, - NULL, - NULL, - NULL, - NULL -}; - -PyMODINIT_FUNC -PyInit_datetime(void) -{ - PyObject *m; /* a module object */ - PyObject *d; /* its dict */ - PyObject *x; - PyObject *delta; - - m = PyModule_Create(&datetimemodule); - if (m == NULL) - return NULL; - - if (PyType_Ready(&PyDateTime_DateType) < 0) - return NULL; - if (PyType_Ready(&PyDateTime_DateTimeType) < 0) - return NULL; - if (PyType_Ready(&PyDateTime_DeltaType) < 0) - return NULL; - if (PyType_Ready(&PyDateTime_TimeType) < 0) - return NULL; - if (PyType_Ready(&PyDateTime_TZInfoType) < 0) - return NULL; - if (PyType_Ready(&PyDateTime_TimeZoneType) < 0) - return NULL; - - /* timedelta values */ - d = PyDateTime_DeltaType.tp_dict; - - x = new_delta(0, 0, 1, 0); - if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) - return NULL; - Py_DECREF(x); - - x = new_delta(-MAX_DELTA_DAYS, 0, 0, 0); - if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) - return NULL; - 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 NULL; - 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 NULL; - Py_DECREF(x); - - x = new_date(MAXYEAR, 12, 31); - if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) - return NULL; - Py_DECREF(x); - - x = new_delta(1, 0, 0, 0); - if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) - return NULL; - Py_DECREF(x); - - /* time values */ - d = PyDateTime_TimeType.tp_dict; - - x = new_time(0, 0, 0, 0, Py_None); - if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) - return NULL; - Py_DECREF(x); - - x = new_time(23, 59, 59, 999999, Py_None); - if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) - return NULL; - Py_DECREF(x); - - x = new_delta(0, 0, 1, 0); - if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) - return NULL; - Py_DECREF(x); - - /* datetime values */ - d = PyDateTime_DateTimeType.tp_dict; - - x = new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None); - if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) - return NULL; - Py_DECREF(x); - - x = new_datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999, Py_None); - if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) - return NULL; - Py_DECREF(x); - - x = new_delta(0, 0, 1, 0); - if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) - return NULL; - Py_DECREF(x); - - /* timezone values */ - d = PyDateTime_TimeZoneType.tp_dict; - - delta = new_delta(0, 0, 0, 0); - if (delta == NULL) - return NULL; - x = new_timezone(delta, NULL); - Py_DECREF(delta); - if (x == NULL || PyDict_SetItemString(d, "utc", x) < 0) - return NULL; - PyDateTime_TimeZone_UTC = x; - - delta = new_delta(-1, 60, 0, 1); /* -23:59 */ - if (delta == NULL) - return NULL; - x = new_timezone(delta, NULL); - Py_DECREF(delta); - if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) - return NULL; - Py_DECREF(x); - - delta = new_delta(0, (23 * 60 + 59) * 60, 0, 0); /* +23:59 */ - if (delta == NULL) - return NULL; - x = new_timezone(delta, NULL); - Py_DECREF(delta); - if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) - return NULL; - Py_DECREF(x); - - /* 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_TimeType); - PyModule_AddObject(m, "time", (PyObject *) &PyDateTime_TimeType); - - Py_INCREF(&PyDateTime_DeltaType); - PyModule_AddObject(m, "timedelta", (PyObject *) &PyDateTime_DeltaType); - - Py_INCREF(&PyDateTime_TZInfoType); - PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); - - Py_INCREF(&PyDateTime_TimeZoneType); - PyModule_AddObject(m, "timezone", (PyObject *) &PyDateTime_TimeZoneType); - - x = PyCapsule_New(&CAPI, PyDateTime_CAPSULE_NAME, NULL); - if (x == NULL) - return NULL; - PyModule_AddObject(m, "datetime_CAPI", x); - - /* 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 = PyLong_FromLong(1); - us_per_ms = PyLong_FromLong(1000); - us_per_second = PyLong_FromLong(1000000); - us_per_minute = PyLong_FromLong(60000000); - seconds_per_day = PyLong_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 NULL; - - /* 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 NULL; - return m; -} - -/* --------------------------------------------------------------------------- -Some time zone algebra. For a datetime x, let - x.n = x stripped of its timezone -- its naive time. - x.o = x.utcoffset(), and assuming that doesn't raise an exception or - return None - x.d = x.dst(), and assuming that doesn't raise an exception or - return None - x.s = x's standard offset, x.o - x.d - -Now some derived rules, where k is a duration (timedelta). - -1. x.o = x.s + x.d - This follows from the definition of x.s. - -2. If x and y have the same tzinfo member, x.s = y.s. - This is actually a requirement, an assumption we need to make about - sane tzinfo classes. - -3. The naive UTC time corresponding to x is x.n - x.o. - This is again a requirement for a sane tzinfo class. - -4. (x+k).s = x.s - This follows from #2, and that datimetimetz+timedelta preserves tzinfo. - -5. (x+k).n = x.n + k - Again follows from how arithmetic is defined. - -Now we can explain tz.fromutc(x). Let's assume it's an interesting case -(meaning that the various tzinfo methods exist, and don't blow up or return -None when called). - -The function wants to return a datetime y with timezone tz, equivalent to x. -x is already in UTC. - -By #3, we want - - y.n - y.o = x.n [1] - -The algorithm starts by attaching tz to x.n, and calling that y. So -x.n = y.n at the start. Then it wants to add a duration k to y, so that [1] -becomes true; in effect, we want to solve [2] for k: - - (y+k).n - (y+k).o = x.n [2] - -By #1, this is the same as - - (y+k).n - ((y+k).s + (y+k).d) = x.n [3] - -By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start. -Substituting that into [3], - - x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving - k - (y+k).s - (y+k).d = 0; rearranging, - k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so - k = y.s - (y+k).d - -On the RHS, (y+k).d can't be computed directly, but y.s can be, and we -approximate k by ignoring the (y+k).d term at first. Note that k can't be -very large, since all offset-returning methods return a duration of magnitude -less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must -be 0, so ignoring it has no consequence then. - -In any case, the new value is - - z = y + y.s [4] - -It's helpful to step back at look at [4] from a higher level: it's simply -mapping from UTC to tz's standard time. - -At this point, if - - z.n - z.o = x.n [5] - -we have an equivalent time, and are almost done. The insecurity here is -at the start of daylight time. Picture US Eastern for concreteness. The wall -time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good -sense then. The docs ask that an Eastern tzinfo class consider such a time to -be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST -on the day DST starts. We want to return the 1:MM EST spelling because that's -the only spelling that makes sense on the local wall clock. - -In fact, if [5] holds at this point, we do have the standard-time spelling, -but that takes a bit of proof. We first prove a stronger result. What's the -difference between the LHS and RHS of [5]? Let - - diff = x.n - (z.n - z.o) [6] - -Now - z.n = by [4] - (y + y.s).n = by #5 - y.n + y.s = since y.n = x.n - x.n + y.s = since z and y are have the same tzinfo member, - y.s = z.s by #2 - x.n + z.s - -Plugging that back into [6] gives - - diff = - x.n - ((x.n + z.s) - z.o) = expanding - x.n - x.n - z.s + z.o = cancelling - - z.s + z.o = by #2 - z.d - -So diff = z.d. - -If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time -spelling we wanted in the endcase described above. We're done. Contrarily, -if z.d = 0, then we have a UTC equivalent, and are also done. - -If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to -add to z (in effect, z is in tz's standard time, and we need to shift the -local clock into tz's daylight time). - -Let - - z' = z + z.d = z + diff [7] - -and we can again ask whether - - z'.n - z'.o = x.n [8] - -If so, we're done. If not, the tzinfo class is insane, according to the -assumptions we've made. This also requires a bit of proof. As before, let's -compute the difference between the LHS and RHS of [8] (and skipping some of -the justifications for the kinds of substitutions we've done several times -already): - - diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7] - x.n - (z.n + diff - z'.o) = replacing diff via [6] - x.n - (z.n + x.n - (z.n - z.o) - z'.o) = - x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n - - z.n + z.n - z.o + z'.o = cancel z.n - - z.o + z'.o = #1 twice - -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo - z'.d - z.d - -So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal, -we've found the UTC-equivalent so are done. In fact, we stop with [7] and -return z', not bothering to compute z'.d. - -How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by -a dst() offset, and starting *from* a time already in DST (we know z.d != 0), -would have to change the result dst() returns: we start in DST, and moving -a little further into it takes us out of DST. - -There isn't a sane case where this can happen. The closest it gets is at -the end of DST, where there's an hour in UTC with no spelling in a hybrid -tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During -that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM -UTC) because the docs insist on that, but 0:MM is taken as being in daylight -time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local -clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in -standard time. Since that's what the local clock *does*, we want to map both -UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous -in local time, but so it goes -- it's the way the local clock works. - -When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0, -so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going. -z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8] -(correctly) concludes that z' is not UTC-equivalent to x. - -Because we know z.d said z was in daylight time (else [5] would have held and -we would have stopped then), and we know z.d != z'.d (else [8] would have held -and we would have stopped then), and there are only 2 possible values dst() can -return in Eastern, it follows that z'.d must be 0 (which it is in the example, -but the reasoning doesn't depend on the example -- it depends on there being -two possible dst() outcomes, one zero and the other non-zero). Therefore -z' must be in standard time, and is the spelling we want in this case. - -Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is -concerned (because it takes z' as being in standard time rather than the -daylight time we intend here), but returning it gives the real-life "local -clock repeats an hour" behavior when mapping the "unspellable" UTC hour into -tz. - -When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with -the 1:MM standard time spelling we want. - -So how can this break? One of the assumptions must be violated. Two -possibilities: - -1) [2] effectively says that y.s is invariant across all y belong to a given - time zone. This isn't true if, for political reasons or continental drift, - a region decides to change its base offset from UTC. - -2) There may be versions of "double daylight" time where the tail end of - the analysis gives up a step too early. I haven't thought about that - enough to say. - -In any case, it's clear that the default fromutc() is strong enough to handle -"almost all" time zones: so long as the standard offset is invariant, it -doesn't matter if daylight time transition points change from year to year, or -if daylight time is skipped in some years; it doesn't matter how large or -small dst() may get within its bounds; and it doesn't even matter if some -perverse time zone returns a negative dst()). So a breaking case must be -pretty bizarre, and a tzinfo subclass can override fromutc() if it is. ---------------------------------------------------------------------------- */ diff --git a/PC/config.c b/PC/config.c index 28d02b1..7d501b5 100644 --- a/PC/config.c +++ b/PC/config.c @@ -43,7 +43,7 @@ extern PyObject* PyInit__sre(void); extern PyObject* PyInit_parser(void); extern PyObject* PyInit_winreg(void); extern PyObject* PyInit__struct(void); -extern PyObject* PyInit_datetime(void); +extern PyObject* PyInit__datetime(void); extern PyObject* PyInit__functools(void); extern PyObject* PyInit__json(void); extern PyObject* PyInit_zlib(void); @@ -116,7 +116,7 @@ struct _inittab _PyImport_Inittab[] = { {"parser", PyInit_parser}, {"winreg", PyInit_winreg}, {"_struct", PyInit__struct}, - {"datetime", PyInit_datetime}, + {"_datetime", PyInit__datetime}, {"_functools", PyInit__functools}, {"_json", PyInit__json}, diff --git a/PCbuild/pythoncore.vcproj b/PCbuild/pythoncore.vcproj index b1a583f..ad0afd3 100644 --- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@ -1068,7 +1068,7 @@ >