Skip to content

Commit

Permalink
Format date and datetime fields based on locale (#45617)
Browse files Browse the repository at this point in the history
  • Loading branch information
Joonalai authored and nyalldawson committed Dec 9, 2021
1 parent 8caad83 commit 4f91243
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 26 deletions.
Expand Up @@ -22,9 +22,9 @@ the field configuration.
#include "qgsdatetimefieldformatter.h"
%End
public:
static const QString DATE_FORMAT;
static QString DATE_FORMAT;
static const QString TIME_FORMAT;
static const QString DATETIME_FORMAT;
static QString DATETIME_FORMAT;
static const QString QT_ISO_FORMAT;
static const QString DISPLAY_FOR_ISO_FORMAT;

Expand All @@ -48,6 +48,7 @@ The type is expected to be one of
- QVariant.Date
- QVariant.Time
%End

};

/************************************************************************
Expand Down
9 changes: 6 additions & 3 deletions python/core/auto_generated/qgsapplication.sip.in
Expand Up @@ -443,11 +443,12 @@ Returns the QGIS locale.
.. versionadded:: 3.0
%End

static void setLocale( QLocale locale );
static void setLocale( const QLocale &locale );
%Docstring
Sets the QGIS locale.
Sets the QGIS locale - used mainly by 3rd party apps and tests.
In QGIS this is internally triggered by the application in startup.

.. versionadded:: 3.22
.. versionadded:: 3.22.2
%End

static QString userThemesFolder();
Expand Down Expand Up @@ -1091,6 +1092,8 @@ In order to register translatable strings, connect to this signal and register t
void localeChanged();
%Docstring
Emitted when project locale has been changed.

.. versionadded:: 3.22.2
%End


Expand Down
15 changes: 13 additions & 2 deletions src/core/fieldformatter/qgsdatetimefieldformatter.cpp
Expand Up @@ -20,9 +20,9 @@
#include "qgsvectorlayer.h"
#include "qgsapplication.h"

const QString QgsDateTimeFieldFormatter::DATE_FORMAT = QStringLiteral( "yyyy-MM-dd" );
QString QgsDateTimeFieldFormatter::DATE_FORMAT = QStringLiteral( "yyyy-MM-dd" );
const QString QgsDateTimeFieldFormatter::TIME_FORMAT = QStringLiteral( "HH:mm:ss" );
const QString QgsDateTimeFieldFormatter::DATETIME_FORMAT = QStringLiteral( "yyyy-MM-dd HH:mm:ss" );
QString QgsDateTimeFieldFormatter::DATETIME_FORMAT = QStringLiteral( "yyyy-MM-dd HH:mm:ss" );
// we need to use Qt::ISODate rather than a string format definition in QDate::fromString
const QString QgsDateTimeFieldFormatter::QT_ISO_FORMAT = QStringLiteral( "Qt ISO Date" );
// but QDateTimeEdit::setDisplayFormat only accepts string formats, so use with time zone by default
Expand Down Expand Up @@ -62,6 +62,10 @@ QString QgsDateTimeFieldFormatter::representValue( QgsVectorLayer *layer, int fi
// we always show time zones for datetime values
showTimeZone = true;
}
else if ( static_cast<QMetaType::Type>( value.type() ) == QMetaType::QTime )
{
return value.toTime().toString( displayFormat );
}
else
{
if ( fieldIsoFormat )
Expand Down Expand Up @@ -106,3 +110,10 @@ QString QgsDateTimeFieldFormatter::defaultFormat( QVariant::Type type )
return QgsDateTimeFieldFormatter::DATE_FORMAT;
}
}

void QgsDateTimeFieldFormatter::applyLocaleChange()
{
QString dateFormat = QLocale().dateFormat( QLocale::FormatType::ShortFormat );
QgsDateTimeFieldFormatter::DATETIME_FORMAT = QString( "%1 %2" ).arg( dateFormat, QgsDateTimeFieldFormatter::TIME_FORMAT );
QgsDateTimeFieldFormatter::DATE_FORMAT = dateFormat;
}
11 changes: 9 additions & 2 deletions src/core/fieldformatter/qgsdatetimefieldformatter.h
Expand Up @@ -31,9 +31,9 @@
class CORE_EXPORT QgsDateTimeFieldFormatter : public QgsFieldFormatter
{
public:
static const QString DATE_FORMAT;
static QString DATE_FORMAT;
static const QString TIME_FORMAT;
static const QString DATETIME_FORMAT;
static QString DATETIME_FORMAT;
static const QString QT_ISO_FORMAT;
static const QString DISPLAY_FOR_ISO_FORMAT;

Expand All @@ -55,6 +55,13 @@ class CORE_EXPORT QgsDateTimeFieldFormatter : public QgsFieldFormatter
* - QVariant::Time
*/
static QString defaultFormat( QVariant::Type type );

/**
* Adjusts the date time formats according to locale.
*
* \since QGIS 3.22.2
*/
static void applyLocaleChange(); SIP_SKIP;
};

#endif // QGSDATETIMEFIELDKIT_H
5 changes: 4 additions & 1 deletion src/core/qgsapplication.cpp
Expand Up @@ -85,6 +85,7 @@

#include "layout/qgspagesizeregistry.h"
#include "qgsrecentstylehandler.h"
#include "qgsdatetimefieldformatter.h"

