2013-09-08 07:56:49 +08:00
|
|
|
"""
|
|
|
|
Timezone-related classes and functions.
|
2011-11-18 21:01:06 +08:00
|
|
|
|
|
|
|
This module uses pytz when it's available and fallbacks when it isn't.
|
|
|
|
"""
|
|
|
|
|
2013-09-08 07:56:49 +08:00
|
|
|
import sys
|
2011-11-18 21:01:06 +08:00
|
|
|
import time as _time
|
2015-01-28 20:35:27 +08:00
|
|
|
from datetime import datetime, timedelta, tzinfo
|
|
|
|
from threading import local
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.utils import lru_cache, six
|
|
|
|
from django.utils.decorators import ContextDecorator
|
2011-11-18 21:01:06 +08:00
|
|
|
|
|
|
|
try:
|
|
|
|
import pytz
|
|
|
|
except ImportError:
|
|
|
|
pytz = None
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
2013-09-08 15:04:31 +08:00
|
|
|
'utc', 'get_fixed_timezone',
|
2014-01-26 22:35:22 +08:00
|
|
|
'get_default_timezone', 'get_default_timezone_name',
|
|
|
|
'get_current_timezone', 'get_current_timezone_name',
|
2011-11-18 21:01:06 +08:00
|
|
|
'activate', 'deactivate', 'override',
|
2014-01-26 22:35:22 +08:00
|
|
|
'localtime', 'now',
|
|
|
|
'is_aware', 'is_naive', 'make_aware', 'make_naive',
|
2011-11-18 21:01:06 +08:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
# UTC and local time zones
|
|
|
|
|
|
|
|
ZERO = timedelta(0)
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
class UTC(tzinfo):
|
|
|
|
"""
|
|
|
|
UTC implementation taken from Python's docs.
|
|
|
|
|
|
|
|
Used only when pytz isn't available.
|
|
|
|
"""
|
|
|
|
|
2012-02-27 05:17:58 +08:00
|
|
|
def __repr__(self):
|
|
|
|
return "<UTC>"
|
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def utcoffset(self, dt):
|
|
|
|
return ZERO
|
|
|
|
|
|
|
|
def tzname(self, dt):
|
|
|
|
return "UTC"
|
|
|
|
|
|
|
|
def dst(self, dt):
|
|
|
|
return ZERO
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2013-09-08 15:04:31 +08:00
|
|
|
class FixedOffset(tzinfo):
|
|
|
|
"""
|
|
|
|
Fixed offset in minutes east from UTC. Taken from Python's docs.
|
|
|
|
|
|
|
|
Kept as close as possible to the reference version. __init__ was changed
|
|
|
|
to make its arguments optional, according to Python's requirement that
|
|
|
|
tzinfo subclasses can be instantiated without arguments.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, offset=None, name=None):
|
|
|
|
if offset is not None:
|
|
|
|
self.__offset = timedelta(minutes=offset)
|
|
|
|
if name is not None:
|
|
|
|
self.__name = name
|
|
|
|
|
|
|
|
def utcoffset(self, dt):
|
|
|
|
return self.__offset
|
|
|
|
|
|
|
|
def tzname(self, dt):
|
|
|
|
return self.__name
|
|
|
|
|
|
|
|
def dst(self, dt):
|
|
|
|
return ZERO
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2013-09-08 07:56:49 +08:00
|
|
|
class ReferenceLocalTimezone(tzinfo):
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
2013-09-08 15:04:31 +08:00
|
|
|
Local time. Taken from Python's docs.
|
2011-11-18 21:01:06 +08:00
|
|
|
|
|
|
|
Used only when pytz isn't available, and most likely inaccurate. If you're
|
|
|
|
having trouble with this class, don't waste your time, just install pytz.
|
2013-09-08 07:56:49 +08:00
|
|
|
|
2013-09-08 15:04:31 +08:00
|
|
|
Kept as close as possible to the reference version. __init__ was added to
|
|
|
|
delay the computation of STDOFFSET, DSTOFFSET and DSTDIFF which is
|
|
|
|
performed at import time in the example.
|
|
|
|
|
|
|
|
Subclasses contain further improvements.
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.STDOFFSET = timedelta(seconds=-_time.timezone)
|
|
|
|
if _time.daylight:
|
|
|
|
self.DSTOFFSET = timedelta(seconds=-_time.altzone)
|
|
|
|
else:
|
|
|
|
self.DSTOFFSET = self.STDOFFSET
|
|
|
|
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
|
|
|
|
tzinfo.__init__(self)
|
|
|
|
|
|
|
|
def utcoffset(self, dt):
|
|
|
|
if self._isdst(dt):
|
|
|
|
return self.DSTOFFSET
|
|
|
|
else:
|
|
|
|
return self.STDOFFSET
|
|
|
|
|
|
|
|
def dst(self, dt):
|
|
|
|
if self._isdst(dt):
|
|
|
|
return self.DSTDIFF
|
|
|
|
else:
|
|
|
|
return ZERO
|
|
|
|
|
|
|
|
def tzname(self, dt):
|
2013-09-08 15:04:31 +08:00
|
|
|
return _time.tzname[self._isdst(dt)]
|
2011-11-18 21:01:06 +08:00
|
|
|
|
|
|
|
def _isdst(self, dt):
|
|
|
|
tt = (dt.year, dt.month, dt.day,
|
|
|
|
dt.hour, dt.minute, dt.second,
|
|
|
|
dt.weekday(), 0, 0)
|
|
|
|
stamp = _time.mktime(tt)
|
|
|
|
tt = _time.localtime(stamp)
|
|
|
|
return tt.tm_isdst > 0
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2013-09-08 07:56:49 +08:00
|
|
|
class LocalTimezone(ReferenceLocalTimezone):
|
|
|
|
"""
|
|
|
|
Slightly improved local time implementation focusing on correctness.
|
|
|
|
|
|
|
|
It still crashes on dates before 1970 or after 2038, but at least the
|
|
|
|
error message is helpful.
|
|
|
|
"""
|
|
|
|
|
2013-09-08 15:04:31 +08:00
|
|
|
def tzname(self, dt):
|
|
|
|
is_dst = False if dt is None else self._isdst(dt)
|
|
|
|
return _time.tzname[is_dst]
|
|
|
|
|
2013-09-08 07:56:49 +08:00
|
|
|
def _isdst(self, dt):
|
|
|
|
try:
|
|
|
|
return super(LocalTimezone, self)._isdst(dt)
|
|
|
|
except (OverflowError, ValueError) as exc:
|
|
|
|
exc_type = type(exc)
|
|
|
|
exc_value = exc_type(
|
2013-10-20 07:33:10 +08:00
|
|
|
"Unsupported value: %r. You should install pytz." % dt)
|
2013-09-08 07:56:49 +08:00
|
|
|
exc_value.__cause__ = exc
|
2015-11-25 19:54:52 +08:00
|
|
|
if not hasattr(exc, '__traceback__'):
|
|
|
|
exc.__traceback__ = sys.exc_info()[2]
|
2013-09-08 07:56:49 +08:00
|
|
|
six.reraise(exc_type, exc_value, sys.exc_info()[2])
|
2011-11-18 21:01:06 +08:00
|
|
|
|
|
|
|
utc = pytz.utc if pytz else UTC()
|
|
|
|
"""UTC time zone as a tzinfo instance."""
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2013-09-08 15:04:31 +08:00
|
|
|
def get_fixed_timezone(offset):
|
|
|
|
"""
|
|
|
|
Returns a tzinfo instance with a fixed offset from UTC.
|
|
|
|
"""
|
|
|
|
if isinstance(offset, timedelta):
|
|
|
|
offset = offset.seconds // 60
|
|
|
|
sign = '-' if offset < 0 else '+'
|
|
|
|
hhmm = '%02d%02d' % divmod(abs(offset), 60)
|
|
|
|
name = sign + hhmm
|
|
|
|
return FixedOffset(offset, name)
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2014-11-19 04:50:03 +08:00
|
|
|
# In order to avoid accessing settings at compile time,
|
|
|
|
# wrap the logic in a function and cache the result.
|
|
|
|
@lru_cache.lru_cache()
|
2011-11-18 21:01:06 +08:00
|
|
|
def get_default_timezone():
|
|
|
|
"""
|
|
|
|
Returns the default time zone as a tzinfo instance.
|
|
|
|
|
|
|
|
This is the time zone defined by settings.TIME_ZONE.
|
|
|
|
"""
|
2014-11-19 04:50:03 +08:00
|
|
|
if isinstance(settings.TIME_ZONE, six.string_types) and pytz is not None:
|
|
|
|
return pytz.timezone(settings.TIME_ZONE)
|
|
|
|
else:
|
|
|
|
# This relies on os.environ['TZ'] being set to settings.TIME_ZONE.
|
|
|
|
return LocalTimezone()
|
2011-11-18 21:01:06 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
# This function exists for consistency with get_current_timezone_name
|
|
|
|
def get_default_timezone_name():
|
|
|
|
"""
|
|
|
|
Returns the name of the default time zone.
|
|
|
|
"""
|
|
|
|
return _get_timezone_name(get_default_timezone())
|
|
|
|
|
|
|
|
_active = local()
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def get_current_timezone():
|
|
|
|
"""
|
|
|
|
Returns the currently active time zone as a tzinfo instance.
|
|
|
|
"""
|
|
|
|
return getattr(_active, "value", get_default_timezone())
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def get_current_timezone_name():
|
|
|
|
"""
|
|
|
|
Returns the name of the currently active time zone.
|
|
|
|
"""
|
|
|
|
return _get_timezone_name(get_current_timezone())
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def _get_timezone_name(timezone):
|
|
|
|
"""
|
|
|
|
Returns the name of ``timezone``.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# for pytz timezones
|
|
|
|
return timezone.zone
|
|
|
|
except AttributeError:
|
|
|
|
# for regular tzinfo objects
|
2013-02-12 04:50:49 +08:00
|
|
|
return timezone.tzname(None)
|
2011-11-18 21:01:06 +08:00
|
|
|
|
|
|
|
# Timezone selection functions.
|
|
|
|
|
|
|
|
# These functions don't change os.environ['TZ'] and call time.tzset()
|
|
|
|
# because it isn't thread safe.
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def activate(timezone):
|
|
|
|
"""
|
|
|
|
Sets the time zone for the current thread.
|
|
|
|
|
|
|
|
The ``timezone`` argument must be an instance of a tzinfo subclass or a
|
|
|
|
time zone name. If it is a time zone name, pytz is required.
|
|
|
|
"""
|
|
|
|
if isinstance(timezone, tzinfo):
|
|
|
|
_active.value = timezone
|
2012-07-20 20:22:00 +08:00
|
|
|
elif isinstance(timezone, six.string_types) and pytz is not None:
|
2011-11-18 21:01:06 +08:00
|
|
|
_active.value = pytz.timezone(timezone)
|
|
|
|
else:
|
|
|
|
raise ValueError("Invalid timezone: %r" % timezone)
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def deactivate():
|
|
|
|
"""
|
|
|
|
Unsets the time zone for the current thread.
|
|
|
|
|
|
|
|
Django will then use the time zone defined by settings.TIME_ZONE.
|
|
|
|
"""
|
|
|
|
if hasattr(_active, "value"):
|
|
|
|
del _active.value
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2014-08-31 02:06:38 +08:00
|
|
|
class override(ContextDecorator):
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
|
|
|
Temporarily set the time zone for the current thread.
|
|
|
|
|
|
|
|
This is a context manager that uses ``~django.utils.timezone.activate()``
|
|
|
|
to set the timezone on entry, and restores the previously active timezone
|
|
|
|
on exit.
|
|
|
|
|
|
|
|
The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
|
|
|
|
time zone name, or ``None``. If is it a time zone name, pytz is required.
|
|
|
|
If it is ``None``, Django enables the default time zone.
|
|
|
|
"""
|
|
|
|
def __init__(self, timezone):
|
|
|
|
self.timezone = timezone
|
|
|
|
|
|
|
|
def __enter__(self):
|
2014-08-29 04:26:37 +08:00
|
|
|
self.old_timezone = getattr(_active, 'value', None)
|
2011-11-18 21:01:06 +08:00
|
|
|
if self.timezone is None:
|
|
|
|
deactivate()
|
|
|
|
else:
|
|
|
|
activate(self.timezone)
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
2013-01-31 23:00:39 +08:00
|
|
|
if self.old_timezone is None:
|
|
|
|
deactivate()
|
2011-11-18 21:01:06 +08:00
|
|
|
else:
|
2013-01-31 23:00:39 +08:00
|
|
|
_active.value = self.old_timezone
|
2011-11-18 21:01:06 +08:00
|
|
|
|
|
|
|
|
2012-03-04 03:57:25 +08:00
|
|
|
# Templates
|
2011-11-18 21:01:06 +08:00
|
|
|
|
2012-04-29 21:37:23 +08:00
|
|
|
def template_localtime(value, use_tz=None):
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
|
|
|
Checks if value is a datetime and converts it to local time if necessary.
|
|
|
|
|
|
|
|
If use_tz is provided and is not None, that will force the value to
|
|
|
|
be converted (or not), overriding the value of settings.USE_TZ.
|
2012-03-04 03:57:25 +08:00
|
|
|
|
|
|
|
This function is designed for use by the template engine.
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
2016-04-04 08:37:32 +08:00
|
|
|
should_convert = (
|
|
|
|
isinstance(value, datetime) and
|
|
|
|
(settings.USE_TZ if use_tz is None else use_tz) and
|
|
|
|
not is_naive(value) and
|
|
|
|
getattr(value, 'convert_to_local_time', True)
|
|
|
|
)
|
2012-04-29 21:37:23 +08:00
|
|
|
return localtime(value) if should_convert else value
|
2011-11-18 21:01:06 +08:00
|
|
|
|
2012-03-04 03:57:25 +08:00
|
|
|
|
|
|
|
# Utilities
|
|
|
|
|
2016-09-01 08:19:33 +08:00
|
|
|
def localtime(value=None, timezone=None):
|
2012-04-29 21:37:23 +08:00
|
|
|
"""
|
|
|
|
Converts an aware datetime.datetime to local time.
|
|
|
|
|
2016-09-01 08:19:33 +08:00
|
|
|
Only aware datetimes are allowed. When value is omitted, it defaults to
|
|
|
|
now().
|
|
|
|
|
2012-04-29 21:37:23 +08:00
|
|
|
Local time is defined by the current time zone, unless another time zone
|
|
|
|
is specified.
|
|
|
|
"""
|
2016-09-01 08:19:33 +08:00
|
|
|
if value is None:
|
|
|
|
value = now()
|
2012-04-29 21:37:23 +08:00
|
|
|
if timezone is None:
|
|
|
|
timezone = get_current_timezone()
|
2014-05-17 05:12:59 +08:00
|
|
|
# If `value` is naive, astimezone() will raise a ValueError,
|
|
|
|
# so we don't need to perform a redundant check.
|
2012-04-29 21:37:23 +08:00
|
|
|
value = value.astimezone(timezone)
|
|
|
|
if hasattr(timezone, 'normalize'):
|
2014-05-17 05:12:59 +08:00
|
|
|
# This method is available for pytz time zones.
|
2012-04-29 21:37:23 +08:00
|
|
|
value = timezone.normalize(value)
|
|
|
|
return value
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2016-09-01 08:19:33 +08:00
|
|
|
def localdate(value=None, timezone=None):
|
|
|
|
"""
|
|
|
|
Convert an aware datetime to local time and return the value's date.
|
|
|
|
|
|
|
|
Only aware datetimes are allowed. When value is omitted, it defaults to
|
|
|
|
now().
|
|
|
|
|
|
|
|
Local time is defined by the current time zone, unless another time zone is
|
|
|
|
specified.
|
|
|
|
"""
|
|
|
|
return localtime(value, timezone).date()
|
|
|
|
|
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def now():
|
|
|
|
"""
|
|
|
|
Returns an aware or naive datetime.datetime, depending on settings.USE_TZ.
|
|
|
|
"""
|
|
|
|
if settings.USE_TZ:
|
|
|
|
# timeit shows that datetime.now(tz=utc) is 24% slower
|
|
|
|
return datetime.utcnow().replace(tzinfo=utc)
|
|
|
|
else:
|
|
|
|
return datetime.now()
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-25 17:25:43 +08:00
|
|
|
# By design, these four functions don't perform any checks on their arguments.
|
|
|
|
# The caller should ensure that they don't receive an invalid value like None.
|
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def is_aware(value):
|
|
|
|
"""
|
|
|
|
Determines if a given datetime.datetime is aware.
|
|
|
|
|
2015-05-02 20:02:39 +08:00
|
|
|
The concept is defined in Python's docs:
|
2011-11-18 21:01:06 +08:00
|
|
|
http://docs.python.org/library/datetime.html#datetime.tzinfo
|
2015-05-02 20:02:39 +08:00
|
|
|
|
|
|
|
Assuming value.tzinfo is either None or a proper datetime.tzinfo,
|
|
|
|
value.utcoffset() implements the appropriate logic.
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
2015-05-02 20:02:39 +08:00
|
|
|
return value.utcoffset() is not None
|
2011-11-18 21:01:06 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-11-18 21:01:06 +08:00
|
|
|
def is_naive(value):
|
|
|
|
"""
|
|
|
|
Determines if a given datetime.datetime is naive.
|
|
|
|
|
2015-05-02 20:02:39 +08:00
|
|
|
The concept is defined in Python's docs:
|
2011-11-18 21:01:06 +08:00
|
|
|
http://docs.python.org/library/datetime.html#datetime.tzinfo
|
2015-05-02 20:02:39 +08:00
|
|
|
|
|
|
|
Assuming value.tzinfo is either None or a proper datetime.tzinfo,
|
|
|
|
value.utcoffset() implements the appropriate logic.
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
2015-05-02 20:02:39 +08:00
|
|
|
return value.utcoffset() is None
|
2011-11-18 21:01:06 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2015-03-31 13:58:37 +08:00
|
|
|
def make_aware(value, timezone=None, is_dst=None):
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
|
|
|
Makes a naive datetime.datetime in a given time zone aware.
|
|
|
|
"""
|
2014-10-16 10:52:41 +08:00
|
|
|
if timezone is None:
|
|
|
|
timezone = get_current_timezone()
|
2011-11-18 21:01:06 +08:00
|
|
|
if hasattr(timezone, 'localize'):
|
2014-05-17 05:12:59 +08:00
|
|
|
# This method is available for pytz time zones.
|
2015-03-31 13:58:37 +08:00
|
|
|
return timezone.localize(value, is_dst=is_dst)
|
2011-11-18 21:01:06 +08:00
|
|
|
else:
|
2014-05-17 05:12:59 +08:00
|
|
|
# Check that we won't overwrite the timezone of an aware datetime.
|
|
|
|
if is_aware(value):
|
|
|
|
raise ValueError(
|
2014-05-18 00:54:34 +08:00
|
|
|
"make_aware expects a naive datetime, got %s" % value)
|
2014-05-17 05:12:59 +08:00
|
|
|
# This may be wrong around DST changes!
|
2011-11-18 21:01:06 +08:00
|
|
|
return value.replace(tzinfo=timezone)
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2014-10-16 10:52:41 +08:00
|
|
|
def make_naive(value, timezone=None):
|
2011-11-18 21:01:06 +08:00
|
|
|
"""
|
|
|
|
Makes an aware datetime.datetime naive in a given time zone.
|
|
|
|
"""
|
2014-10-16 10:52:41 +08:00
|
|
|
if timezone is None:
|
|
|
|
timezone = get_current_timezone()
|
2014-05-17 05:12:59 +08:00
|
|
|
# If `value` is naive, astimezone() will raise a ValueError,
|
|
|
|
# so we don't need to perform a redundant check.
|
2011-11-18 21:01:06 +08:00
|
|
|
value = value.astimezone(timezone)
|
|
|
|
if hasattr(timezone, 'normalize'):
|
2014-05-17 05:12:59 +08:00
|
|
|
# This method is available for pytz time zones.
|
2012-01-03 04:45:09 +08:00
|
|
|
value = timezone.normalize(value)
|
2011-11-18 21:01:06 +08:00
|
|
|
return value.replace(tzinfo=None)
|