overhauled (fixed?) time module
This commit is contained in:
parent
1166bc28cc
commit
2685759932
2 changed files with 130 additions and 71 deletions
|
@ -104,7 +104,7 @@ def required(value, field=None):
|
||||||
raise formalchemy.ValidationError(msg)
|
raise formalchemy.ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
def pretty_datetime(value):
|
def pretty_datetime(value, from_='local', to='local'):
|
||||||
"""
|
"""
|
||||||
Formats a ``datetime.datetime`` instance and returns a "pretty"
|
Formats a ``datetime.datetime`` instance and returns a "pretty"
|
||||||
human-readable string from it, e.g. "42 minutes ago". ``value`` is
|
human-readable string from it, e.g. "42 minutes ago". ``value`` is
|
||||||
|
@ -113,10 +113,10 @@ def pretty_datetime(value):
|
||||||
|
|
||||||
if not isinstance(value, datetime.datetime):
|
if not isinstance(value, datetime.datetime):
|
||||||
return str(value) if value else ''
|
return str(value) if value else ''
|
||||||
value = edbob.local_time(value)
|
if not value.tzinfo:
|
||||||
fmt = formalchemy.fields.DateTimeFieldRenderer.format
|
value = edbob.local_time(value, from_=from_, to=to)
|
||||||
return literal('<span title="%s">%s</span>' % (
|
return literal('<span title="%s">%s</span>' % (
|
||||||
value.strftime(fmt),
|
value.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
|
||||||
pretty.date(value)))
|
pretty.date(value)))
|
||||||
|
|
||||||
|
|
||||||
|
|
191
edbob/time.py
191
edbob/time.py
|
@ -29,91 +29,150 @@
|
||||||
import datetime
|
import datetime
|
||||||
import pytz
|
import pytz
|
||||||
import logging
|
import logging
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import edbob
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['local_time', 'set_timezone', 'utc_time']
|
__all__ = ['utc_time', 'local_time']
|
||||||
|
|
||||||
|
timezones = {}
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
timezone = None
|
|
||||||
|
|
||||||
|
|
||||||
def init(config):
|
def init(config):
|
||||||
"""
|
"""
|
||||||
Initializes the time framework. Currently this only sets the local
|
Reads configuration to become aware of all timezones which may concern the
|
||||||
timezone according to config.
|
application.
|
||||||
"""
|
|
||||||
|
|
||||||
tz = config.get('edbob.time', 'timezone')
|
|
||||||
if tz:
|
|
||||||
set_timezone(tz)
|
|
||||||
log.info("Timezone set to '%s'" % tz)
|
|
||||||
else:
|
|
||||||
log.warning("No timezone configured; falling back to US/Central")
|
|
||||||
set_timezone('US/Central')
|
|
||||||
|
|
||||||
|
|
||||||
def local_time(timestamp=None):
|
|
||||||
"""
|
|
||||||
Tries to return a "localized" version of ``timestamp``, which should be a
|
|
||||||
UTC-based ``datetime.datetime`` instance.
|
|
||||||
|
|
||||||
If a local timezone has been configured, then
|
|
||||||
``datetime.datetime.utcnow()`` will be called to obtain a value for
|
|
||||||
``timestamp`` if one is not specified. Then ``timestamp`` will be modified
|
|
||||||
in such a way that its ``tzinfo`` member contains the local timezone, but
|
|
||||||
the effective UTC value for the timestamp remains accurate.
|
|
||||||
|
|
||||||
If a local timezone has *not* been configured, then
|
|
||||||
``datetime.datetime.now()`` will be called instead to obtain the value
|
|
||||||
should none be specified. ``timestamp`` will be returned unchanged.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if timezone:
|
|
||||||
if timestamp is None:
|
|
||||||
timestamp = datetime.datetime.utcnow()
|
|
||||||
timestamp = pytz.utc.localize(timestamp)
|
|
||||||
return timestamp.astimezone(timezone)
|
|
||||||
|
|
||||||
if timestamp is None:
|
|
||||||
timestamp = datetime.datetime.now()
|
|
||||||
return timestamp
|
|
||||||
|
|
||||||
|
|
||||||
def set_timezone(tz):
|
|
||||||
"""
|
|
||||||
Sets edbob's notion of the "local" timezone. ``tz`` should be an Olson
|
|
||||||
name.
|
|
||||||
|
|
||||||
.. highlight:: ini
|
.. highlight:: ini
|
||||||
|
|
||||||
You usually don't need to call this yourself, since it's called by
|
The bare minimum configuration required is the ``zone.local`` setting::
|
||||||
:func:`edbob.init()` whenever the config file includes a timezone (but
|
|
||||||
only as long as ``edbob.time`` is configured to be initialized)::
|
|
||||||
|
|
||||||
[edbob]
|
|
||||||
init = edbob.time
|
|
||||||
|
|
||||||
[edbob.time]
|
[edbob.time]
|
||||||
timezone = US/Central
|
zone.local = US/Pacific
|
||||||
|
|
||||||
|
Multiple timezones may be configured like so::
|
||||||
|
|
||||||
|
[edbob.time]
|
||||||
|
zone.local = America/Los_Angeles
|
||||||
|
zone.head_office = America/New_York
|
||||||
|
zone.that_other_place = Asia/Manila
|
||||||
|
zone.some_app = US/Central
|
||||||
|
|
||||||
|
See `Wikipedia
|
||||||
|
<http://en.wikipedia.org/wiki/List_of_tz_database_time_zones>`_ for a
|
||||||
|
(presumably) full list of valid timezone names.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
A ``zone.utc`` option is automatically created for you; there is no need
|
||||||
|
to define it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
global timezone
|
for key in config.options('edbob.time'):
|
||||||
|
if key.startswith('zone.'):
|
||||||
|
tz = config.get('edbob.time', key)
|
||||||
|
if tz:
|
||||||
|
key = key[5:]
|
||||||
|
log.info("'%s' timezone set to '%s'" % (key, tz))
|
||||||
|
set_timezone(tz, key)
|
||||||
|
|
||||||
if tz is None:
|
if 'local' not in timezones:
|
||||||
timezone = None
|
tz = config.get('edbob.time', 'timezone')
|
||||||
|
if tz:
|
||||||
|
warnings.warn("Config option 'timezone' in 'edbob.time' section is deprecated. "
|
||||||
|
"Please set 'zone.local' instead.",
|
||||||
|
DeprecationWarning)
|
||||||
|
set_timezone(tz)
|
||||||
else:
|
else:
|
||||||
timezone = pytz.timezone(tz)
|
log.warning("'local' timezone not configured; falling back to 'America/Chicago'")
|
||||||
|
set_timezone('America/Chicago')
|
||||||
|
|
||||||
|
set_timezone('UTC', 'utc')
|
||||||
|
|
||||||
|
|
||||||
def utc_time(timestamp=None):
|
def get_timezone(key='local'):
|
||||||
"""
|
"""
|
||||||
Returns a timestamp whose ``tzinfo`` member is set to the UTC timezone.
|
Returns the timezone referenced by ``key``.
|
||||||
|
|
||||||
If ``timestamp`` is not provided, then ``datetime.datetime.utcnow()`` will
|
|
||||||
be called to obtain the value.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if timestamp is None:
|
if key not in timezones:
|
||||||
timestamp = datetime.datetime.utcnow()
|
edbob.config.require('edbob.time', 'zone.%s' % key)
|
||||||
return pytz.utc.localize(timestamp)
|
return timezones[key]
|
||||||
|
|
||||||
|
|
||||||
|
def set_timezone(tz, key='local'):
|
||||||
|
"""
|
||||||
|
Stores a timezone in the global dictionary, using ``key``.
|
||||||
|
|
||||||
|
``tz`` must be a valid "Olson" time zone name, e.g. ``'US/Central'``. See
|
||||||
|
`Wikipedia <http://en.wikipedia.org/wiki/List_of_tz_database_time_zones>`_
|
||||||
|
for a (presumably) full list of valid names.
|
||||||
|
"""
|
||||||
|
|
||||||
|
timezones[key] = pytz.timezone(tz)
|
||||||
|
|
||||||
|
|
||||||
|
def local_time(stamp=None, from_='local', naive=False):
|
||||||
|
"""
|
||||||
|
Returns a :class:`datetime.datetime` instance, with its ``tzinfo`` member
|
||||||
|
set to the timezone referenced by the key ``'local'``. If ``naive`` is
|
||||||
|
``True``, the result is stripped of its ``tzinfo`` member.
|
||||||
|
|
||||||
|
If ``stamp`` is provided, and it is not already "aware," then its value is
|
||||||
|
interpreted as being local to the timezone referenced by ``from_``.
|
||||||
|
|
||||||
|
If ``stamp`` is not provided, the current time is assumed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not stamp:
|
||||||
|
stamp = utc_time()
|
||||||
|
elif not stamp.tzinfo:
|
||||||
|
stamp = localize(stamp, from_=from_)
|
||||||
|
return localize(stamp, naive=naive)
|
||||||
|
|
||||||
|
|
||||||
|
def localize(stamp, from_='local', to='local', naive=False):
|
||||||
|
"""
|
||||||
|
Creates a "localized" version of ``stamp`` and returns it.
|
||||||
|
|
||||||
|
``stamp`` must be a :class:`datetime.datetime` instance. If it is naive,
|
||||||
|
its value is interpreted as being local to the timezone referenced by
|
||||||
|
``from_``. If it is already aware (i.e. not naive), then ``from_`` is
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
The timezone referenced by ``to`` is used to determine the final, "local"
|
||||||
|
value for the timestamp. If ``naive`` is ``True``, the timestamp is
|
||||||
|
stripped of its ``tzinfo`` member before being returned. Otherwise, it
|
||||||
|
will remain aware of its timezone.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not stamp.tzinfo:
|
||||||
|
tz = get_timezone(from_)
|
||||||
|
stamp = tz.localize(stamp)
|
||||||
|
tz = get_timezone(to)
|
||||||
|
stamp = stamp.astimezone(tz)
|
||||||
|
if naive:
|
||||||
|
stamp = stamp.replace(tzinfo=None)
|
||||||
|
return stamp
|
||||||
|
|
||||||
|
|
||||||
|
def utc_time(stamp=None, from_='local', naive=False):
|
||||||
|
"""
|
||||||
|
Returns a :class:`datetime.datetime` instance, with its ``tzinfo`` member
|
||||||
|
set to the UTC timezone. If ``naive`` is ``True``, the result is stripped
|
||||||
|
of its ``tzinfo`` member.
|
||||||
|
|
||||||
|
If ``stamp`` is provided, and it is not already "aware," then its value is
|
||||||
|
interpreted as being local to the timezone referenced by ``from_``.
|
||||||
|
|
||||||
|
If ``stamp`` is not provided, the current time is assumed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not stamp:
|
||||||
|
stamp = datetime.datetime.utcnow()
|
||||||
|
stamp = pytz.utc.localize(stamp)
|
||||||
|
elif not stamp.tzinfo:
|
||||||
|
stamp = localize(stamp, from_=from_)
|
||||||
|
return localize(stamp, to='utc', naive=naive)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue