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)
|
||||
|
||||
|
||||
def pretty_datetime(value):
|
||||
def pretty_datetime(value, from_='local', to='local'):
|
||||
"""
|
||||
Formats a ``datetime.datetime`` instance and returns a "pretty"
|
||||
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):
|
||||
return str(value) if value else ''
|
||||
value = edbob.local_time(value)
|
||||
fmt = formalchemy.fields.DateTimeFieldRenderer.format
|
||||
if not value.tzinfo:
|
||||
value = edbob.local_time(value, from_=from_, to=to)
|
||||
return literal('<span title="%s">%s</span>' % (
|
||||
value.strftime(fmt),
|
||||
value.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
|
||||
pretty.date(value)))
|
||||
|
||||
|
||||
|
|
193
edbob/time.py
193
edbob/time.py
|
@ -29,91 +29,150 @@
|
|||
import datetime
|
||||
import pytz
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
import edbob
|
||||
|
||||
|
||||
__all__ = ['local_time', 'set_timezone', 'utc_time']
|
||||
__all__ = ['utc_time', 'local_time']
|
||||
|
||||
timezones = {}
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
timezone = None
|
||||
|
||||
|
||||
def init(config):
|
||||
"""
|
||||
Initializes the time framework. Currently this only sets the local
|
||||
timezone according to config.
|
||||
"""
|
||||
|
||||
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.
|
||||
Reads configuration to become aware of all timezones which may concern the
|
||||
application.
|
||||
|
||||
.. highlight:: ini
|
||||
|
||||
You usually don't need to call this yourself, since it's called by
|
||||
: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
|
||||
The bare minimum configuration required is the ``zone.local`` setting::
|
||||
|
||||
[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:
|
||||
timezone = None
|
||||
else:
|
||||
timezone = pytz.timezone(tz)
|
||||
if 'local' not in timezones:
|
||||
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:
|
||||
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.
|
||||
|
||||
If ``timestamp`` is not provided, then ``datetime.datetime.utcnow()`` will
|
||||
be called to obtain the value.
|
||||
Returns the timezone referenced by ``key``.
|
||||
"""
|
||||
|
||||
if timestamp is None:
|
||||
timestamp = datetime.datetime.utcnow()
|
||||
return pytz.utc.localize(timestamp)
|
||||
if key not in timezones:
|
||||
edbob.config.require('edbob.time', 'zone.%s' % key)
|
||||
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