diff --git a/resources/function_help/json/format_date b/resources/function_help/json/format_date index c694af9fc0b..6fe082c2b5f 100644 --- a/resources/function_help/json/format_date +++ b/resources/function_help/json/format_date @@ -4,8 +4,10 @@ "description": "Formats a date type or string into a custom string format. Uses Qt date/time format strings. See QDateTime::toString.", "arguments": [ {"arg":"datetime","description":"date, time or datetime value"}, - {"arg":"format","description":"String template used to format the string.
ExpressionOutput
dthe day as number without a leading zero (1 to 31)
ddthe day as number with a leading zero (01 to 31)
dddthe abbreviated localized day name (e.g. 'Mon' to 'Sun')
ddddthe long localized day name (e.g. 'Monday' to 'Sunday')
Mthe month as number without a leading zero (1-12)
MMthe month as number with a leading zero (01-12)
MMMthe abbreviated localized month name (e.g. 'Jan' to 'Dec')
MMMMthe long localized month name (e.g. 'January' to 'December')
yythe year as two digit number (00-99)
yyyythe year as four digit number

These expressions may be used for the time part of the format string:

ExpressionOutput
hthe hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display)
hhthe hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display)
Hthe hour without a leading zero (0 to 23, even with AM/PM display)
HHthe hour with a leading zero (00 to 23, even with AM/PM display)
mthe minute without a leading zero (0 to 59)
mmthe minute with a leading zero (00 to 59)
sthe second without a leading zero (0 to 59)
ssthe second with a leading zero (00 to 59)
zthe milliseconds without leading zeroes (0 to 999)
zzzthe milliseconds with leading zeroes (000 to 999)
AP or Ainterpret as an AM/PM time. AP must be either \"AM\" or \"PM\".
ap or aInterpret as an AM/PM time. ap must be either \"am\" or \"pm\".
"} + {"arg":"format","description":"String template used to format the string.
ExpressionOutput
dthe day as number without a leading zero (1 to 31)
ddthe day as number with a leading zero (01 to 31)
dddthe abbreviated localized day name (e.g. 'Mon' to 'Sun')
ddddthe long localized day name (e.g. 'Monday' to 'Sunday')
Mthe month as number without a leading zero (1-12)
MMthe month as number with a leading zero (01-12)
MMMthe abbreviated localized month name (e.g. 'Jan' to 'Dec')
MMMMthe long localized month name (e.g. 'January' to 'December')
yythe year as two digit number (00-99)
yyyythe year as four digit number

These expressions may be used for the time part of the format string:

