From 7f9dbe59bf17cfdf069d32e213a349a8658913cc Mon Sep 17 00:00:00 2001 From: Pavel Shramov Date: Fri, 28 Aug 2020 12:47:36 +0300 Subject: [PATCH 1/2] wip: bpo-15443 Nanoseconds support for datetime objects Changed microseconds to nanoseconds in timedelta, time and datetime objects. All __repr__ and __str__ methods are unchanged to simplify testing. Pickling is (hopefully) backward compatible. --- Lib/datetime.py | 302 +++++++++++++++++++++++-------------- Lib/test/datetimetester.py | 129 ++++++++++------ 2 files changed, 275 insertions(+), 156 deletions(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index 3090978508c921..ffe5a91d7a1c4d 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -158,26 +158,32 @@ def _build_struct_time(y, m, d, hh, mm, ss, dstflag): 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, timespec='auto'): +def _format_time(hh, mm, ss, ns, timespec='auto'): specs = { 'hours': '{:02d}', 'minutes': '{:02d}:{:02d}', 'seconds': '{:02d}:{:02d}:{:02d}', 'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}', - 'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}' + 'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}', + 'nanoseconds': '{:02d}:{:02d}:{:02d}.{:09d}' } if timespec == 'auto': # Skip trailing microseconds when us==0. - timespec = 'microseconds' if us else 'seconds' + timespec = 'nanoseconds' if ns else 'seconds' + if ns and ns % 1000 == 0: + timespec = 'microseconds' + ns //= 1000 + elif timespec == 'microseconds': + ns //= 1000 elif timespec == 'milliseconds': - us //= 1000 + ns //= 1000000 try: fmt = specs[timespec] except KeyError: raise ValueError('Unknown timespec value') else: - return fmt.format(hh, mm, ss, us) + return fmt.format(hh, mm, ss, ns) def _format_offset(off): s = '' @@ -279,7 +285,7 @@ def _parse_isoformat_date(dtstr): return [year, month, day] def _parse_hh_mm_ss_ff(tstr): - # Parses things of the form HH[:MM[:SS[.fff[fff]]]] + # Parses things of the form HH[:MM[:SS[.fff[fff[fff]]]]] len_str = len(tstr) time_comps = [0, 0, 0, 0] @@ -308,17 +314,19 @@ def _parse_hh_mm_ss_ff(tstr): pos += 1 len_remainder = len_str - pos - if len_remainder not in (3, 6): - raise ValueError('Invalid microsecond component') + if len_remainder not in (3, 6, 9): + raise ValueError('Invalid subsecond component') time_comps[3] = int(tstr[pos:]) if len_remainder == 3: + time_comps[3] *= 1000000 + elif len_remainder == 6: time_comps[3] *= 1000 return time_comps def _parse_isoformat_time(tstr): - # Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]] + # Format supported is HH[:MM[:SS[.fff[fff[fff]]]]][+HH:MM[:SS[.ffffff[fff]]]] len_str = len(tstr) if len_str < 2: raise ValueError('Isoformat time too short') @@ -337,8 +345,9 @@ def _parse_isoformat_time(tstr): # HH:MM len: 5 # HH:MM:SS len: 8 # HH:MM:SS.ffffff len: 15 + # HH:MM:SS.fffffffff len: 18 - if len(tzstr) not in (5, 8, 15): + if len(tzstr) not in (5, 8, 15, 18): raise ValueError('Malformed time zone string') tz_comps = _parse_hh_mm_ss_ff(tzstr) @@ -348,7 +357,7 @@ def _parse_isoformat_time(tstr): tzsign = -1 if tstr[tz_pos - 1] == '-' else 1 td = timedelta(hours=tz_comps[0], minutes=tz_comps[1], - seconds=tz_comps[2], microseconds=tz_comps[3]) + seconds=tz_comps[2], nanoseconds=tz_comps[3]) tzi = timezone(tzsign * td) @@ -394,11 +403,12 @@ def _check_date_fields(year, month, day): raise ValueError('day must be in 1..%d' % dim, day) return year, month, day -def _check_time_fields(hour, minute, second, microsecond, fold): +def _check_time_fields(hour, minute, second, microsecond, nanosecond, fold): hour = _index(hour) minute = _index(minute) second = _index(second) microsecond = _index(microsecond) + nanosecond = _index(nanosecond) if not 0 <= hour <= 23: raise ValueError('hour must be in 0..23', hour) if not 0 <= minute <= 59: @@ -407,9 +417,13 @@ def _check_time_fields(hour, minute, second, microsecond, fold): raise ValueError('second must be in 0..59', second) if not 0 <= microsecond <= 999999: raise ValueError('microsecond must be in 0..999999', microsecond) + if not 0 <= nanosecond <= 999999999: + raise ValueError('nanosecond must be in 0..999999999', microsecond) if fold not in (0, 1): raise ValueError('fold must be either 0 or 1', fold) - return hour, minute, second, microsecond, fold + if nanosecond == 0: + nanosecond = microsecond * 1000 + return hour, minute, second, nanosecond, fold def _check_tzinfo_arg(tz): if tz is not None and not isinstance(tz, tzinfo): @@ -453,13 +467,13 @@ class timedelta: returning a timedelta, and addition or subtraction of a datetime and a timedelta giving a datetime. - Representation: (days, seconds, microseconds). Why? Because I + Representation: (days, seconds, nanoseconds). Why? Because I felt like it. """ - __slots__ = '_days', '_seconds', '_microseconds', '_hashcode' + __slots__ = '_days', '_seconds', '_nanoseconds', '_hashcode' def __new__(cls, days=0, seconds=0, microseconds=0, - milliseconds=0, minutes=0, hours=0, weeks=0): + milliseconds=0, minutes=0, hours=0, weeks=0, nanoseconds=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 @@ -468,6 +482,17 @@ def __new__(cls, days=0, seconds=0, microseconds=0, # guide the C implementation; it's way more convoluted than speed- # ignoring auto-overflow-to-long idiomatic Python could be. + if isinstance(microseconds, tuple) and all(x == 0 for x in (milliseconds, minutes, hours, weeks, nanoseconds)): + # Pickle support + res, ns = microseconds + assert res == 9 # We've got pickled nanoseconds + self = object.__new__(cls) + self._days = days + self._seconds = seconds + self._nanoseconds = ns + self._hashcode = -1 + return self + # XXX Check that all inputs are ints or floats. # Final values, all integer. @@ -478,6 +503,7 @@ def __new__(cls, days=0, seconds=0, microseconds=0, days += weeks*7 seconds += minutes*60 + hours*3600 microseconds += milliseconds*1000 + nanoseconds += microseconds*1000 # Get rid of all fractions, and normalize s and us. # Take a deep breath . @@ -517,37 +543,37 @@ def __new__(cls, days=0, seconds=0, microseconds=0, 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 + nsdouble = secondsfrac * 1e9 + assert abs(nsdouble) < 2.1e9 # exact value not critical # secondsfrac isn't referenced again - if isinstance(microseconds, float): - microseconds = round(microseconds + usdouble) - seconds, microseconds = divmod(microseconds, 1000000) + if isinstance(nanoseconds, float): + nanoseconds = round(nanoseconds + nsdouble) + seconds, nanoseconds = divmod(nanoseconds, 1000000000) days, seconds = divmod(seconds, 24*3600) d += days s += seconds else: - microseconds = int(microseconds) - seconds, microseconds = divmod(microseconds, 1000000) + nanoseconds = int(nanoseconds) + seconds, nanoseconds = divmod(nanoseconds, 1000000000) days, seconds = divmod(seconds, 24*3600) d += days s += seconds - microseconds = round(microseconds + usdouble) + nanoseconds = round(nanoseconds + nsdouble) assert isinstance(s, int) - assert isinstance(microseconds, int) + assert isinstance(nanoseconds, int) assert abs(s) <= 3 * 24 * 3600 - assert abs(microseconds) < 3.1e6 + assert abs(nanoseconds) < 3.1e9 # Just a little bit of carrying possible for microseconds and seconds. - seconds, us = divmod(microseconds, 1000000) + seconds, ns = divmod(nanoseconds, 1000000000) s += seconds 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 + assert isinstance(ns, int) and 0 <= ns < 1000000000 if abs(d) > 999999999: raise OverflowError("timedelta # of days is too large: %d" % d) @@ -555,7 +581,7 @@ def __new__(cls, days=0, seconds=0, microseconds=0, self = object.__new__(cls) self._days = d self._seconds = s - self._microseconds = us + self._nanoseconds = ns self._hashcode = -1 return self @@ -565,8 +591,12 @@ def __repr__(self): args.append("days=%d" % self._days) if self._seconds: args.append("seconds=%d" % self._seconds) - if self._microseconds: - args.append("microseconds=%d" % self._microseconds) + if self._nanoseconds: + us, ns = divmod(self._nanoseconds, 1000) + if ns == 0: + args.append("microseconds=%d" % us) + else: + args.append("nanoseconds=%d" % self._nanoseconds) if not args: args.append('0') return "%s.%s(%s)" % (self.__class__.__module__, @@ -581,14 +611,17 @@ def __str__(self): 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 + if self._nanoseconds: + us, ns = divmod(self._nanoseconds, 1000) + s = s + ".%06d" % us + if ns: + s = s + "%03d" % ns return s def total_seconds(self): """Total seconds in the duration.""" - return ((self.days * 86400 + self.seconds) * 10**6 + - self.microseconds) / 10**6 + return ((self.days * 86400 + self.seconds) * 10**9 + + self.nanoseconds) / 10**9 # Read-only field accessors @property @@ -604,7 +637,12 @@ def seconds(self): @property def microseconds(self): """microseconds""" - return self._microseconds + return self._nanoseconds // 1000 + + @property + def nanoseconds(self): + """nanoseconds""" + return self._nanoseconds def __add__(self, other): if isinstance(other, timedelta): @@ -612,7 +650,7 @@ def __add__(self, other): # our __class__ here, but need a real timedelta return timedelta(self._days + other._days, self._seconds + other._seconds, - self._microseconds + other._microseconds) + nanoseconds = self._nanoseconds + other._nanoseconds) return NotImplemented __radd__ = __add__ @@ -623,7 +661,7 @@ def __sub__(self, other): # our __class__ here, but need a real timedelta return timedelta(self._days - other._days, self._seconds - other._seconds, - self._microseconds - other._microseconds) + nanoseconds = self._nanoseconds - other._nanoseconds) return NotImplemented def __rsub__(self, other): @@ -636,7 +674,7 @@ def __neg__(self): # our __class__ here, but need a real timedelta return timedelta(-self._days, -self._seconds, - -self._microseconds) + nanoseconds = -self._nanoseconds) def __pos__(self): return self @@ -653,51 +691,54 @@ def __mul__(self, other): # our __class__ here, but need a real timedelta return timedelta(self._days * other, self._seconds * other, - self._microseconds * other) + nanoseconds = self._nanoseconds * other) if isinstance(other, float): - usec = self._to_microseconds() + nsec = self._to_nanoseconds() a, b = other.as_integer_ratio() - return timedelta(0, 0, _divide_and_round(usec * a, b)) + return timedelta(0, 0, nanoseconds = _divide_and_round(nsec * a, b)) return NotImplemented __rmul__ = __mul__ def _to_microseconds(self): - return ((self._days * (24*3600) + self._seconds) * 1000000 + - self._microseconds) + return self._to_nanoseconds() // 1000 + + def _to_nanoseconds(self): + return ((self._days * (24*3600) + self._seconds) * 1000000000 + + self._nanoseconds) def __floordiv__(self, other): if not isinstance(other, (int, timedelta)): return NotImplemented - usec = self._to_microseconds() + nsec = self._to_nanoseconds() if isinstance(other, timedelta): - return usec // other._to_microseconds() + return nsec // other._to_nanoseconds() if isinstance(other, int): - return timedelta(0, 0, usec // other) + return timedelta(0, 0, nanoseconds = nsec // other) def __truediv__(self, other): if not isinstance(other, (int, float, timedelta)): return NotImplemented - usec = self._to_microseconds() + nsec = self._to_nanoseconds() if isinstance(other, timedelta): - return usec / other._to_microseconds() + return nsec / other._to_nanoseconds() if isinstance(other, int): - return timedelta(0, 0, _divide_and_round(usec, other)) + return timedelta(0, 0, nanoseconds = _divide_and_round(nsec, other)) if isinstance(other, float): a, b = other.as_integer_ratio() - return timedelta(0, 0, _divide_and_round(b * usec, a)) + return timedelta(0, 0, nanoseconds = _divide_and_round(b * nsec, a)) def __mod__(self, other): if isinstance(other, timedelta): - r = self._to_microseconds() % other._to_microseconds() - return timedelta(0, 0, r) + r = self._to_nanoseconds() % other._to_nanoseconds() + return timedelta(0, 0, nanoseconds = 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) + q, r = divmod(self._to_nanoseconds(), + other._to_nanoseconds()) + return q, timedelta(0, 0, nanoseconds = r) return NotImplemented # Comparisons of timedelta objects with other. @@ -744,20 +785,21 @@ def __hash__(self): def __bool__(self): return (self._days != 0 or self._seconds != 0 or - self._microseconds != 0) + self._nanoseconds != 0) # Pickle support. def _getstate(self): - return (self._days, self._seconds, self._microseconds) + # Last field is tuple with resolution to support future change of nanoseconds to something else + return (self._days, self._seconds, (9, self._nanoseconds)) 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) + nanoseconds=999999999) +timedelta.resolution = timedelta(nanoseconds=1) class date: """Concrete date type. @@ -1232,21 +1274,21 @@ class time: dst() Properties (readonly): - hour, minute, second, microsecond, tzinfo, fold + hour, minute, second, microsecond, nanosecond, tzinfo, fold """ - __slots__ = '_hour', '_minute', '_second', '_microsecond', '_tzinfo', '_hashcode', '_fold' + __slots__ = '_hour', '_minute', '_second', '_nanosecond', '_tzinfo', '_hashcode', '_fold' - def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0): + def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, nanosecond=0, fold=0): """Constructor. Arguments: hour, minute (required) - second, microsecond (default to zero) + second, microsecond, nanosecond (default to zero) tzinfo (default to None) fold (keyword only, default to zero) """ - if (isinstance(hour, (bytes, str)) and len(hour) == 6 and + if (isinstance(hour, (bytes, str)) and len(hour) in (6, 7) and ord(hour[0:1])&0x7F < 24): # Pickle support if isinstance(hour, str): @@ -1262,14 +1304,14 @@ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold self.__setstate(hour, minute or None) self._hashcode = -1 return self - hour, minute, second, microsecond, fold = _check_time_fields( - hour, minute, second, microsecond, fold) + hour, minute, second, nanosecond, fold = _check_time_fields( + hour, minute, second, microsecond, nanosecond, fold) _check_tzinfo_arg(tzinfo) self = object.__new__(cls) self._hour = hour self._minute = minute self._second = second - self._microsecond = microsecond + self._nanosecond = nanosecond self._tzinfo = tzinfo self._hashcode = -1 self._fold = fold @@ -1294,7 +1336,12 @@ def second(self): @property def microsecond(self): """microsecond (0-999999)""" - return self._microsecond + return self._nanosecond // 1000 + + @property + def nanosecond(self): + """nanosecond (0-999999999)""" + return self._nanosecond @property def tzinfo(self): @@ -1354,9 +1401,9 @@ def _cmp(self, other, allow_mixed=False): if base_compare: return _cmp((self._hour, self._minute, self._second, - self._microsecond), + self._nanosecond), (other._hour, other._minute, other._second, - other._microsecond)) + other._nanosecond)) if myoff is None or otoff is None: if allow_mixed: return 2 # arbitrary non-zero value @@ -1364,8 +1411,8 @@ def _cmp(self, other, allow_mixed=False): 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)) + return _cmp((myhhmm, self._second, self._nanosecond), + (othhmm, other._second, other._nanosecond)) def __hash__(self): """Hash.""" @@ -1383,9 +1430,9 @@ def __hash__(self): assert not m % timedelta(minutes=1), "whole minute" m //= timedelta(minutes=1) if 0 <= h < 24: - self._hashcode = hash(time(h, m, self.second, self.microsecond)) + self._hashcode = hash(time(h, m, self.second, nanosecond=self.nanosecond)) else: - self._hashcode = hash((h, m, self.second, self.microsecond)) + self._hashcode = hash((h, m, self.second, self.nanosecond)) return self._hashcode # Conversion to string @@ -1397,8 +1444,12 @@ def _tzstr(self): def __repr__(self): """Convert to formal string, for repr().""" - if self._microsecond != 0: - s = ", %d, %d" % (self._second, self._microsecond) + if self._nanosecond != 0: + us, ns = divmod(self._nanosecond, 1000) + if ns == 0: + s = ", %d, %d" % (self._second, us) + else: + s = ", %d, nanosecond=%d" % (self._second, self._nanosecond) elif self._second != 0: s = ", %d" % self._second else: @@ -1424,7 +1475,7 @@ def isoformat(self, timespec='auto'): terms of the time to include. """ s = _format_time(self._hour, self._minute, self._second, - self._microsecond, timespec) + self._nanosecond, timespec) tz = self._tzstr() if tz: s += tz @@ -1439,7 +1490,8 @@ def fromisoformat(cls, time_string): raise TypeError('fromisoformat: argument must be str') try: - return cls(*_parse_isoformat_time(time_string)) + h, m, s, ns, tzinfo = _parse_isoformat_time(time_string) + return cls(h, m, s, nanosecond=ns, tzinfo=tzinfo) except Exception: raise ValueError(f'Invalid isoformat string: {time_string!r}') @@ -1502,7 +1554,7 @@ def dst(self): return offset def replace(self, hour=None, minute=None, second=None, microsecond=None, - tzinfo=True, *, fold=None): + tzinfo=True, *, nanosecond=None, fold=None): """Return a new time with new values for the specified fields.""" if hour is None: hour = self.hour @@ -1510,24 +1562,28 @@ def replace(self, hour=None, minute=None, second=None, microsecond=None, minute = self.minute if second is None: second = self.second - if microsecond is None: - microsecond = self.microsecond + if nanosecond is None: + if microsecond is None: + nanosecond = self.nanosecond + else: + nanosecond = microsecond * 1000 if tzinfo is True: tzinfo = self.tzinfo if fold is None: fold = self._fold - return type(self)(hour, minute, second, microsecond, tzinfo, fold=fold) + return type(self)(hour, minute, second, nanosecond=nanosecond, tzinfo=tzinfo, fold=fold) # Pickle support. def _getstate(self, protocol=3): - us2, us3 = divmod(self._microsecond, 256) - us1, us2 = divmod(us2, 256) + ns3, ns4 = divmod(self._nanosecond, 256) + ns2, ns3 = divmod(ns3, 256) + ns1, ns2 = divmod(ns2, 256) h = self._hour if self._fold and protocol > 3: h += 128 basestate = bytes([h, self._minute, self._second, - us1, us2, us3]) + ns1, ns2, ns3, ns4]) if self._tzinfo is None: return (basestate,) else: @@ -1536,14 +1592,20 @@ def _getstate(self, protocol=3): def __setstate(self, string, tzinfo): if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class): raise TypeError("bad tzinfo state arg") - h, self._minute, self._second, us1, us2, us3 = string + if len(string) == 6: + h, self._minute, self._second, us1, us2, us3 = string + else: + h, self._minute, self._second, ns1, ns2, ns3, ns4 = string if h > 127: self._fold = 1 self._hour = h - 128 else: self._fold = 0 self._hour = h - self._microsecond = (((us1 << 8) | us2) << 8) | us3 + if len(string) == 6: + self._nanosecond = 1000 * ((((us1 << 8) | us2) << 8) | us3) + else: + self._nanosecond = (((((ns1 << 8) | ns2) << 8) | ns3) << 8) | ns4 self._tzinfo = tzinfo def __reduce_ex__(self, protocol): @@ -1568,7 +1630,7 @@ class datetime(date): __slots__ = date.__slots__ + time.__slots__ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, - microsecond=0, tzinfo=None, *, fold=0): + microsecond=0, tzinfo=None, *, nanosecond=0, fold=0): if (isinstance(year, (bytes, str)) and len(year) == 10 and 1 <= ord(year[2:3])&0x7F <= 12): # Pickle support @@ -1586,8 +1648,8 @@ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, self._hashcode = -1 return self year, month, day = _check_date_fields(year, month, day) - hour, minute, second, microsecond, fold = _check_time_fields( - hour, minute, second, microsecond, fold) + hour, minute, second, nanosecond, fold = _check_time_fields( + hour, minute, second, microsecond, nanosecond, fold) _check_tzinfo_arg(tzinfo) self = object.__new__(cls) self._year = year @@ -1596,7 +1658,7 @@ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, self._hour = hour self._minute = minute self._second = second - self._microsecond = microsecond + self._nanosecond = nanosecond self._tzinfo = tzinfo self._hashcode = -1 self._fold = fold @@ -1621,7 +1683,12 @@ def second(self): @property def microsecond(self): """microsecond (0-999999)""" - return self._microsecond + return self._nanosecond // 1000 + + @property + def nanosecond(self): + """nanosecond (0-999999999)""" + return self._nanosecond @property def tzinfo(self): @@ -1739,7 +1806,9 @@ def fromisoformat(cls, date_string): else: time_components = [0, 0, 0, 0, None] - return cls(*(date_components + time_components)) + ns = time_components[3] + time_components[3] = 0 + return cls(*(date_components + time_components), nanosecond=ns) def timetuple(self): "Return local time tuple compatible with time.localtime()." @@ -1821,7 +1890,7 @@ def timetz(self): def replace(self, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None, tzinfo=True, - *, fold=None): + *, nanosecond=None, fold=None): """Return a new datetime with new values for the specified fields.""" if year is None: year = self.year @@ -1835,14 +1904,19 @@ def replace(self, year=None, month=None, day=None, hour=None, minute = self.minute if second is None: second = self.second - if microsecond is None: - microsecond = self.microsecond + if nanosecond is not None and microsecond is not None: + raise ValueError("Both microsecond and nanosecond are not None") + if nanosecond is None: + if microsecond is None: + nanosecond = self.nanosecond + else: + nanosecond = microsecond * 1000 if tzinfo is True: tzinfo = self.tzinfo if fold is None: fold = self.fold return type(self)(year, month, day, hour, minute, second, - microsecond, tzinfo, fold=fold) + nanosecond=nanosecond, tzinfo=tzinfo, fold=fold) def _local_timezone(self): if self.tzinfo is None: @@ -1896,8 +1970,8 @@ def ctime(self): def isoformat(self, sep='T', timespec='auto'): """Return the time formatted according to ISO. - The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'. - By default, the fractional part is omitted if self.microsecond == 0. + The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmmmmm'. + By default, the fractional part is omitted if self.nanosecond == 0. If self.tzinfo is not None, the UTC offset is also attached, giving giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'. @@ -1910,7 +1984,7 @@ def isoformat(self, sep='T', timespec='auto'): """ s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) + _format_time(self._hour, self._minute, self._second, - self._microsecond, timespec)) + self._nanosecond, timespec)) off = self.utcoffset() tz = _format_offset(off) @@ -1921,10 +1995,14 @@ def isoformat(self, sep='T', timespec='auto'): def __repr__(self): """Convert to formal string, for repr().""" + us, ns = divmod(self._nanosecond, 1000) 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] + self._hour, self._minute, self._second] + if self._nanosecond != 0: + if ns == 0: + L.append(us) + else: + L.append("nanosecond=" + str(self._nanosecond)) if L[-1] == 0: del L[-1] s = "%s.%s(%s)" % (self.__class__.__module__, @@ -2049,10 +2127,10 @@ def _cmp(self, other, allow_mixed=False): if base_compare: return _cmp((self._year, self._month, self._day, self._hour, self._minute, self._second, - self._microsecond), + self._nanosecond), (other._year, other._month, other._day, other._hour, other._minute, other._second, - other._microsecond)) + other._nanosecond)) if myoff is None or otoff is None: if allow_mixed: return 2 # arbitrary non-zero value @@ -2072,14 +2150,14 @@ def __add__(self, other): hours=self._hour, minutes=self._minute, seconds=self._second, - microseconds=self._microsecond) + nanoseconds=self._nanosecond) delta += other hour, rem = divmod(delta.seconds, 3600) minute, second = divmod(rem, 60) if 0 < delta.days <= _MAXORDINAL: return type(self).combine(date.fromordinal(delta.days), time(hour, minute, second, - delta.microseconds, + nanosecond=delta.nanoseconds, tzinfo=self._tzinfo)) raise OverflowError("result out of range") @@ -2098,7 +2176,7 @@ def __sub__(self, other): secs2 = other._second + other._minute * 60 + other._hour * 3600 base = timedelta(days1 - days2, secs1 - secs2, - self._microsecond - other._microsecond) + nanoseconds = self._nanosecond - other._nanosecond) if self._tzinfo is other._tzinfo: return base myoff = self.utcoffset() @@ -2121,14 +2199,14 @@ def __hash__(self): else: days = _ymd2ord(self.year, self.month, self.day) seconds = self.hour * 3600 + self.minute * 60 + self.second - self._hashcode = hash(timedelta(days, seconds, self.microsecond) - tzoff) + self._hashcode = hash(timedelta(days, seconds, nanoseconds = self._nanosecond) - tzoff) return self._hashcode # Pickle support. def _getstate(self, protocol=3): yhi, ylo = divmod(self._year, 256) - us2, us3 = divmod(self._microsecond, 256) + us2, us3 = divmod(self.microsecond, 256) us1, us2 = divmod(us2, 256) m = self._month if self._fold and protocol > 3: @@ -2153,7 +2231,7 @@ def __setstate(self, string, tzinfo): self._fold = 0 self._month = m self._year = yhi * 256 + ylo - self._microsecond = (((us1 << 8) | us2) << 8) | us3 + self._nanosecond = 1000 * ((((us1 << 8) | us2) << 8) | us3) self._tzinfo = tzinfo def __reduce_ex__(self, protocol): diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 8b61c26f9e5c24..6645890c1619be 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -558,34 +558,34 @@ def test_computations(self): 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) + res = td.resolution + eq((3*res) * 0.5, 2*res) + eq((5*res) * 0.5, 2*res) + eq(0.5 * (3*res), 2*res) + eq(0.5 * (5*res), 2*res) + eq((-3*res) * 0.5, -2*res) + eq((-5*res) * 0.5, -2*res) # Issue #23521 eq(td(seconds=1) * 0.123456, td(microseconds=123456)) - eq(td(seconds=1) * 0.6112295, td(microseconds=611229)) + eq(td(seconds=1) * 0.6112295, td(microseconds=611229 if td.resolution == td(microseconds=1) else 611229.5)) # 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) + eq((3*res) / 2, 2*res) + eq((5*res) / 2, 2*res) + eq((-3*res) / 2.0, -2*res) + eq((-5*res) / 2.0, -2*res) + eq((3*res) / -2, -2*res) + eq((5*res) / -2, -2*res) + eq((3*res) / -2.0, -2*res) + eq((5*res) / -2.0, -2*res) for i in range(-10, 10): - eq((i*us/3)//us, round(i/3)) + eq((i*res/3)//res, round(i/3)) for i in range(-10, 10): - eq((i*us/-3)//us, round(i/-3)) + eq((i*res/-3)//res, round(i/-3)) # Issue #23521 - eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229)) + eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229 if td.resolution == td(microseconds=1) else 611229.5)) # Issue #11576 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998), @@ -736,6 +736,9 @@ def test_str(self): microseconds=999999)), "999999999 days, 23:59:59.999999") + if td.resolution < td(microseconds=1): + eq(str(td(nanoseconds=1)), "0:00:00.000000001") + def test_repr(self): name = 'datetime.' + self.theclass.__name__ self.assertEqual(repr(self.theclass(1)), @@ -755,6 +758,12 @@ def test_repr(self): self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)), "%s(seconds=1, microseconds=100)" % name) + if timedelta.resolution < timedelta(microseconds=1): + self.assertEqual(repr(self.theclass(nanoseconds=100200)), + "%s(nanoseconds=100200)" % name) + self.assertEqual(repr(self.theclass(seconds=1, nanoseconds=100200)), + "%s(seconds=1, nanoseconds=100200)" % name) + def test_roundtrip(self): for td in (timedelta(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999), @@ -779,8 +788,12 @@ def test_resolution_info(self): 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)) + if timedelta.resolution == timedelta(microseconds=1): + self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, microseconds=1e6-1)) + self.assertEqual(timedelta.resolution, timedelta(0, 0, microseconds=1)) + else: + self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, nanoseconds=1e9-1)) + self.assertEqual(timedelta.resolution, timedelta(0, 0, nanoseconds=1)) def test_overflow(self): tiny = timedelta.resolution @@ -815,33 +828,42 @@ def test_microsecond_rounding(self): 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.5/1000), td(microseconds=0)) - eq(td(milliseconds=-0.5/1000), td(microseconds=-0)) - eq(td(milliseconds=0.6/1000), td(microseconds=1)) - eq(td(milliseconds=-0.6/1000), td(microseconds=-1)) - eq(td(milliseconds=1.5/1000), td(microseconds=2)) - eq(td(milliseconds=-1.5/1000), td(microseconds=-2)) - eq(td(seconds=0.5/10**6), td(microseconds=0)) - eq(td(seconds=-0.5/10**6), td(microseconds=-0)) - eq(td(seconds=1/2**7), td(microseconds=7812)) - eq(td(seconds=-1/2**7), td(microseconds=-7812)) + if td.resolution == td(microseconds=1): + td_big = lambda x: td(seconds=x) + td_tick = lambda x: td(microseconds=x) + ticks_per_second = 1e6 + else: + td_big = lambda x: td(milliseconds=x) + td_tick = lambda x: td(nanoseconds=x) + ticks_per_second = 1e9 + + eq(td_big(0.4/1000000), td(0)) # rounds to 0 + eq(td_big(-0.4/1000000), td(0)) # rounds to 0 + eq(td_big(0.5/1000000), td_tick(0)) + eq(td_big(-0.5/1000000), td_tick(-0)) + eq(td_big(0.6/1000000), td_tick(1)) + eq(td_big(-0.6/1000000), td_tick(-1)) + eq(td_big(1.5/1000000), td_tick(2)) + eq(td_big(-1.5/1000000), td_tick(-2)) + eq(td_big(0.5/10**6), td_tick(0)) + eq(td_big(-0.5/10**6), td_tick(-0)) + eq(td_big(1/2**7), td_tick(7812)) + eq(td_big(-1/2**7), td_tick(-7812)) # 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)) + ticks_per_hour = 3600 * ticks_per_second + ticks_per_day = ticks_per_hour * 24 + eq(td(days=.4/ticks_per_day), td(0)) + eq(td(hours=.2/ticks_per_hour), td(0)) + eq(td(days=.4/ticks_per_day, hours=.2/ticks_per_hour), td_tick(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)) + eq(td(days=-.4/ticks_per_day), td(0)) + eq(td(hours=-.2/ticks_per_hour), td(0)) + eq(td(days=-.4/ticks_per_day, hours=-.2/ticks_per_hour), td_tick(-1)) # Test for a patch in Issue 8860 - eq(td(microseconds=0.5), 0.5*td(microseconds=1.0)) - eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution) + eq(td_tick(0.5), 0.5*td_tick(1.0)) + eq(td_tick(0.5)//td.resolution, 0.5*td.resolution//td.resolution) def test_massive_normalization(self): td = timedelta(microseconds=-1) @@ -1976,7 +1998,10 @@ def test_basic_attributes_nonzero(self): self.assertEqual(dt.microsecond, 8000) def test_roundtrip(self): - for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7), + test = self.theclass(1, 2, 3, 4, 5, 6, 7) + if timedelta.resolution < timedelta(microseconds=1): + test = self.theclass(1, 2, 3, 4, 5, 6, nanosecond=7) + for dt in (test, self.theclass.now()): # Verify dt -> string -> datetime identity. s = repr(dt) @@ -1989,6 +2014,8 @@ def test_roundtrip(self): dt2 = self.theclass(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond) + if timedelta.resolution < timedelta(microseconds=1): + dt2 = dt2.replace(nanosecond=dt.nanosecond) self.assertEqual(dt, dt2) def test_isoformat(self): @@ -2035,6 +2062,16 @@ def test_isoformat(self): t = self.theclass(2, 3, 2, tzinfo=tz) self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16") + if timedelta.resolution < timedelta(microseconds=1): + t = self.theclass(1, 2, 3, 4, 5, 1, nanosecond=123456) + self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01") + self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000") + self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123") + self.assertEqual(t.isoformat(timespec='nanoseconds'), "0001-02-03T04:05:01.000123456") + self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123456") + + self.assertEqual(str(t), "0001-02-03 04:05:01.000123456") + def test_isoformat_timezone(self): tzoffsets = [ ('05:00', timedelta(hours=5)), @@ -3335,6 +3372,10 @@ def test_repr(self): self.assertEqual(repr(self.theclass(23, 15, 0, 0)), "%s(23, 15)" % name) + if timedelta.resolution < timedelta(microseconds=1): + self.assertEqual(repr(self.theclass(1, 2, 3, nanosecond=4)), + "%s(1, 2, 3, nanosecond=4)" % name) + def test_resolution_info(self): self.assertIsInstance(self.theclass.min, self.theclass) self.assertIsInstance(self.theclass.max, self.theclass) From dbbaf7a2af6a84ccfc06f52192b198dca59e2541 Mon Sep 17 00:00:00 2001 From: Pavel Shramov Date: Thu, 1 Oct 2020 08:10:42 +0300 Subject: [PATCH 2/2] wip: datetime: Replace char arrays with packed structures Structures provides same layout but allows compiler to check for invalid access. Downside is in structure all fields are in native byte order and needs to be converted to big-endian in pickle/unpickle. --- Include/datetime.h | 69 +++++++++++++++++++++++++-------------- Modules/_datetimemodule.c | 55 ++++++++++++++----------------- 2 files changed, 68 insertions(+), 56 deletions(-) diff --git a/Include/datetime.h b/Include/datetime.h index bb565201a164d7..8886b877bc028c 100644 --- a/Include/datetime.h +++ b/Include/datetime.h @@ -21,14 +21,40 @@ extern "C" { * 10 */ +typedef struct +{ + unsigned short year; + unsigned char month; + unsigned char day; +} __attribute__((packed)) _PyDateTime_DateData; + /* # of bytes for year, month, and day. */ -#define _PyDateTime_DATE_DATASIZE 4 +#define _PyDateTime_DATE_DATASIZE sizeof(_PyDateTime_DateData) + +typedef struct +{ + unsigned char hour; + unsigned char minute; + unsigned int second : 8; + unsigned int microsecond : 24; +} __attribute__((packed)) _PyDateTime_TimeData; /* # of bytes for hour, minute, second, and usecond. */ -#define _PyDateTime_TIME_DATASIZE 6 +#define _PyDateTime_TIME_DATASIZE sizeof(_PyDateTime_TimeData) + +typedef struct +{ + unsigned short year; + unsigned char month; + unsigned char day; + unsigned char hour; + unsigned char minute; + unsigned int second : 8; + unsigned int microsecond : 24; +} __attribute__((packed)) _PyDateTime_DateTimeData; /* # of bytes for year, month, day, hour, minute, second, and usecond. */ -#define _PyDateTime_DATETIME_DATASIZE 10 +#define _PyDateTime_DATETIME_DATASIZE sizeof(_PyDateTime_DateTimeData) typedef struct @@ -71,7 +97,7 @@ typedef struct */ #define _PyDateTime_TIMEHEAD \ _PyTZINFO_HEAD \ - unsigned char data[_PyDateTime_TIME_DATASIZE]; + _PyDateTime_TimeData data; typedef struct { @@ -94,12 +120,12 @@ typedef struct typedef struct { _PyTZINFO_HEAD - unsigned char data[_PyDateTime_DATE_DATASIZE]; + _PyDateTime_DateData data; } PyDateTime_Date; #define _PyDateTime_DATETIMEHEAD \ _PyTZINFO_HEAD \ - unsigned char data[_PyDateTime_DATETIME_DATASIZE]; + _PyDateTime_DateTimeData data; typedef struct { @@ -119,30 +145,23 @@ typedef struct // o is a pointer to a time or a datetime object. #define _PyDateTime_HAS_TZINFO(o) (((_PyDateTime_BaseTZInfo *)(o))->hastzinfo) -#define PyDateTime_GET_YEAR(o) ((((PyDateTime_Date*)o)->data[0] << 8) | \ - ((PyDateTime_Date*)o)->data[1]) -#define PyDateTime_GET_MONTH(o) (((PyDateTime_Date*)o)->data[2]) -#define PyDateTime_GET_DAY(o) (((PyDateTime_Date*)o)->data[3]) - -#define PyDateTime_DATE_GET_HOUR(o) (((PyDateTime_DateTime*)o)->data[4]) -#define PyDateTime_DATE_GET_MINUTE(o) (((PyDateTime_DateTime*)o)->data[5]) -#define PyDateTime_DATE_GET_SECOND(o) (((PyDateTime_DateTime*)o)->data[6]) -#define PyDateTime_DATE_GET_MICROSECOND(o) \ - ((((PyDateTime_DateTime*)o)->data[7] << 16) | \ - (((PyDateTime_DateTime*)o)->data[8] << 8) | \ - ((PyDateTime_DateTime*)o)->data[9]) +#define PyDateTime_GET_YEAR(o) (((PyDateTime_Date*)o)->data.year) +#define PyDateTime_GET_MONTH(o) (((PyDateTime_Date*)o)->data.month) +#define PyDateTime_GET_DAY(o) (((PyDateTime_Date*)o)->data.day) + +#define PyDateTime_DATE_GET_HOUR(o) (((PyDateTime_DateTime*)o)->data.hour) +#define PyDateTime_DATE_GET_MINUTE(o) (((PyDateTime_DateTime*)o)->data.minute) +#define PyDateTime_DATE_GET_SECOND(o) (((PyDateTime_DateTime*)o)->data.second) +#define PyDateTime_DATE_GET_MICROSECOND(o) (((PyDateTime_DateTime*)o)->data.microsecond) #define PyDateTime_DATE_GET_FOLD(o) (((PyDateTime_DateTime*)o)->fold) #define PyDateTime_DATE_GET_TZINFO(o) (_PyDateTime_HAS_TZINFO(o) ? \ ((PyDateTime_DateTime *)(o))->tzinfo : Py_None) /* Apply for time instances. */ -#define PyDateTime_TIME_GET_HOUR(o) (((PyDateTime_Time*)o)->data[0]) -#define PyDateTime_TIME_GET_MINUTE(o) (((PyDateTime_Time*)o)->data[1]) -#define PyDateTime_TIME_GET_SECOND(o) (((PyDateTime_Time*)o)->data[2]) -#define PyDateTime_TIME_GET_MICROSECOND(o) \ - ((((PyDateTime_Time*)o)->data[3] << 16) | \ - (((PyDateTime_Time*)o)->data[4] << 8) | \ - ((PyDateTime_Time*)o)->data[5]) +#define PyDateTime_TIME_GET_HOUR(o) (((PyDateTime_Time*)o)->data.hour) +#define PyDateTime_TIME_GET_MINUTE(o) (((PyDateTime_Time*)o)->data.minute) +#define PyDateTime_TIME_GET_SECOND(o) (((PyDateTime_Time*)o)->data.second) +#define PyDateTime_TIME_GET_MICROSECOND(o) (((PyDateTime_Time*)o)->data.microsecond) #define PyDateTime_TIME_GET_FOLD(o) (((PyDateTime_Time*)o)->fold) #define PyDateTime_TIME_GET_TZINFO(o) (_PyDateTime_HAS_TZINFO(o) ? \ ((PyDateTime_Time *)(o))->tzinfo : Py_None) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 0631272429f4f1..2855d4d874ce3e 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -76,8 +76,7 @@ class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCa #define DATE_GET_FOLD PyDateTime_DATE_GET_FOLD /* Date accessors for date and datetime. */ -#define SET_YEAR(o, v) (((o)->data[0] = ((v) & 0xff00) >> 8), \ - ((o)->data[1] = ((v) & 0x00ff))) +#define SET_YEAR(o, v) (PyDateTime_GET_YEAR(o) = (v)) #define SET_MONTH(o, v) (PyDateTime_GET_MONTH(o) = (v)) #define SET_DAY(o, v) (PyDateTime_GET_DAY(o) = (v)) @@ -85,10 +84,7 @@ class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCa #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))) +#define DATE_SET_MICROSECOND(o, v) (PyDateTime_DATE_GET_MICROSECOND(o) = (v)) #define DATE_SET_FOLD(o, v) (PyDateTime_DATE_GET_FOLD(o) = (v)) /* Time accessors for time. */ @@ -100,10 +96,7 @@ class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCa #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))) +#define TIME_SET_MICROSECOND(o, v) (PyDateTime_TIME_GET_MICROSECOND(o) = (v)) #define TIME_SET_FOLD(o, v) (PyDateTime_TIME_GET_FOLD(o) = (v)) /* Delta accessors for timedelta. */ @@ -2792,7 +2785,7 @@ date_from_pickle(PyTypeObject *type, PyObject *state) me = (PyDateTime_Date *) (type->tp_alloc(type, 0)); if (me != NULL) { const char *pdata = PyBytes_AS_STRING(state); - memcpy(me->data, pdata, _PyDateTime_DATE_DATASIZE); + memcpy(&me->data, pdata, _PyDateTime_DATE_DATASIZE); me->hashcode = -1; } return (PyObject *)me; @@ -3380,8 +3373,8 @@ 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, + int diff = memcmp(&((PyDateTime_Date *)self)->data, + &((PyDateTime_Date *)other)->data, _PyDateTime_DATE_DATASIZE); return diff_to_bool(diff, op); } @@ -3432,7 +3425,7 @@ date_hash(PyDateTime_Date *self) { if (self->hashcode == -1) { self->hashcode = generic_hash( - (unsigned char *)self->data, _PyDateTime_DATE_DATASIZE); + (unsigned char *)&self->data, _PyDateTime_DATE_DATASIZE); } return self->hashcode; @@ -3460,7 +3453,7 @@ static PyObject * date_getstate(PyDateTime_Date *self) { PyObject* field; - field = PyBytes_FromStringAndSize((char*)self->data, + field = PyBytes_FromStringAndSize((char*)&self->data, _PyDateTime_DATE_DATASIZE); return Py_BuildValue("(N)", field); } @@ -4157,7 +4150,7 @@ time_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) if (me != NULL) { const char *pdata = PyBytes_AS_STRING(state); - memcpy(me->data, pdata, _PyDateTime_TIME_DATASIZE); + memcpy(&me->data, pdata, _PyDateTime_TIME_DATASIZE); me->hashcode = -1; me->hastzinfo = aware; if (aware) { @@ -4165,7 +4158,7 @@ time_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) me->tzinfo = tzinfo; } if (pdata[0] & (1 << 7)) { - me->data[0] -= 128; + GET_YEAR(me) &= 0x7f; me->fold = 1; } else { @@ -4414,8 +4407,8 @@ time_richcompare(PyObject *self, PyObject *other, int op) Py_RETURN_NOTIMPLEMENTED; if (GET_TIME_TZINFO(self) == GET_TIME_TZINFO(other)) { - diff = memcmp(((PyDateTime_Time *)self)->data, - ((PyDateTime_Time *)other)->data, + diff = memcmp(&((PyDateTime_Time *)self)->data, + &((PyDateTime_Time *)other)->data, _PyDateTime_TIME_DATASIZE); return diff_to_bool(diff, op); } @@ -4432,8 +4425,8 @@ time_richcompare(PyObject *self, PyObject *other, int op) if ((offset1 == offset2) || (PyDelta_Check(offset1) && PyDelta_Check(offset2) && delta_cmp(offset1, offset2) == 0)) { - diff = memcmp(((PyDateTime_Time *)self)->data, - ((PyDateTime_Time *)other)->data, + diff = memcmp(&((PyDateTime_Time *)self)->data, + &((PyDateTime_Time *)other)->data, _PyDateTime_TIME_DATASIZE); result = diff_to_bool(diff, op); } @@ -4504,7 +4497,7 @@ time_hash(PyDateTime_Time *self) /* Reduce this to a hash of another object. */ if (offset == Py_None) self->hashcode = generic_hash( - (unsigned char *)self->data, _PyDateTime_TIME_DATASIZE); + (unsigned char *)&self->data, _PyDateTime_TIME_DATASIZE); else { PyObject *temp1, *temp2; int seconds, microseconds; @@ -4627,7 +4620,7 @@ time_getstate(PyDateTime_Time *self, int proto) PyObject *basestate; PyObject *result = NULL; - basestate = PyBytes_FromStringAndSize((char *)self->data, + basestate = PyBytes_FromStringAndSize((char *)&self->data, _PyDateTime_TIME_DATASIZE); if (basestate != NULL) { if (proto > 3 && TIME_GET_FOLD(self)) @@ -4823,7 +4816,7 @@ datetime_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) if (me != NULL) { const char *pdata = PyBytes_AS_STRING(state); - memcpy(me->data, pdata, _PyDateTime_DATETIME_DATASIZE); + memcpy(&me->data, pdata, _PyDateTime_DATETIME_DATASIZE); me->hashcode = -1; me->hastzinfo = aware; if (aware) { @@ -4831,7 +4824,7 @@ datetime_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) me->tzinfo = tzinfo; } if (pdata[2] & (1 << 7)) { - me->data[2] -= 128; + me->data.month -= 128; me->fold = 1; } else { @@ -5702,8 +5695,8 @@ datetime_richcompare(PyObject *self, PyObject *other, int op) } if (GET_DT_TZINFO(self) == GET_DT_TZINFO(other)) { - diff = memcmp(((PyDateTime_DateTime *)self)->data, - ((PyDateTime_DateTime *)other)->data, + diff = memcmp(&((PyDateTime_DateTime *)self)->data, + &((PyDateTime_DateTime *)other)->data, _PyDateTime_DATETIME_DATASIZE); return diff_to_bool(diff, op); } @@ -5720,8 +5713,8 @@ datetime_richcompare(PyObject *self, PyObject *other, int op) if ((offset1 == offset2) || (PyDelta_Check(offset1) && PyDelta_Check(offset2) && delta_cmp(offset1, offset2) == 0)) { - diff = memcmp(((PyDateTime_DateTime *)self)->data, - ((PyDateTime_DateTime *)other)->data, + diff = memcmp(&((PyDateTime_DateTime *)self)->data, + &((PyDateTime_DateTime *)other)->data, _PyDateTime_DATETIME_DATASIZE); if ((op == Py_EQ || op == Py_NE) && diff == 0) { int ex = pep495_eq_exception(self, other, offset1, offset2); @@ -5804,7 +5797,7 @@ datetime_hash(PyDateTime_DateTime *self) /* Reduce this to a hash of another object. */ if (offset == Py_None) self->hashcode = generic_hash( - (unsigned char *)self->data, _PyDateTime_DATETIME_DATASIZE); + (unsigned char *)&self->data, _PyDateTime_DATETIME_DATASIZE); else { PyObject *temp1, *temp2; int days, seconds; @@ -6280,7 +6273,7 @@ datetime_getstate(PyDateTime_DateTime *self, int proto) PyObject *basestate; PyObject *result = NULL; - basestate = PyBytes_FromStringAndSize((char *)self->data, + basestate = PyBytes_FromStringAndSize((char *)&self->data, _PyDateTime_DATETIME_DATASIZE); if (basestate != NULL) { if (proto > 3 && DATE_GET_FOLD(self))