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