Add expression functions set_timezone and convert_timezone

These functions replace the current timezone for a datetime object
and convert a datetime object to a different timezone respectively.
This commit is contained in:
Nyall Dawson 2025-07-23 09:35:46 +10:00
parent 687f91711f
commit 3075e6b709
4 changed files with 80 additions and 1 deletions

View File

@ -0,0 +1,19 @@
{
"name": "convert_timezone",
"type": "function",
"groups": ["Conversions", "Date and Time"],
"description": "Converts a datetime object to a different timezone.",
"arguments": [{
"arg": "datetime",
"description": "datetime value"
},
{
"arg": "timezone",
"description": "target timezone"
}],
"examples": [{
"expression": "convert_timezone(\"DATE_FIELD\", timezone_from_id('Australia/Darwin'))",
"returns": "Datetime from DATE_FIELD, converted to the 'Australia/Darwin' timezone"
}],
"tags": ["time", "zone", "date", "datetime", "offset", "utc"]
}

View File

@ -0,0 +1,19 @@
{
"name": "set_timezone",
"type": "function",
"groups": ["Conversions", "Date and Time"],
"description": "Sets the timezone object associated with a datetime value, without changing the date or time components. This function can be used to replace the timezone for a datetime.",
"arguments": [{
"arg": "datetime",
"description": "datetime value"
},
{
"arg": "timezone",
"description": "new timezone for datetime"
}],
"examples": [{
"expression": "set_timezone(make_datetime(2020,1,1,10,0,0), timezone_from_id('Australia/Darwin'))",
"returns": "Datetime of 2020-01-01 10:00:00 with associated timezone 'Australia/Darwin'"
}],
"tags": ["time", "zone", "date", "datetime", "offset", "utc"]
}

View File

@ -1387,6 +1387,29 @@ static QVariant fcnGetTimeZone( const QVariantList &values, const QgsExpressionC
return QVariant();
}
static QVariant fcnSetTimeZone( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QDateTime datetime = QgsExpressionUtils::getDateTimeValue( values.at( 0 ), parent );
const QTimeZone tz = QgsExpressionUtils::getTimeZoneValue( values.at( 1 ), parent );
if ( datetime.isValid() && tz.isValid() )
{
datetime.setTimeZone( tz );
return QVariant::fromValue( datetime );
}
return QVariant();
}
static QVariant fcnConvertTimeZone( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QDateTime datetime = QgsExpressionUtils::getDateTimeValue( values.at( 0 ), parent );
const QTimeZone tz = QgsExpressionUtils::getTimeZoneValue( values.at( 1 ), parent );
if ( datetime.isValid() && tz.isValid() )
{
return QVariant::fromValue( datetime.toTimeZone( tz ) );
}
return QVariant();
}
static QVariant fcnTimeZoneToId( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QTimeZone timeZone = QgsExpressionUtils::getTimeZoneValue( values.at( 0 ), parent );
@ -1396,6 +1419,7 @@ static QVariant fcnTimeZoneToId( const QVariantList &values, const QgsExpression
}
return QVariant();
}
static QVariant fcnMakeInterval( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const double years = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
@ -8687,6 +8711,8 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "timezone_from_id" ), { QgsExpressionFunction::Parameter( QStringLiteral( "id" ) ) }, fcnTimeZoneFromId, QStringLiteral( "Date and Time" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "timezone_id" ), { QgsExpressionFunction::Parameter( QStringLiteral( "timezone" ) ) }, fcnTimeZoneToId, QStringLiteral( "Date and Time" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "get_timezone" ), { QgsExpressionFunction::Parameter( QStringLiteral( "datetime" ) ) }, fcnGetTimeZone, QStringLiteral( "Date and Time" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "set_timezone" ), { QgsExpressionFunction::Parameter( QStringLiteral( "datetime" ) ), QgsExpressionFunction::Parameter( QStringLiteral( "timezone" ) ) }, fcnSetTimeZone, QStringLiteral( "Date and Time" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "convert_timezone" ), { QgsExpressionFunction::Parameter( QStringLiteral( "datetime" ) ), QgsExpressionFunction::Parameter( QStringLiteral( "timezone" ) ) }, fcnConvertTimeZone, QStringLiteral( "Date and Time" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "lower" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ), fcnLower, QStringLiteral( "String" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "upper" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ), fcnUpper, QStringLiteral( "String" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "title" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ), fcnTitle, QStringLiteral( "String" ) )

View File

@ -4850,18 +4850,33 @@ class TestQgsExpression : public QObject
QCOMPARE( QgsExpression( "foo + bar" ).isField(), false );
}
void testGetTimeZone()
void testTimeZoneFunctions()
{
QgsExpressionContext context;
QgsExpressionContextScope *scope = new QgsExpressionContextScope();
scope->setVariable( QStringLiteral( "dt_with_timezone" ), QDateTime( QDate( 2020, 1, 1 ), QTime( 1, 2, 3 ), QTimeZone( "Australia/Brisbane" ) ) );
context.appendScope( scope );
// get_timezone
QCOMPARE( QgsExpression( QString( "timezone_id(get_timezone(@dt_with_timezone))" ) ).evaluate( &context ).toString(), QStringLiteral( "Australia/Brisbane" ) );
QCOMPARE( QgsExpression( QString( "get_timezone(NULL)" ) ).evaluate( &context ), QVariant() );
QgsExpression invalid( QString( "get_timezone(123)" ) );
QCOMPARE( invalid.evaluate( &context ), QVariant() );
QCOMPARE( invalid.evalErrorString(), QStringLiteral( "Cannot convert '123' to DateTime" ) );
// set_timezone
QCOMPARE( QgsExpression( QString( "set_timezone(@dt_with_timezone, timezone_from_id('Australia/Darwin'))" ) ).evaluate( &context ).toDateTime(), QDateTime( QDate( 2020, 1, 1 ), QTime( 1, 2, 3 ), QTimeZone( "Australia/Darwin" ) ) );
QCOMPARE( QgsExpression( QString( "set_timezone(NULL, timezone_from_id('Australia/Darwin'))" ) ).evaluate( &context ), QVariant() );
invalid = QgsExpression( QString( "set_timezone(123, timezone_from_id('Australia/Darwin'))" ) );
QCOMPARE( invalid.evaluate( &context ), QVariant() );
QCOMPARE( invalid.evalErrorString(), QStringLiteral( "Cannot convert '123' to DateTime" ) );
// convert_timezone
QCOMPARE( QgsExpression( QString( "convert_timezone(@dt_with_timezone, timezone_from_id('Australia/Darwin'))" ) ).evaluate( &context ).toDateTime(), QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 32, 3 ), QTimeZone( "Australia/Darwin" ) ) );
QCOMPARE( QgsExpression( QString( "convert_timezone(NULL, timezone_from_id('Australia/Darwin'))" ) ).evaluate( &context ), QVariant() );
invalid = QgsExpression( QString( "convert_timezone(123, timezone_from_id('Australia/Darwin'))" ) );
QCOMPARE( invalid.evaluate( &context ), QVariant() );
QCOMPARE( invalid.evalErrorString(), QStringLiteral( "Cannot convert '123' to DateTime" ) );
}
void test_expressionToLayerFieldIndex()