#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
#include <QDesktopWidget>
Expand Down Expand Up @@ -230,6 +231,8 @@ QgsApplication::QgsApplication( int &argc, char **argv, bool GUIenabled, const Q
mApplicationMembers = new ApplicationMembers();

*sProfilePath() = profileFolder;

connect( instance(), &QgsApplication::localeChanged, &QgsDateTimeFieldFormatter::applyLocaleChange );
}

void QgsApplication::init( QString profileFolder )
Expand Down Expand Up @@ -1310,7 +1313,7 @@ QString QgsApplication::locale()
}
}

void QgsApplication::setLocale( QLocale locale )
void QgsApplication::setLocale( const QLocale &locale )
{
QLocale::setDefault( locale );
emit instance()->localeChanged();
Expand Down
10 changes: 7 additions & 3 deletions src/core/qgsapplication.h
Expand Up @@ -469,10 +469,12 @@ class CORE_EXPORT QgsApplication : public QApplication
static QString locale();

/**
* Sets the QGIS locale.
* \since QGIS 3.22
* Sets the QGIS locale - used mainly by 3rd party apps and tests.
* In QGIS this is internally triggered by the application in startup.
*
* \since QGIS 3.22.2
*/
static void setLocale( QLocale locale );
static void setLocale( const QLocale &locale );

//! Returns the path to user's themes folder
static QString userThemesFolder();
Expand Down Expand Up @@ -1052,6 +1054,8 @@ class CORE_EXPORT QgsApplication : public QApplication

/**
* Emitted when project locale has been changed.
*
* \since QGIS 3.22.2
*/
void localeChanged();

Expand Down
59 changes: 46 additions & 13 deletions tests/src/python/test_qgsfieldformatters.py
Expand Up @@ -24,6 +24,7 @@
Qt
)
from qgis.core import (
QgsApplication,
QgsFeature,
QgsProject,
QgsRelation,
Expand Down Expand Up @@ -679,34 +680,66 @@ def setUpClass(cls):
@classmethod
def tearDownClass(cls):
"""Reset locale"""
QLocale.setDefault(QLocale(QLocale.English))
QgsApplication.setLocale(QLocale(QLocale.English))

def test_representValue(self):
layer = QgsVectorLayer("point?field=int:integer&field=datetime:datetime&field=long:long",
layer = QgsVectorLayer("point?field=datetime:datetime&field=date:date&field=time:time",
"layer", "memory")
self.assertTrue(layer.isValid())
QgsProject.instance().addMapLayers([layer])

field_formatter = QgsDateTimeFieldFormatter()

# default configuration should show timezone information
config = {}
self.assertEqual(field_formatter.representValue(layer, 1, config, None,
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.UTC)),
'2020-03-04 12:13:14 (UTC)')
self.assertEqual(field_formatter.representValue(layer, 1, config, None,
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.OffsetFromUTC, 3600)),
'2020-03-04 12:13:14 (UTC+01:00)')

# if specific display format is set then use that
config = {"display_format": "dd/MM/yyyy HH:mm:ss"}
self.assertEqual(field_formatter.representValue(layer, 1, config, None,
self.assertEqual(field_formatter.representValue(layer, 0, config, None,
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.UTC)),
'04/03/2020 12:13:14')
self.assertEqual(field_formatter.representValue(layer, 1, config, None,
self.assertEqual(field_formatter.representValue(layer, 0, config, None,
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.OffsetFromUTC, 3600)),
'04/03/2020 12:13:14')

locale_assertions = {
QLocale(QLocale.English): {
"date_format": 'M/d/yy',
"time_format": 'HH:mm:ss',
"datetime_format": 'M/d/yy HH:mm:ss',
"datetime_utc": '3/4/20 12:13:14 (UTC)',
"datetime_utc+1": '3/4/20 12:13:14 (UTC+01:00)'
},
QLocale(QLocale.Finnish): {
"date_format": 'd.M.yyyy',
"time_format": 'HH:mm:ss',
"datetime_format": 'd.M.yyyy HH:mm:ss',
"datetime_utc": '4.3.2020 12:13:14 (UTC)',
"datetime_utc+1": '4.3.2020 12:13:14 (UTC+01:00)'
},
}

for locale, assertions in locale_assertions.items():
QgsApplication.setLocale(locale)
field_formatter = QgsDateTimeFieldFormatter()

self.assertEqual(field_formatter.defaultFormat(QVariant.Date), assertions["date_format"], locale.name())
self.assertEqual(field_formatter.defaultFormat(QVariant.Time), assertions["time_format"], locale.name())
self.assertEqual(field_formatter.defaultFormat(QVariant.DateTime), assertions["datetime_format"], locale.name())

# default configuration should show timezone information
config = {}
self.assertEqual(field_formatter.representValue(layer, 0, config, None,
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.UTC)),
assertions["datetime_utc"], locale.name())
self.assertEqual(field_formatter.representValue(layer, 0, config, None,
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.OffsetFromUTC, 3600)),
assertions["datetime_utc+1"], locale.name())
self.assertEqual(field_formatter.representValue(layer, 1, config, None,
QDate(2020, 3, 4)),
assertions["datetime_utc"].split(" ")[0], locale.name())
config = {"display_format": "HH:mm:s"}
self.assertEqual(field_formatter.representValue(layer, 2, config, None,
QTime(12, 13, 14)),
assertions["datetime_utc"].split(" ")[1], locale.name())


if __name__ == '__main__':
unittest.main()

0 comments on commit 4f91243

Please sign in to comment.