merging tzutils into timeutils, lots of little timeutils docs enhancements

This commit is contained in:
Mahmoud Hashemi 2015-08-16 18:47:06 -07:00
parent 13d5ee88c7
commit 027865d9ce
7 changed files with 252 additions and 250 deletions

View File

@ -371,7 +371,7 @@ def iter_find_files(directory, patterns, ignored=None):
>>> filenames = sorted(iter_find_files(_CUR_DIR, '*.py')) >>> filenames = sorted(iter_find_files(_CUR_DIR, '*.py'))
>>> filenames[-1].split('/')[-1] >>> filenames[-1].split('/')[-1]
'tzutils.py' 'typeutils.py'
Or, Python files while ignoring emacs lockfiles: Or, Python files while ignoring emacs lockfiles:

View File

@ -5,18 +5,25 @@ nontrivial, but thankfully its support is first-class in
Python. ``dateutils`` provides some additional tools for working with Python. ``dateutils`` provides some additional tools for working with
time. time.
See :mod:`tzutils` for handy timezone-related boltons. Additionally, timeutils provides a few basic utilities for working
with timezones in Python. The Python :mod:`datetime` module's
documentation describes how to create a
:class:`datetime.datetime`-compatible :class:`datetime.tzinfo`
subtype. It even provides a few examples.
The following module defines usable forms of the timezones in those
docs, as well as a couple other useful ones, :data:`UTC` (aka GMT) and
:data:`LocalTZ` (representing the local timezone as configured in the
operating system). For timezones beyond these, as well as a higher
degree of accuracy in corner cases, check out `pytz`_.
.. _pytz: https://pypi.python.org/pypi/pytz
""" """
import re import re
import time
import bisect import bisect
import datetime from datetime import tzinfo, timedelta, datetime
from datetime import timedelta
__all__ = ['total_seconds', 'isoparse', 'parse_td', 'relative_time',
'decimal_relative_time']
def total_seconds(td): def total_seconds(td):
@ -28,7 +35,7 @@ def total_seconds(td):
Returns: Returns:
float: total number of seconds float: total number of seconds
>>> td = datetime.timedelta(days=4, seconds=33) >>> td = timedelta(days=4, seconds=33)
>>> total_seconds(td) >>> total_seconds(td)
345633.0 345633.0
""" """
@ -42,28 +49,30 @@ _NONDIGIT_RE = re.compile('\D')
def isoparse(iso_str): def isoparse(iso_str):
"""Parses the very limited subset of ISO8601 as returned by """Parses the limited subset of `ISO8601-formatted time`_ strings as
:meth:`datetime.datetime.isoformat`. returned by :meth:`datetime.datetime.isoformat`.
>>> epoch_dt = datetime.datetime.utcfromtimestamp(0) >>> epoch_dt = datetime.utcfromtimestamp(0)
>>> iso_str = epoch_dt.isoformat() >>> iso_str = epoch_dt.isoformat()
>>> print(iso_str) >>> print(iso_str)
1970-01-01T00:00:00 1970-01-01T00:00:00
>>> isoparse(iso_str) >>> isoparse(iso_str)
datetime.datetime(1970, 1, 1, 0, 0) datetime.datetime(1970, 1, 1, 0, 0)
>>> utcnow = datetime.datetime.utcnow() >>> utcnow = datetime.utcnow()
>>> utcnow == isoparse(utcnow.isoformat()) >>> utcnow == isoparse(utcnow.isoformat())
True True
For further datetime parsing, see the `iso8601`_ package for strict For further datetime parsing, see the `iso8601`_ package for strict
ISO parsing and `dateutil`_ package for loose parsing and more. ISO parsing and `dateutil`_ package for loose parsing and more.
.. _ISO8601-formatted time strings: https://en.wikipedia.org/wiki/ISO_8601
.. _iso8601: https://pypi.python.org/pypi/iso8601 .. _iso8601: https://pypi.python.org/pypi/iso8601
.. _dateutil: https://pypi.python.org/pypi/python-dateutil .. _dateutil: https://pypi.python.org/pypi/python-dateutil
""" """
dt_args = [int(p) for p in _NONDIGIT_RE.split(iso_str)] dt_args = [int(p) for p in _NONDIGIT_RE.split(iso_str)]
return datetime.datetime(*dt_args) return datetime(*dt_args)
_BOUNDS = [(0, timedelta(seconds=1), 'second'), _BOUNDS = [(0, timedelta(seconds=1), 'second'),
@ -157,7 +166,7 @@ def decimal_relative_time(d, other=None, ndigits=0, cardinalize=True):
localization into other languages and custom phrasing and localization into other languages and custom phrasing and
formatting. formatting.
>>> now = datetime.datetime.utcnow() >>> now = datetime.utcnow()
>>> decimal_relative_time(now - timedelta(days=1, seconds=3600), now) >>> decimal_relative_time(now - timedelta(days=1, seconds=3600), now)
(1.0, 'day') (1.0, 'day')
>>> decimal_relative_time(now - timedelta(seconds=0.002), now, ndigits=5) >>> decimal_relative_time(now - timedelta(seconds=0.002), now, ndigits=5)
@ -166,7 +175,7 @@ def decimal_relative_time(d, other=None, ndigits=0, cardinalize=True):
(-2.5, 'years') (-2.5, 'years')
""" """
if other is None: if other is None:
other = datetime.datetime.utcnow() other = datetime.utcnow()
diff = other - d diff = other - d
diff_seconds = total_seconds(diff) diff_seconds = total_seconds(diff)
abs_diff = abs(diff) abs_diff = abs(diff)
@ -194,7 +203,7 @@ def relative_time(d, other=None, ndigits=0):
Returns: Returns:
A short English-language string. A short English-language string.
>>> now = datetime.datetime.utcnow() >>> now = datetime.utcnow()
>>> relative_time(now, ndigits=1) >>> relative_time(now, ndigits=1)
'0 seconds ago' '0 seconds ago'
>>> relative_time(now - timedelta(days=1, seconds=36000), ndigits=1) >>> relative_time(now - timedelta(days=1, seconds=36000), ndigits=1)
@ -208,3 +217,184 @@ def relative_time(d, other=None, ndigits=0):
if drt < 0: if drt < 0:
phrase = 'from now' phrase = 'from now'
return '%g %s %s' % (abs(drt), unit, phrase) return '%g %s %s' % (abs(drt), unit, phrase)
# Timezone support (brought in from tzutils)
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
class ConstantTZInfo(tzinfo):
"""
A :class:`datetime.tzinfo` subtype whose *offset* remains constant
(no daylight savings).
Args:
name (str): Name of the timezone.
offset (datetime.timedelta): Offset of the timezone.
"""
def __init__(self, name="ConstantTZ", offset=ZERO):
self.name = name
self.offset = offset
@property
def utcoffset_hours(self):
return total_seconds(self.offset) / (60 * 60)
def utcoffset(self, dt):
return self.offset
def tzname(self, dt):
return self.name
def dst(self, dt):
return ZERO
def __repr__(self):
cn = self.__class__.__name__
return '%s(name=%r, offset=%r)' % (cn, self.name, self.offset)
UTC = ConstantTZInfo('UTC')
class LocalTZInfo(tzinfo):
"""The ``LocalTZInfo`` type takes data available in the time module
about the local timezone and makes a practical
:class:`datetime.tzinfo` to represent the timezone settings of the
operating system.
For a more in-depth integration with the operating system, check
out `tzlocal`_. It builds on `pytz`_ and implements heuristics for
many versions of major operating systems to provide the official
``pytz`` tzinfo, instead of the LocalTZ generalization.
.. _tzlocal: https://pypi.python.org/pypi/tzlocal
.. _pytz: https://pypi.python.org/pypi/pytz
"""
_std_offset = timedelta(seconds=-time.timezone)
_dst_offset = _std_offset
if time.daylight:
_dst_offset = timedelta(seconds=-time.altzone)
def is_dst(self, dt):
dt_t = (dt.year, dt.month, dt.day, dt.hour, dt.minute,
dt.second, dt.weekday(), 0, -1)
local_t = time.localtime(time.mktime(dt_t))
return local_t.tm_isdst > 0
def utcoffset(self, dt):
if self.is_dst(dt):
return self._dst_offset
return self._std_offset
def dst(self, dt):
if self.is_dst(dt):
return self._dst_offset - self._std_offset
return ZERO
def tzname(self, dt):
return time.tzname[self.is_dst(dt)]
def __repr__(self):
return '%s()' % self.__class__.__name__
LocalTZ = LocalTZInfo()
def _first_sunday_on_or_after(dt):
days_to_go = 6 - dt.weekday()
if days_to_go:
dt += timedelta(days_to_go)
return dt
# US DST Rules
#
# This is a simplified (i.e., wrong for a few cases) set of rules for US
# DST start and end times. For a complete and up-to-date set of DST rules
# and timezone definitions, visit the Olson Database (or try pytz):
# http://www.twinsun.com/tz/tz-link.htm
# http://sourceforge.net/projects/pytz/ (might not be up-to-date)
#
# In the US, since 2007, DST starts at 2am (standard time) on the second
# Sunday in March, which is the first Sunday on or after Mar 8.
DSTSTART_2007 = datetime(1, 3, 8, 2)
# and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov.
DSTEND_2007 = datetime(1, 11, 1, 1)
# From 1987 to 2006, DST used to start at 2am (standard time) on the first
# Sunday in April and to end at 2am (DST time; 1am standard time) on the last
# Sunday of October, which is the first Sunday on or after Oct 25.
DSTSTART_1987_2006 = datetime(1, 4, 1, 2)
DSTEND_1987_2006 = datetime(1, 10, 25, 1)
# From 1967 to 1986, DST used to start at 2am (standard time) on the last
# Sunday in April (the one on or after April 24) and to end at 2am (DST time;
# 1am standard time) on the last Sunday of October, which is the first Sunday
# on or after Oct 25.
DSTSTART_1967_1986 = datetime(1, 4, 24, 2)
DSTEND_1967_1986 = DSTEND_1987_2006
class USTimeZone(tzinfo):
"""Copied directly from the Python docs, the ``USTimeZone`` is a
:class:`datetime.tzinfo` subtype used to create the
:data:`Eastern`, :data:`Central`, :data:`Mountain`, and
:data:`Pacific` tzinfo types.
"""
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 may be sensible here, in one or both cases.
# It depends on how you want to treat them. The default
# fromutc() implementation (called by the default astimezone()
# implementation) passes a datetime with dt.tzinfo is self.
return ZERO
assert dt.tzinfo is self
# Find start and end times for US DST. For years before 1967, return
# ZERO for no DST.
if 2006 < dt.year:
dststart, dstend = DSTSTART_2007, DSTEND_2007
elif 1986 < dt.year < 2007:
dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006
elif 1966 < dt.year < 1987:
dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986
else:
return ZERO
start = _first_sunday_on_or_after(dststart.replace(year=dt.year))
end = _first_sunday_on_or_after(dstend.replace(year=dt.year))
# 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")

View File

@ -1,206 +0,0 @@
# -*- coding: utf-8 -*-
"""The Python :mod:`datetime` module's documentation describes how
to create a :class:`datetime.datetime`-compatible
:class:`datetime.tzinfo` subtype. It even provides a few examples.
The following module defines usable forms of the timezones in those
docs, as well as a couple other useful ones, :data:`UTC` (aka GMT) and
:data:`LocalTZ` (representing the local timezone). For timezones
beyond these, as well as a higher degree of accuracy, check out `pytz`_.
.. _pytz: https://pypi.python.org/pypi/pytz
"""
import time
from datetime import tzinfo, timedelta, datetime
# Basic timezones cribbed from etavta and Python docs.
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
# copied from timeutils for tzutils independence:
def total_seconds(td):
"""For those with older versions of Python, a pure-Python
implementation of Python 2.7's :meth:`timedelta.total_seconds`.
Args:
td (datetime.timedelta): The timedelta to convert to seconds.
Returns:
float: total number of seconds
>>> td = timedelta(days=4, seconds=33)
>>> total_seconds(td)
345633.0
"""
a_milli = 1000000.0
td_ds = td.seconds + (td.days * 86400) # 24 * 60 * 60
td_micro = td.microseconds + (td_ds * a_milli)
return td_micro / a_milli
class ConstantTZInfo(tzinfo):
"""
A :class:`datetime.tzinfo` subtype whose *offset* remains constant
(no daylight savings).
Args:
name (str): Name of the timezone.
offset (datetime.timedelta): Offset of the timezone.
"""
def __init__(self, name="ConstantTZ", offset=ZERO):
self.name = name
self.offset = offset
@property
def utcoffset_hours(self):
return total_seconds(self.offset) / (60 * 60)
def utcoffset(self, dt):
return self.offset
def tzname(self, dt):
return self.name
def dst(self, dt):
return ZERO
def __repr__(self):
cn = self.__class__.__name__
return '%s(name=%r, offset=%r)' % (cn, self.name, self.offset)
UTC = ConstantTZInfo('UTC')
class LocalTZInfo(tzinfo):
"""The ``LocalTZInfo`` type takes data available in the time module about
the local timezone and makes a practical tzinfo to represent the
timezone settings of the operating system.
"""
_std_offset = timedelta(seconds=-time.timezone)
_dst_offset = _std_offset
if time.daylight:
_dst_offset = timedelta(seconds=-time.altzone)
def is_dst(self, dt):
dt_t = (dt.year, dt.month, dt.day, dt.hour, dt.minute,
dt.second, dt.weekday(), 0, -1)
local_t = time.localtime(time.mktime(dt_t))
return local_t.tm_isdst > 0
def utcoffset(self, dt):
if self.is_dst(dt):
return self._dst_offset
return self._std_offset
def dst(self, dt):
if self.is_dst(dt):
return self._dst_offset - self._std_offset
return ZERO
def tzname(self, dt):
return time.tzname[self.is_dst(dt)]
def __repr__(self):
return '%s()' % self.__class__.__name__
LocalTZ = LocalTZInfo()
def _first_sunday_on_or_after(dt):
days_to_go = 6 - dt.weekday()
if days_to_go:
dt += timedelta(days_to_go)
return dt
# US DST Rules
#
# This is a simplified (i.e., wrong for a few cases) set of rules for US
# DST start and end times. For a complete and up-to-date set of DST rules
# and timezone definitions, visit the Olson Database (or try pytz):
# http://www.twinsun.com/tz/tz-link.htm
# http://sourceforge.net/projects/pytz/ (might not be up-to-date)
#
# In the US, since 2007, DST starts at 2am (standard time) on the second
# Sunday in March, which is the first Sunday on or after Mar 8.
DSTSTART_2007 = datetime(1, 3, 8, 2)
# and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov.
DSTEND_2007 = datetime(1, 11, 1, 1)
# From 1987 to 2006, DST used to start at 2am (standard time) on the first
# Sunday in April and to end at 2am (DST time; 1am standard time) on the last
# Sunday of October, which is the first Sunday on or after Oct 25.
DSTSTART_1987_2006 = datetime(1, 4, 1, 2)
DSTEND_1987_2006 = datetime(1, 10, 25, 1)
# From 1967 to 1986, DST used to start at 2am (standard time) on the last
# Sunday in April (the one on or after April 24) and to end at 2am (DST time;
# 1am standard time) on the last Sunday of October, which is the first Sunday
# on or after Oct 25.
DSTSTART_1967_1986 = datetime(1, 4, 24, 2)
DSTEND_1967_1986 = DSTEND_1987_2006
class USTimeZone(tzinfo):
"""Copied directly from the Python docs, the ``USTimeZone`` is a
:class:`datetime.tzinfo` subtype used to create the
:data:`Eastern`, :data:`Central`, :data:`Mountain`, and
:data:`Pacific` tzinfo types.
"""
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 may be sensible here, in one or both cases.
# It depends on how you want to treat them. The default
# fromutc() implementation (called by the default astimezone()
# implementation) passes a datetime with dt.tzinfo is self.
return ZERO
assert dt.tzinfo is self
# Find start and end times for US DST. For years before 1967, return
# ZERO for no DST.
if 2006 < dt.year:
dststart, dstend = DSTSTART_2007, DSTEND_2007
elif 1986 < dt.year < 2007:
dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006
elif 1966 < dt.year < 1987:
dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986
else:
return ZERO
start = _first_sunday_on_or_after(dststart.replace(year=dt.year))
end = _first_sunday_on_or_after(dstend.replace(year=dt.year))
# 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")

View File

@ -58,7 +58,7 @@ a definite set of themes have emerged:
1. :mod:`~boltons.queueutils` - `heapq docs`_ 1. :mod:`~boltons.queueutils` - `heapq docs`_
2. :mod:`~boltons.iterutils` - `itertools docs`_ 2. :mod:`~boltons.iterutils` - `itertools docs`_
3. :mod:`~boltons.tzutils` - `datetime docs`_ 3. :mod:`~boltons.timeutils` - `datetime docs`_
2. Reimplementations and tweaks of the standard library: 2. Reimplementations and tweaks of the standard library:

View File

@ -104,4 +104,3 @@ Section listing
tbutils tbutils
timeutils timeutils
typeutils typeutils
tzutils

View File

@ -2,5 +2,46 @@
====================================== ======================================
.. automodule:: boltons.timeutils .. automodule:: boltons.timeutils
:members:
:undoc-members: .. autofunction:: total_seconds
.. autofunction:: isoparse
.. autofunction:: parse_timedelta
.. autofunction:: total_seconds
.. autofunction:: relative_time
.. autofunction:: decimal_relative_time
General timezones
-----------------
By default, :class:`datetime.datetime` objects are "naïve", meaning
they lack attached timezone information. These objects can be useful
for many operations, but many operations require timezone-aware
datetimes.
The two most important timezones in programming are Coordinated
Universal Time (`UTC`_) and the local timezone of the host running
your code. Boltons provides two :class:`datetime.tzinfo` subtypes for
working with them:
.. _UTC: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
.. autoattribute:: boltons.timeutils.UTC
.. autodata:: boltons.timeutils.LocalTZ
.. autoclass:: boltons.timeutils.ConstantTZInfo
US timezones
------------
These four US timezones were implemented in the :mod:`datetime`
documentation and have been reproduced here in boltons for
convenience. More in-depth support is provided by `pytz`_.
.. _pytz: https://pypi.python.org/pypi/pytz
.. autoattribute:: boltons.timeutils.Eastern
.. autoattribute:: boltons.timeutils.Central
.. autoattribute:: boltons.timeutils.Mountain
.. autoattribute:: boltons.timeutils.Pacific
.. autoclass:: boltons.timeutils.USTimeZone

View File

@ -1,22 +0,0 @@
``tzutils`` - Barebone timezones
================================
.. automodule:: boltons.tzutils
General timezones
-----------------
.. autoattribute:: boltons.tzutils.UTC
.. autodata:: boltons.tzutils.LocalTZ
.. autoclass:: boltons.tzutils.ConstantTZInfo
US timezones
------------
.. autoattribute:: boltons.tzutils.Eastern
.. autoattribute:: boltons.tzutils.Central
.. autoattribute:: boltons.tzutils.Mountain
.. autoattribute:: boltons.tzutils.Pacific
.. autoclass:: boltons.tzutils.USTimeZone