[frameworks/ktexttemplate] /: Make date/time filter locale-aware

Volker Krause <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 3eecf70aab9096a29c2ee52a8cdf58bd56dd0176 by Volker Krause.
Committed on 16/08/2026 at 10:28.
Pushed by vkrause into branch 'master'.

Make date/time filter locale-aware

That's in line with what the Django template syntax does (which this is
all modeled after).

There's at least three different alternative solutions for locale-aware
date/time formatting across the PIM repos, which would become obsolete
with fixing this properly here.

M  +26   -2    autotests/testfilters.cpp
M  +29   -6    src/defaultfilters/datetime.cpp

https://invent.kde.org/frameworks/ktexttemplate/-/commit/3eecf70aab9096a29c2ee52a8cdf58bd56dd0176

diff --git a/autotests/testfilters.cpp b/autotests/testfilters.cpp
index 34dd67f5..7bbfa39d 100644
--- a/autotests/testfilters.cpp
+++ b/autotests/testfilters.cpp
@@ -18,9 +18,12 @@
 #include "context.h"
 #include "engine.h"
 #include "ktexttemplate_paths.h"
+#include "qtlocalizer.h"
 #include "template.h"
 #include <util.h>
 
+using namespace Qt::Literals;
+
 using Dict = QHash<QString, QVariant>;
 
 Q_DECLARE_METATYPE(KTextTemplate::Error)
@@ -90,6 +93,8 @@ void TestFilters::initTestCase()
         QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH),
         appDirPath + QStringLiteral("/tests/") // For testtags.qs
     });
+
+    m_engine->addDefaultLibrary(u"ktexttemplate_i18ntags"_s); // for with_locale for testing date/time filters
 }
 
 void TestFilters::cleanupTestCase()
@@ -107,6 +112,7 @@ void TestFilters::doTest()
     auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
 
     Context context(dict);
+    context.setLocalizer(QSharedPointer<AbstractLocalizer>(new QtLocalizer(QLocale(QLocale::English, QLocale::UnitedStates))));
 
     auto result = t->render(&context);
 
@@ -332,11 +338,29 @@ void TestFilters::testDateBasedFilters_data()
     dict.insert(QStringLiteral("d"), d);
 
     QTest::newRow("date01") << "{{ d|date:\"MM\" }}" << dict << QStringLiteral("01") << NoError;
-    QTest::newRow("date02") << QStringLiteral("{{ d|date }}") << dict << d.toString(QStringLiteral("MMM. d, yyyy")) << NoError;
+    QTest::newRow("date02") << QStringLiteral("{{ d|date }}") << dict << QLocale(u"en_US"_s).toString(d.date(), QLocale::LongFormat) << NoError;
+    QTest::newRow("date03") << "{{ d|date:\"MMMM\" }}" << dict << u"January"_s << NoError;
+    QTest::newRow("date04") << "{{ d|date:\"SHORT_DATE_FORMAT\" }}" << dict << u"1/1/08"_s << NoError;
+    QTest::newRow("date05") << "{{ d|date:\"SHORT_DATETIME_FORMAT\" }}" << dict << u"1/1/08 12:00\u202fAM"_s << NoError;
 
     dict.clear();
     dict.insert(QStringLiteral("d"), QStringLiteral("fail_string"));