ExpressionOutput
hthe hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display)
hhthe hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display)
Hthe hour without a leading zero (0 to 23, even with AM/PM display)
HHthe hour with a leading zero (00 to 23, even with AM/PM display)
mthe minute without a leading zero (0 to 59)
mmthe minute with a leading zero (00 to 59)
sthe second without a leading zero (0 to 59)
ssthe second with a leading zero (00 to 59)
zthe milliseconds without leading zeroes (0 to 999)
zzzthe milliseconds with leading zeroes (000 to 999)
AP or Ainterpret as an AM/PM time. AP must be either \"AM\" or \"PM\".
ap or aInterpret as an AM/PM time. ap must be either \"am\" or \"pm\".
"}, + {"arg":"language","optional":true,"description":"language (lowercase, two- or three-letter, ISO 639 language code) used to format the date into a custom string"} ], - "examples": [ { "expression":"format_date('2012-05-15','dd.MM.yyyy')", "returns":"'15.05.2012'"} + "examples": [ { "expression":"format_date('2012-05-15','dd.MM.yyyy')", "returns":"'15.05.2012'"}, + { "expression":"format_date('2012-05-15','d MMMM yyyy','fr')", "returns":"'15 juin 2012'"} ] } diff --git a/resources/function_help/json/to_date b/resources/function_help/json/to_date index 7f0177df9ba..26b6d560009 100644 --- a/resources/function_help/json/to_date +++ b/resources/function_help/json/to_date @@ -4,10 +4,12 @@ "description": "Converts a string into a date object. An optional format string can be provided to parse the string; see QDate::fromString for additional documentation on the format.", "arguments": [ {"arg":"string","description":"string representing a date value"}, - {"arg":"format","optional":true,"description":"format used to convert the string into a date"} + {"arg":"format","optional":true,"description":"format used to convert the string into a date"}, + {"arg":"language","optional":true,"description":"language (lowercase, two- or three-letter, ISO 639 language code) used to convert the string into a date"} ], "examples": [ { "expression":"to_date('2012-05-04')", "returns":"2012-05-04"}, - { "expression":"to_date('June 29, 2019','MMMM d, yyyy')", "returns":"2019-06-29"} + { "expression":"to_date('June 29, 2019','MMMM d, yyyy')", "returns":"2019-06-29"}, + { "expression":"to_date('29 juin, 2019','d MMMM, yyyy','fr')", "returns":"2019-06-29"} ] } diff --git a/resources/function_help/json/to_datetime b/resources/function_help/json/to_datetime index 27f7573b8e6..e10ad2dd1dc 100644 --- a/resources/function_help/json/to_datetime +++ b/resources/function_help/json/to_datetime @@ -4,10 +4,12 @@ "description": "Converts a string into a datetime object. An optional format string can be provided to parse the string; see QDateTime::fromString for additional documentation on the format.", "arguments": [ {"arg":"string","description":"string representing a datetime value"}, - {"arg":"format","optional":true,"description":"format used to convert the string into a date"} + {"arg":"format","optional":true,"description":"format used to convert the string into a datetime"}, + {"arg":"language","optional":true,"description":"language (lowercase, two- or three-letter, ISO 639 language code) used to convert the string into a datetime"} ], "examples": [ { "expression":"to_datetime('2012-05-04 12:50:00')", "returns":"2012-05-04T12:50:00"}, - { "expression":"to_datetime('June 29, 2019 @ 12:34','MMMM d, yyyy @ HH:mm')", "returns":"2019-06-29T12:34"} + { "expression":"to_datetime('June 29, 2019 @ 12:34','MMMM d, yyyy @ HH:mm')", "returns":"2019-06-29T12:34"}, + { "expression":"to_datetime('29 juin, 2019 @ 12:34','d MMMM, yyyy @ HH:mm','fr')", "returns":"2019-06-29T12:34"} ] } diff --git a/resources/function_help/json/to_time b/resources/function_help/json/to_time index 3c65088ab89..87368153850 100644 --- a/resources/function_help/json/to_time +++ b/resources/function_help/json/to_time @@ -4,10 +4,12 @@ "description": "Converts a string into a time object. An optional format string can be provided to parse the string; see QTime::fromString for additional documentation on the format.", "arguments": [ {"arg":"string","description":"string representing a time value"}, - {"arg":"format","optional":true,"description":"format used to convert the string into a date"} + {"arg":"format","optional":true,"description":"format used to convert the string into a time"}, + {"arg":"language","optional":true,"description":"language (lowercase, two- or three-letter, ISO 639 language code) used to convert the string into a time"} ], "examples": [ { "expression":"to_time('12:30:01')", "returns":"12:30:01"}, - { "expression":"to_time('12:34','HH:mm')", "returns":"12:34:00"} + { "expression":"to_time('12:34','HH:mm')", "returns":"12:34:00"}, + { "expression":"to_time('12:34','HH:mm','fr')", "returns":"12:34:00"} ] } diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 64aa3f66331..033ec039927 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -1025,11 +1025,24 @@ static QVariant fcnToString( const QVariantList &values, const QgsExpressionCont static QVariant fcnToDateTime( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QString format = QgsExpressionUtils::getStringValue( values.at( 1 ), parent ); - if ( format.isEmpty() ) + QString language = QgsExpressionUtils::getStringValue( values.at( 2 ), parent ); + if ( format.isEmpty() && !language.isEmpty() ) + { + parent->setEvalErrorString( QObject::tr( "A format is required to convert to DateTime when the language is specified" ) ); + return QVariant( QDateTime() ); + } + + if ( format.isEmpty() && language.isEmpty() ) return QVariant( QgsExpressionUtils::getDateTimeValue( values.at( 0 ), parent ) ); QString datetimestring = QgsExpressionUtils::getStringValue( values.at( 0 ), parent ); - QDateTime datetime = QDateTime::fromString( datetimestring, format ); + QLocale locale = QLocale(); + if ( !language.isEmpty() ) + { + locale = QLocale( language ); + } + + QDateTime datetime = locale.toDateTime( datetimestring, format ); if ( !datetime.isValid() ) { parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to DateTime" ).arg( datetimestring ) ); @@ -1744,11 +1757,24 @@ static QVariant fcnNow( const QVariantList &, const QgsExpressionContext *, QgsE static QVariant fcnToDate( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QString format = QgsExpressionUtils::getStringValue( values.at( 1 ), parent ); - if ( format.isEmpty() ) + QString language = QgsExpressionUtils::getStringValue( values.at( 2 ), parent ); + if ( format.isEmpty() && !language.isEmpty() ) + { + parent->setEvalErrorString( QObject::tr( "A format is required to convert to Date when the language is specified" ) ); + return QVariant( QDate() ); + } + + if ( format.isEmpty() && language.isEmpty() ) return QVariant( QgsExpressionUtils::getDateValue( values.at( 0 ), parent ) ); QString datestring = QgsExpressionUtils::getStringValue( values.at( 0 ), parent ); - QDate date = QDate::fromString( datestring, format ); + QLocale locale = QLocale(); + if ( !language.isEmpty() ) + { + locale = QLocale( language ); + } + + QDate date = locale.toDate( datestring, format ); if ( !date.isValid() ) { parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Date" ).arg( datestring ) ); @@ -1760,11 +1786,24 @@ static QVariant fcnToDate( const QVariantList &values, const QgsExpressionContex static QVariant fcnToTime( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QString format = QgsExpressionUtils::getStringValue( values.at( 1 ), parent ); - if ( format.isEmpty() ) + QString language = QgsExpressionUtils::getStringValue( values.at( 2 ), parent ); + if ( format.isEmpty() && !language.isEmpty() ) + { + parent->setEvalErrorString( QObject::tr( "A format is required to convert to Time when the language is specified" ) ); + return QVariant( QTime() ); + } + + if ( format.isEmpty() && language.isEmpty() ) return QVariant( QgsExpressionUtils::getTimeValue( values.at( 0 ), parent ) ); QString timestring = QgsExpressionUtils::getStringValue( values.at( 0 ), parent ); - QTime time = QTime::fromString( timestring, format ); + QLocale locale = QLocale(); + if ( !language.isEmpty() ) + { + locale = QLocale( language ); + } + + QTime time = locale.toTime( timestring, format ); if ( !time.isValid() ) { parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Time" ).arg( timestring ) ); @@ -3885,9 +3924,16 @@ static QVariant fcnFormatNumber( const QVariantList &values, const QgsExpression static QVariant fcnFormatDate( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { - QDateTime dt = QgsExpressionUtils::getDateTimeValue( values.at( 0 ), parent ); + QDateTime datetime = QgsExpressionUtils::getDateTimeValue( values.at( 0 ), parent ); QString format = QgsExpressionUtils::getStringValue( values.at( 1 ), parent ); - return dt.toString( format ); + QString language = QgsExpressionUtils::getStringValue( values.at( 2 ), parent ); + + QLocale locale = QLocale(); + if ( !language.isEmpty() ) + { + locale = QLocale( language ); + } + return locale.toString( datetime, format ); } static QVariant fcnColorGrayscaleAverage( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) @@ -5184,9 +5230,9 @@ const QList &QgsExpression::Functions() << new QgsStaticExpressionFunction( QStringLiteral( "to_int" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnToInt, QStringLiteral( "Conversions" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "toint" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "to_real" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnToReal, QStringLiteral( "Conversions" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "toreal" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "to_string" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnToString, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "String" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "tostring" ) ) - << new QgsStaticExpressionFunction( QStringLiteral( "to_datetime" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ), true, QVariant() ), fcnToDateTime, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "todatetime" ) ) - << new QgsStaticExpressionFunction( QStringLiteral( "to_date" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ), true, QVariant() ), fcnToDate, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "todate" ) ) - << new QgsStaticExpressionFunction( QStringLiteral( "to_time" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ), true, QVariant() ), fcnToTime, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "totime" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "to_datetime" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ), true, QVariant() ) << QgsExpressionFunction::Parameter( QStringLiteral( "language" ), true, QVariant() ), fcnToDateTime, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "todatetime" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "to_date" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ), true, QVariant() ) << QgsExpressionFunction::Parameter( QStringLiteral( "language" ), true, QVariant() ), fcnToDate, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "todate" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "to_time" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ), true, QVariant() ) << QgsExpressionFunction::Parameter( QStringLiteral( "language" ), true, QVariant() ), fcnToTime, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "totime" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "to_interval" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnToInterval, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "tointerval" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "to_dm" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "axis" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "precision" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "formatting" ), true ), fcnToDegreeMinute, QStringLiteral( "Conversions" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "todm" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "to_dms" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "axis" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "precision" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "formatting" ), true ), fcnToDegreeMinuteSecond, QStringLiteral( "Conversions" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "todms" ) ) @@ -5335,7 +5381,7 @@ const QList &QgsExpression::Functions() << new QgsStaticExpressionFunction( QStringLiteral( "lpad" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "width" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "fill" ) ), fcnLPad, QStringLiteral( "String" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "format" ), -1, fcnFormatString, QStringLiteral( "String" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "format_number" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "number" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "places" ) ), fcnFormatNumber, QStringLiteral( "String" ) ) - << new QgsStaticExpressionFunction( QStringLiteral( "format_date" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "datetime" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ) ), fcnFormatDate, QStringList() << QStringLiteral( "String" ) << QStringLiteral( "Date and Time" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "format_date" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "datetime" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "format" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "language" ), true, QVariant() ), fcnFormatDate, QStringList() << QStringLiteral( "String" ) << QStringLiteral( "Date and Time" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "color_grayscale_average" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "color" ) ), fcnColorGrayscaleAverage, QStringLiteral( "Color" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "color_mix_rgb" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "color1" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "color2" ) ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 2b3c1258189..b30531bda3e 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1294,10 +1294,16 @@ class TestQgsExpression: public QObject QTest::newRow( "epoch" ) << "epoch(to_datetime('2017-01-01T00:00:01+00:00'))" << false << QVariant( 1483228801000LL ); QTest::newRow( "epoch invalid date" ) << "epoch('invalid')" << true << QVariant(); QTest::newRow( "date from format" ) << "to_date('June 29, 2019','MMMM d, yyyy')" << false << QVariant( QDate( 2019, 6, 29 ) ); + QTest::newRow( "date from format and language" ) << "to_date('29 juin, 2019','d MMMM, yyyy','fr')" << false << QVariant( QDate( 2019, 6, 29 ) ); QTest::newRow( "date from format, wrong string" ) << "to_date('wrong.string.here','yyyy.MM.dd')" << true << QVariant(); QTest::newRow( "date from format, wrong format" ) << "to_date('2019-01-01','wrong')" << true << QVariant(); + QTest::newRow( "date from format, language missing format" ) << "to_date('2019-01-01',language:='fr')" << true << QVariant(); QTest::newRow( "datetime from format" ) << "to_datetime('June 29, 2019 @ 11:00','MMMM d, yyyy @ HH:mm')" << false << QVariant( QDateTime( QDate( 2019, 6, 29 ), QTime( 11, 0 ) ) ); + QTest::newRow( "datetime from format and language" ) << "to_datetime('29 juin, 2019 @ 11:00','d MMMM, yyyy @ HH:mm','fr')" << false << QVariant( QDateTime( QDate( 2019, 6, 29 ), QTime( 11, 0 ) ) ); QTest::newRow( "time from format" ) << "to_time('12:34:56','HH:mm:ss')" << false << QVariant( QTime( 12, 34, 56 ) ); + QTest::newRow( "time from format and language" ) << "to_time('12:34:56','HH:mm:ss','fr')" << false << QVariant( QTime( 12, 34, 56 ) ); + QTest::newRow( "formatted string from date" ) << "format_date('2019-06-29','MMMM d, yyyy')" << false << QVariant( QString( "June 29, 2019" ) ); + QTest::newRow( "formatted string from date with language" ) << "format_date('2019-06-29','d MMMM yyyy','fr')" << false << QVariant( QString( "29 juin 2019" ) ); // Color functions QTest::newRow( "ramp color" ) << "ramp_color('Spectral',0.3)" << false << QVariant( "254,190,116,255" );