overhauled (fixed?) time module

This commit is contained in:
Lance Edgar 2012-08-02 16:38:15 -07:00
parent 1166bc28cc
commit 2685759932
2 changed files with 130 additions and 71 deletions

View file

@ -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)))

View file

@ -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)