-    QTest::newRow("date03") << "{{ d|date:\"MM\" }}" << dict << QString() << NoError;
+    QTest::newRow("date06") << "{{ d|date:\"MM\" }}" << dict << QString() << NoError;
+
+    dict.clear();
+    dict.insert(u"d"_s, QDate{2026, 7, 14});
+    QTest::newRow("date07") << u"{% with_locale \"de_DE\" %}{{ d|date:\"SHORT_DATE_FORMAT\" }}{% endwith_locale %}"_s << dict << u"14.07.26"_s << NoError;
+    QTest::newRow("date08") << u"{% with_locale \"de_DE\" %}{{ d|date }}{% endwith_locale %}"_s << dict << u"Dienstag, 14. Juli 2026"_s << NoError;
+    QTest::newRow("date09") << u"{% with_locale \"de_DE\" %}{{ d|date:\"DATE_FORMAT\" }}{% endwith_locale %}"_s << dict << u"Dienstag, 14. Juli 2026"_s
+                            << NoError;
+    QTest::newRow("date10") << u"{% with_locale \"de_DE\" %}{{ d|date:\"MMMM\" }}{% endwith_locale %}"_s << dict << u"Juli"_s << NoError;
+
+    dict.clear();
+    dict.insert(u"t"_s, QTime{13, 24, 56});
+    QTest::newRow("time01") << u"{{ t|time }}"_s << dict << u"1:24\u202fPM"_s << NoError;
+    QTest::newRow("time02") << u"{{ t|time:\"H'h'mm\" }}"_s << dict << u"13h24"_s << NoError;
+    QTest::newRow("time03") << u"{% with_locale \"de_DE\" %}{{ t|time }}{% endwith_locale %}"_s << dict << u"13:24"_s << NoError;
+    QTest::newRow("time04") << u"{% with_locale \"de_DE\" %}{{ t|time:\"TIME_FORMAT\" }}{% endwith_locale %}"_s << dict << u"13:24"_s << NoError;
 }
 
 void TestFilters::testStringFilters_data()
diff --git a/src/defaultfilters/datetime.cpp b/src/defaultfilters/datetime.cpp
index 6cb590fd..ea144bad 100644
--- a/src/defaultfilters/datetime.cpp
+++ b/src/defaultfilters/datetime.cpp
@@ -13,6 +13,8 @@
 
 #include <QDateTime>
 
+using namespace Qt::Literals;
+
 QVariant timeSince(const QDateTime &early, const QDateTime &late)
 {
     Q_ASSERT(early.isValid());
@@ -81,12 +83,26 @@ QVariant DateFilter::doFilter(const QVariant &input, const QVariant &argument, b
         d = QDateTime::fromString(getSafeString(input), Qt::ISODateWithMs);
     }
 
-    auto argString = getSafeString(argument);
+    const QString argString = getSafeString(argument);
 
-    if (!argString.get().isEmpty())
-        return d.toString(argString);
+    // locale-specific format constants as defined in https://docs.djangoproject.com/en/6.0/ref/templates/builtins/#date
+    if (argString.isEmpty() || argString == "DATE_FORMAT"_L1) {
+        return context()->localizer()->localizeDate(d.date(), QLocale::LongFormat);
+    }
+    if (argString == "SHORT_DATE_FORMAT"_L1) {
+        return context()->localizer()->localizeDate(d.date(), QLocale::ShortFormat);
+    }
+    if (argString == "DATETIME_FORMAT"_L1) {
+        return context()->localizer()->localizeDateTime(d, QLocale::LongFormat);
+    }
+    if (argString == "SHORT_DATETIME_FORMAT"_L1) {
+        return context()->localizer()->localizeDateTime(d, QLocale::ShortFormat);
+    }
 
-    return d.toString(QStringLiteral("MMM. d, yyyy"));
+    // custom format
+    Q_ASSERT(!argString.isEmpty());
+    QLocale l(context()->localizer()->currentLocale());
+    return l.toString(d, argString);
 }
 
 QVariant TimeFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
@@ -103,8 +119,15 @@ QVariant TimeFilter::doFilter(const QVariant &input, const QVariant &argument, b
         d = QDateTime::fromString(getSafeString(input), Qt::ISODateWithMs);
     }
 
-    auto argString = getSafeString(argument);
-    return d.toString(argString);
+    const QString argString = getSafeString(argument);
+
+    if (argString.isEmpty() || argString == "TIME_FORMAT"_L1) {
+        return context()->localizer()->localizeTime(d.time(), QLocale::ShortFormat);
+    }
+
+    Q_ASSERT(!argString.isEmpty());
+    QLocale l(context()->localizer()->currentLocale());
+    return l.toString(d.time(), argString);
 }
 
 QVariant TimeSinceFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.