# # templatetags/localdt.py # from numbers import Number from datetime import datetime import pytz from django import template from django.template.defaultfilters import timesince from django.utils import formats def utcnow(): # note: needed so we can override this during tests return datetime.utcnow() class LocalBase(template.Node): def __init__(self, args): self.args = args @classmethod def usage(cls): return template.TemplateSyntaxError( cls.usage %(cls.tag_name, ) ) @classmethod def parse(cls, parser, token): args = token.split_contents()[1:] if len(args) < 1: raise cls.usage() return cls(args) def _parse(self): context = self.context in_args = list(self.args) if hasattr(context.get(in_args[0]), "localize"): tz = context[in_args.pop(0)] else: tz = context["localtz"] self.tz = tz # datetime self.datetime = context[in_args.pop(0)] # arg arg = None if in_args and in_args[0] != "as": arg = in_args.pop(0) self.arg = arg # output_var output_var = None if in_args: if len(in_args) != 2: raise self.usage() output_var = in_args[1] self.output_var = output_var def _localize_datetime(self, tz, value): if isinstance(value, Number): value = datetime.utcfromtimestamp(value) value_utc = value.replace(tzinfo=pytz.utc) return tz.normalize(value_utc.astimezone(tz)) def _render(self): raise Exception("Override this!") def render(self, context): self.context = context self._parse() value = self._render() if self.output_var: context[self.output_var] = value return "" return value class LocalDateTimeBase(LocalBase): """ Returns a localized datetime, date or time. {% local{dt,date,time} [tz] datetime [format] [as name] %} If ``tz`` is not specified the ``localtz`` context variable is used. Formatting is done by ``django.utils.formats``. """ usage = "usage: {%% %s [tz] datetime [format] [as name] %%}" def _render(self): format = self.arg localized = self._localize_datetime(self.tz, self.datetime) return self.format_func(localized, format) class LocalDateTime(LocalDateTimeBase): tag_name = "localdt" def format_func(self, value, format=None): format = format or "DATETIME_FORMAT" return formats.date_format(value, format) class LocalDate(LocalDateTimeBase): tag_name = "localdate" format_func = staticmethod(formats.date_format) class LocalTime(LocalDateTimeBase): tag_name = "localtime" format_func = staticmethod(formats.time_format) class LocalTimesince(LocalBase): """ Returns a localized timesince. {% timesince [tz] datetime [target] [as name] %} """ tag_name = "localtimesince" usage = "usage: {%% %s [tz] datetime [target] [as name] %%}" def _render(self): target = self.arg if target is None: target = utcnow() else: target = self.context[target] target = self._localize_datetime(self.tz, target) localized = self._localize_datetime(self.tz, self.datetime) return timesince(localized, target) def register_all(register, values): for value in values: if hasattr(value, "tag_name"): register.tag(value.tag_name, value.parse) register = template.Library() register_all(register, globals().values()) # # templatetags/tests/test_localdt.py # import pytz from datetime import datetime from django.template import Template, Context from nose.tools import assert_equal from .. import localdt import new from functools import wraps def parameterized(input): """ Parameterize a test case: >>> class TestFoo(object): ... data = [ (1, 2), (3, 4) ] ... @parameterized(data) ... def test_add1(self, input, expected): ... assert_equal(add1(input), expected) >>> """ if not hasattr(input, "__iter__"): raise ValueError("expected iterable input; got %r" %(input, )) def parameterized_heper(f): attached_instance_method = [False] @wraps(f) def parameterized_helper_method(self): if not attached_instance_method[0]: # confusingly, we need to create a named instance method and # attach that to the class... cls = self.__class__ im_f = new.instancemethod(f, None, cls) setattr(cls, f.__name__, im_f) attached_instance_method[0] = True for args in input: if isinstance(args, basestring): args = [args] # ... then pull that named instance method off, turning it into # a bound method ... args = [getattr(self, f.__name__)] + list(args) # ... then yield that as a tuple. If those steps aren't # followed precicely, Nose gets upset and doesn't run the test # or doesn't run setup methods. yield tuple(args) f.__name__ = "_helper_for_%s" %(f.__name__, ) return parameterized_helper_method return parameterized_heper class TestLocalDateTime(object): test_datetime = datetime.utcfromtimestamp(1313945312.076319) second_datetime = datetime.utcfromtimestamp(1313950000.0) test_tz = pytz.timezone("Japan") def _render(self, template, context): template = Template(template) context = Context(context) return template.render(context) @parameterized([ ["localdt", "Aug. 22, 2011, 1:48 a.m."], ["localdate", "Aug. 22, 2011"], ["localtime", "1:48 a.m."], ]) def test_conversion(self, tag, expected): template = "{%% load localdt %%}{%% %s some_dt %%}" %(tag, ) context = { "localtz": self.test_tz, "some_dt": self.test_datetime, } result = self._render(template, context) assert_equal(result, expected) @parameterized([ ["{% localtime other_tz some_dt %}", "6:48 p.m."], ["{% localtime other_tz some_dt as foo %}a{{ foo }}b", "a6:48 p.m.b"], ["{% localtime some_dt as foo %}a{{ foo }}b", "a1:48 a.m.b"], ["{% localtimesince some_dt %}", "1 hour, 18 minutes"], ["{% localtimesince other_tz some_dt %}", "1 hour, 18 minutes"], ["{% localtimesince other_tz some_dt other_dt %}", "4 hours, 4 minutes"], ["{% localtimesince some_dt as foo %}a{{ foo }}b", "a1 hour, 18 minutesb"], ["{% localtimesince other_tz some_dt as foo %}a{{ foo }}b", "a1 hour, 18 minutesb"], ["{% localtimesince other_tz some_dt other_dt as foo %}a{{ foo }}b", "a4 hours, 4 minutesb"], ]) def test_parsing(self, input, expected): oldutcnow = localdt.utcnow localdt.utcnow = lambda: self.second_datetime try: template = "{% load localdt %}" + input context = { "localtz": self.test_tz, "other_tz": pytz.timezone("Africa/Johannesburg"), "some_dt": self.test_datetime, "other_dt": datetime.utcfromtimestamp(1313960000.0) } result = self._render(template, context) assert_equal(result, expected) finally: localdt.utcnow = oldutcnow