Skip to content

Conversation

AhmedNassar7
Copy link
Contributor

@AhmedNassar7 AhmedNassar7 commented Apr 20, 2025

Trac ticket number

ticket-35333

Branch description

The date and time template filters now respect the localization context when used with the unlocalize tag or within a {% localize off %} block. This is achieved by passing the current localization context (use_l10n flag) from the template rendering context to the filter functions, allowing them to properly format dates and times according to the unlocalized settings when localization is disabled. Previously, these filters would ignore the localization context setting.

Thanks to @claudep for the initial work on PR #18021 and to @nessita for guidance on the ticket.

Checklist

  • This PR targets the main branch.
  • The commit message is written in past tense, mentions the ticket number, and ends with a period.
  • I have checked the "Has patch" ticket flag in the Trac system.
  • I have added or updated relevant tests.
  • I have added or updated relevant docs, including release notes if applicable.
  • I have attached screenshots in both light and dark modes for any UI changes.

@sarahboyce
Copy link
Contributor

Thank you for the PR @AhmedNassar7
I am not convinced with the approach here. I am wondering if we should introduce a keyword argument to filters similar to needs_autoescape in order to pass the context

For example (note more docs changes and a release note would be required):

--- a/django/template/base.py
+++ b/django/template/base.py
@@ -748,15 +748,14 @@ class FilterExpression:
                     arg_vals.append(mark_safe(arg))
                 else:
                     arg_vals.append(arg.resolve(context))
-            kwargs = {}
             if getattr(func, "expects_localtime", False):
                 obj = template_localtime(obj, context.use_tz)
-                if func.__name__ in ("date", "time"):
-                    kwargs["use_l10n"] = context.use_l10n
+            kwargs = {}
+            if getattr(func, "needs_use_l10n", False):
+                kwargs["use_l10n"] = context.use_l10n
             if getattr(func, "needs_autoescape", False):
-                new_obj = func(obj, autoescape=context.autoescape, *arg_vals, **kwargs)
-            else:
-                new_obj = func(obj, *arg_vals, **kwargs)
+                kwargs["autoescape"] = context.autoescape
+            new_obj = func(obj, *arg_vals, **kwargs)
             if getattr(func, "is_safe", False) and isinstance(obj, SafeData):
                 obj = mark_safe(new_obj)
             else:
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 7676f543f2..1917d34bd3 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -775,7 +775,7 @@ def get_digit(value, arg):
 ###################
 
 
-@register.filter(expects_localtime=True, is_safe=False)
+@register.filter(expects_localtime=True, is_safe=False, needs_use_l10n=True)
 def date(value, arg=None, use_l10n=None):
     """Format a date according to the given format."""
     if value in (None, ""):
@@ -789,7 +789,7 @@ def date(value, arg=None, use_l10n=None):
             return ""
 
 
-@register.filter(expects_localtime=True, is_safe=False)
+@register.filter(expects_localtime=True, is_safe=False, needs_use_l10n=True)
 def time(value, arg=None, use_l10n=None):
     """Format a time according to the given format."""
     if value in (None, ""):
diff --git a/django/template/library.py b/django/template/library.py
index 3ec39ff572..4107f10c27 100644
--- a/django/template/library.py
+++ b/django/template/library.py
@@ -80,7 +80,7 @@ class Library:
         elif name is not None and filter_func is not None:
             # register.filter('somename', somefunc)
             self.filters[name] = filter_func
-            for attr in ("expects_localtime", "is_safe", "needs_autoescape"):
+            for attr in ("expects_localtime", "is_safe", "needs_autoescape", "needs_use_l10n"):
                 if attr in flags:
                     value = flags[attr]
                     # set the flag on the filter for FilterExpression.resolve
diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt
index e56ef54f02..1bd3ba2a01 100644
--- a/docs/howto/custom-template-tags.txt
+++ b/docs/howto/custom-template-tags.txt
@@ -157,11 +157,16 @@ You can use ``register.filter()`` as a decorator instead::
 If you leave off the ``name`` argument, as in the second example above, Django
 will use the function's name as the filter name.
 
-Finally, ``register.filter()`` also accepts three keyword arguments,
-``is_safe``, ``needs_autoescape``, and ``expects_localtime``. These arguments
-are described in :ref:`filters and auto-escaping <filters-auto-escaping>` and
+Finally, ``register.filter()`` also accepts four keyword arguments,
+``is_safe``, ``needs_autoescape``, ``needs_use_l10n``, and
+``expects_localtime``. These arguments are described in
+:ref:`filters and auto-escaping <filters-auto-escaping>` and
 :ref:`filters and time zones <filters-timezones>` below.
 
+.. versionchanged:: 6.0
+
+    The ``needs_use_l10n`` keyword argument was added.
+
 Template filters that expect strings
 ------------------------------------

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants