# HG changeset patch # User Goffi # Date 1509121210 -7200 # Node ID dec31114c402197010b78a929d152fb19fc1962c # Parent f905dfe69fccc7bb32c32b4cb08804221716b837 template: improved date formatter: the date filter is now named "date_fmt" and can be use to specify several useful formats (see docstring), using the current locale. The "relative" format return the whole human readable relative date. The new in_the_past test allow to check if a date is passed. diff -r f905dfe69fcc -r dec31114c402 src/tools/common/template.py --- a/src/tools/common/template.py Fri Oct 27 18:17:35 2017 +0200 +++ b/src/tools/common/template.py Fri Oct 27 18:20:10 2017 +0200 @@ -31,6 +31,7 @@ from babel import support from babel import Locale from babel.core import UnknownLocaleError +from babel import dates try: import sat_templates except ImportError: @@ -44,9 +45,10 @@ raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2') from jinja2 import Markup as safe +from jinja2 import is_undefined HTML_EXT = ('html', 'xhtml') -DEFAULT_LOCALE = u'en' +DEFAULT_LOCALE = u'en_GB' RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]') # TODO: handle external path (an additional search path for templates should be settable by user # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason @@ -200,11 +202,13 @@ # custom filters self.env.filters['next_gidx'] = self._next_gidx self.env.filters['cur_gidx'] = self._cur_gidx - self.env.filters['date_days'] = self._date_days + self.env.filters['date_fmt'] = self._date_fmt self.env.filters['xmlui_class'] = self._xmlui_class self.env.filters['attr_escape'] = self.attr_escape self.env.filters['item_filter'] = self._item_filter self.env.filters['adv_format'] = self._adv_format + # custom tests + self.env.tests['in_the_past'] = self._in_the_past def installTranslations(self): i18n_dir = os.path.join(self.base_dir, 'i18n') @@ -226,8 +230,16 @@ self.env.install_null_translations(True) def setLocale(self, locale_str): + """set current locale + + change current translation locale and self self._locale and self._locale_str + """ if locale_str == self._locale_str: return + if locale_str == 'en': + # we default to GB English when it's not specified + # one of the main reason is to avoid the nonsense U.S. short date format + locale_str = 'en_GB' try: locale = Locale.parse(locale_str) except ValueError as e: @@ -324,6 +336,9 @@ return css_files + + ## custom filters ## + @jinja2.contextfilter def _next_gidx(self, ctx, value): """Use next current global index as suffix""" @@ -336,8 +351,54 @@ current = ctx['gidx'].current(value) return value if not current else u"{}_{}".format(value, current) - def _date_days(self, timestamp): - return int(time.time() - int(timestamp))/(3600*24) + def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None): + """format date according to locale + + @param timestamp(basestring, int): unix time + @param fmt(str): one of: + - short: e.g. u'31/12/17' + - medium: e.g. u'Apr 1, 2007' + - long: e.g. u'April 1, 2007' + - full: e.g. u'Sunday, April 1, 2007' + - relative: format in relative time + e.g.: 3 hours + note that this format is not precise + - iso: ISO 8601 format + e.g.: u'2007-04-01T19:53:23Z' + - auto_limit (int, None): limit in days before using auto_old_fmt + None: use default(7 days) + - auto_old_fmt(unicode, None): format to use when date is olded than limit + None: use default(short) + or a free value which is passed to babel.dates.format_datetime + @param date_only(bool): if True, only display date (not datetime) + + """ + if is_undefined(fmt): + fmt = u'short' + + if (auto_limit is not None or auto_old_fmt is not None) and fmt != 'auto': + raise ValueError(u'auto argument can only be used with auto fmt') + if fmt == 'auto': + days_delta = (time.time() - int(timestamp)) / 3600 + if days_delta > (auto_limit or 7): + fmt = auto_old_fmt or 'short' + else: + fmt = 'relative' + + if fmt == 'relative': + delta = int(timestamp) - time.time() + return dates.format_timedelta(delta, granularity="minute", add_direction=True, locale=self._locale_str) + elif fmt in ('short', 'long'): + formatter = dates.format_date if date_only else dates.format_datetime + return formatter(int(timestamp), format=fmt, locale=self._locale_str) + elif fmt == 'iso': + if date_only: + fmt = 'yyyy-MM-dd' + else: + fmt = "yyyy-MM-ddTHH:mm:ss'Z'" + return dates.format_datetime(int(timestamp), format=fmt) + else: + return dates.format_datetime(int(timestamp), format=fmt, locale=self._locale_str) def attr_escape(self, text): """escape a text to a value usable as an attribute @@ -409,6 +470,16 @@ # jinja use string when no special char is used, so we have to convert to unicode return unicode(template).format(value=value, **kwargs) + ## custom tests ## + + def _in_the_past(self, timestamp): + """check if a date is in the past + + @param timestamp(unicode, int): unix time + @return (bool): True if date is in the past + """ + return time.time() > int(timestamp) + def render(self, template, theme=None, locale=DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs): """render a template