230 lines
6.0 KiB
Python
230 lines
6.0 KiB
Python
from datetime import datetime
|
|
from datetime import timezone as datetime_timezone
|
|
from datetime import tzinfo
|
|
|
|
try:
|
|
import zoneinfo
|
|
except ImportError:
|
|
from backports import zoneinfo
|
|
|
|
from django.conf import settings
|
|
from django.template import Library, Node, TemplateSyntaxError
|
|
from django.utils import timezone
|
|
|
|
register = Library()
|
|
|
|
|
|
# RemovedInDjango50Warning: shim to allow catching the exception in the calling
|
|
# scope if pytz is not installed.
|
|
class UnknownTimezoneException(BaseException):
|
|
pass
|
|
|
|
|
|
# RemovedInDjango50Warning
|
|
def timezone_constructor(tzname):
|
|
if settings.USE_DEPRECATED_PYTZ:
|
|
import pytz
|
|
|
|
try:
|
|
return pytz.timezone(tzname)
|
|
except pytz.UnknownTimeZoneError:
|
|
raise UnknownTimezoneException
|
|
try:
|
|
return zoneinfo.ZoneInfo(tzname)
|
|
except zoneinfo.ZoneInfoNotFoundError:
|
|
raise UnknownTimezoneException
|
|
|
|
|
|
# HACK: datetime instances cannot be assigned new attributes. Define a subclass
|
|
# in order to define new attributes in do_timezone().
|
|
class datetimeobject(datetime):
|
|
pass
|
|
|
|
|
|
# Template filters
|
|
|
|
|
|
@register.filter
|
|
def localtime(value):
|
|
"""
|
|
Convert a datetime to local time in the active time zone.
|
|
|
|
This only makes sense within a {% localtime off %} block.
|
|
"""
|
|
return do_timezone(value, timezone.get_current_timezone())
|
|
|
|
|
|
@register.filter
|
|
def utc(value):
|
|
"""
|
|
Convert a datetime to UTC.
|
|
"""
|
|
return do_timezone(value, datetime_timezone.utc)
|
|
|
|
|
|
@register.filter("timezone")
|
|
def do_timezone(value, arg):
|
|
"""
|
|
Convert a datetime to local time in a given time zone.
|
|
|
|
The argument must be an instance of a tzinfo subclass or a time zone name.
|
|
|
|
Naive datetimes are assumed to be in local time in the default time zone.
|
|
"""
|
|
if not isinstance(value, datetime):
|
|
return ""
|
|
|
|
# Obtain a timezone-aware datetime
|
|
try:
|
|
if timezone.is_naive(value):
|
|
default_timezone = timezone.get_default_timezone()
|
|
value = timezone.make_aware(value, default_timezone)
|
|
# Filters must never raise exceptions, and pytz' exceptions inherit
|
|
# Exception directly, not a specific subclass. So catch everything.
|
|
except Exception:
|
|
return ""
|
|
|
|
# Obtain a tzinfo instance
|
|
if isinstance(arg, tzinfo):
|
|
tz = arg
|
|
elif isinstance(arg, str):
|
|
try:
|
|
tz = timezone_constructor(arg)
|
|
except UnknownTimezoneException:
|
|
return ""
|
|
else:
|
|
return ""
|
|
|
|
result = timezone.localtime(value, tz)
|
|
|
|
# HACK: the convert_to_local_time flag will prevent
|
|
# automatic conversion of the value to local time.
|
|
result = datetimeobject(
|
|
result.year,
|
|
result.month,
|
|
result.day,
|
|
result.hour,
|
|
result.minute,
|
|
result.second,
|
|
result.microsecond,
|
|
result.tzinfo,
|
|
)
|
|
result.convert_to_local_time = False
|
|
return result
|
|
|
|
|
|
# Template tags
|
|
|
|
|
|
class LocalTimeNode(Node):
|
|
"""
|
|
Template node class used by ``localtime_tag``.
|
|
"""
|
|
|
|
def __init__(self, nodelist, use_tz):
|
|
self.nodelist = nodelist
|
|
self.use_tz = use_tz
|
|
|
|
def render(self, context):
|
|
old_setting = context.use_tz
|
|
context.use_tz = self.use_tz
|
|
output = self.nodelist.render(context)
|
|
context.use_tz = old_setting
|
|
return output
|
|
|
|
|
|
class TimezoneNode(Node):
|
|
"""
|
|
Template node class used by ``timezone_tag``.
|
|
"""
|
|
|
|
def __init__(self, nodelist, tz):
|
|
self.nodelist = nodelist
|
|
self.tz = tz
|
|
|
|
def render(self, context):
|
|
with timezone.override(self.tz.resolve(context)):
|
|
output = self.nodelist.render(context)
|
|
return output
|
|
|
|
|
|
class GetCurrentTimezoneNode(Node):
|
|
"""
|
|
Template node class used by ``get_current_timezone_tag``.
|
|
"""
|
|
|
|
def __init__(self, variable):
|
|
self.variable = variable
|
|
|
|
def render(self, context):
|
|
context[self.variable] = timezone.get_current_timezone_name()
|
|
return ""
|
|
|
|
|
|
@register.tag("localtime")
|
|
def localtime_tag(parser, token):
|
|
"""
|
|
Force or prevent conversion of datetime objects to local time,
|
|
regardless of the value of ``settings.USE_TZ``.
|
|
|
|
Sample usage::
|
|
|
|
{% localtime off %}{{ value_in_utc }}{% endlocaltime %}
|
|
"""
|
|
bits = token.split_contents()
|
|
if len(bits) == 1:
|
|
use_tz = True
|
|
elif len(bits) > 2 or bits[1] not in ("on", "off"):
|
|
raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0])
|
|
else:
|
|
use_tz = bits[1] == "on"
|
|
nodelist = parser.parse(("endlocaltime",))
|
|
parser.delete_first_token()
|
|
return LocalTimeNode(nodelist, use_tz)
|
|
|
|
|
|
@register.tag("timezone")
|
|
def timezone_tag(parser, token):
|
|
"""
|
|
Enable a given time zone just for this block.
|
|
|
|
The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
|
|
time zone name, or ``None``. If it is ``None``, the default time zone is
|
|
used within the block.
|
|
|
|
Sample usage::
|
|
|
|
{% timezone "Europe/Paris" %}
|
|
It is {{ now }} in Paris.
|
|
{% endtimezone %}
|
|
"""
|
|
bits = token.split_contents()
|
|
if len(bits) != 2:
|
|
raise TemplateSyntaxError("'%s' takes one argument (timezone)" % bits[0])
|
|
tz = parser.compile_filter(bits[1])
|
|
nodelist = parser.parse(("endtimezone",))
|
|
parser.delete_first_token()
|
|
return TimezoneNode(nodelist, tz)
|
|
|
|
|
|
@register.tag("get_current_timezone")
|
|
def get_current_timezone_tag(parser, token):
|
|
"""
|
|
Store the name of the current time zone in the context.
|
|
|
|
Usage::
|
|
|
|
{% get_current_timezone as TIME_ZONE %}
|
|
|
|
This will fetch the currently active time zone and put its name
|
|
into the ``TIME_ZONE`` context variable.
|
|
"""
|
|
# token.split_contents() isn't useful here because this tag doesn't accept
|
|
# variable as arguments.
|
|
args = token.contents.split()
|
|
if len(args) != 3 or args[1] != "as":
|
|
raise TemplateSyntaxError(
|
|
"'get_current_timezone' requires 'as variable' (got %r)" % args
|
|
)
|
|
return GetCurrentTimezoneNode(args[2])
|