diff options
author | Alexander Belopolsky <abalkin@users.noreply.github.com> | 2017-07-31 14:26:50 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-31 14:26:50 (GMT) |
commit | 018d353c1c8c87767d2335cd884017c2ce12e045 (patch) | |
tree | 36a485b724114e393901f3fa16d741cc63bd7cb2 /Lib/datetime.py | |
parent | c6ea8974e2d939223bfd6d64ee13ec89c090d2e0 (diff) | |
download | cpython-018d353c1c8c87767d2335cd884017c2ce12e045.zip cpython-018d353c1c8c87767d2335cd884017c2ce12e045.tar.gz cpython-018d353c1c8c87767d2335cd884017c2ce12e045.tar.bz2 |
Closes issue bpo-5288: Allow tzinfo objects with sub-minute offsets. (#2896)
* Closes issue bpo-5288: Allow tzinfo objects with sub-minute offsets.
* bpo-5288: Implemented %z formatting of sub-minute offsets.
* bpo-5288: Removed mentions of the whole minute limitation on TZ offsets.
* bpo-5288: Removed one more mention of the whole minute limitation.
Thanks @csabella!
* Fix a formatting error in the docs
* Addressed review comments.
Thanks, @haypo.
Diffstat (limited to 'Lib/datetime.py')
-rw-r--r-- | Lib/datetime.py | 51 |
1 files changed, 29 insertions, 22 deletions
diff --git a/Lib/datetime.py b/Lib/datetime.py index 76a6f95..2f03847 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -206,10 +206,16 @@ def _wrap_strftime(object, format, timetuple): 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) + h, rest = divmod(offset, timedelta(hours=1)) + m, rest = divmod(rest, timedelta(minutes=1)) + s = rest.seconds + u = offset.microseconds + if u: + zreplace = '%c%02d%02d%02d.%06d' % (sign, h, m, s, u) + elif s: + zreplace = '%c%02d%02d%02d' % (sign, h, m, s) + else: + zreplace = '%c%02d%02d' % (sign, h, m) assert '%' not in zreplace newformat.append(zreplace) elif ch == 'Z': @@ -241,7 +247,7 @@ def _check_tzname(name): # 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. +# Else offset is checked for being in range. # If it is, its integer value is returned. Else ValueError is raised. def _check_utc_offset(name, offset): assert name in ("utcoffset", "dst") @@ -250,9 +256,6 @@ def _check_utc_offset(name, offset): if not isinstance(offset, timedelta): raise TypeError("tzinfo.%s() must return None " "or timedelta, not '%s'" % (name, type(offset))) - if offset.microseconds: - raise ValueError("tzinfo.%s() must return a whole number " - "of seconds, got %s" % (name, offset)) if not -timedelta(1) < offset < timedelta(1): raise ValueError("%s()=%s, must be strictly between " "-timedelta(hours=24) and timedelta(hours=24)" % @@ -960,11 +963,11 @@ class tzinfo: raise NotImplementedError("tzinfo subclass must override tzname()") def utcoffset(self, dt): - "datetime -> minutes east of UTC (negative for west of UTC)" + "datetime -> timedelta, positive for 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. + """datetime -> DST offset as timedelta, positive for east of UTC. Return 0 if DST not in effect. utcoffset() must include the DST offset. @@ -1262,8 +1265,8 @@ class time: # Timezone functions def utcoffset(self): - """Return the timezone offset in minutes east of UTC (negative west of - UTC).""" + """Return the timezone offset as timedelta, positive east of UTC + (negative west of UTC).""" if self._tzinfo is None: return None offset = self._tzinfo.utcoffset(None) @@ -1284,8 +1287,8 @@ class time: 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. + """Return 0 if DST is not in effect, or the DST offset (as timedelta + positive 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 @@ -1714,7 +1717,7 @@ class datetime(date): return _strptime._strptime_datetime(cls, date_string, format) def utcoffset(self): - """Return the timezone offset in minutes east of UTC (negative west of + """Return the timezone offset as timedelta positive east of UTC (negative west of UTC).""" if self._tzinfo is None: return None @@ -1736,8 +1739,8 @@ class datetime(date): 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. + """Return 0 if DST is not in effect, or the DST offset (as timedelta + positive 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 @@ -1962,9 +1965,6 @@ class timezone(tzinfo): raise ValueError("offset must be a timedelta " "strictly between -timedelta(hours=24) and " "timedelta(hours=24).") - if (offset.microseconds != 0 or offset.seconds % 60 != 0): - raise ValueError("offset must be a timedelta " - "representing a whole number of minutes") return cls._create(offset, name) @classmethod @@ -2053,8 +2053,15 @@ class timezone(tzinfo): else: sign = '+' hours, rest = divmod(delta, timedelta(hours=1)) - minutes = rest // timedelta(minutes=1) - return 'UTC{}{:02d}:{:02d}'.format(sign, hours, minutes) + minutes, rest = divmod(rest, timedelta(minutes=1)) + seconds = rest.seconds + microseconds = rest.microseconds + if microseconds: + return (f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}' + f'.{microseconds:06d}') + if seconds: + return f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}' + return f'UTC{sign}{hours:02d}:{minutes:02d}' timezone.utc = timezone._create(timedelta(0)) timezone.min = timezone._create(timezone._minoffset) |