Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assorted set of fixes related to DateTime Z formatter #57483

Merged
merged 4 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/core/expression/qgsexpressionfunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5807,10 +5807,14 @@ static QVariant fcnFormatNumber( const QVariantList &values, const QgsExpression

static QVariant fcnFormatDate( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QDateTime datetime = QgsExpressionUtils::getDateTimeValue( values.at( 0 ), parent );
QDateTime datetime = QgsExpressionUtils::getDateTimeValue( values.at( 0 ), parent );
const QString format = QgsExpressionUtils::getStringValue( values.at( 1 ), parent );
const QString language = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );

// Convert to UTC if the format string includes a Z, as QLocale::toString() doesn't do it
if ( format.indexOf( "Z" ) > 0 )
datetime = datetime.toUTC();

QLocale locale = !language.isEmpty() ? QLocale( language ) : QLocale();
return locale.toString( datetime, format );
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/fieldformatter/qgsdatetimefieldformatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ QString QgsDateTimeFieldFormatter::representValue( QgsVectorLayer *layer, int fi
}
else
{
// Convert to UTC if the format string includes a Z, as QLocale::toString() doesn't do it
if ( displayFormat.indexOf( "Z" ) > 0 )
date = date.toUTC();

result = date.toString( displayFormat );
}
}
Expand Down
13 changes: 11 additions & 2 deletions src/core/providers/ogr/qgsogrprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1771,10 +1771,12 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags, QgsFeatureId
}
case OFTDateTime:
{
const QDateTime dt = attrVal.toDateTime();
QDateTime dt = attrVal.toDateTime();
if ( dt.isValid() )
{
ok = true;
if ( mConvertLocalTimeToUTC && dt.timeSpec() == Qt::LocalTime )
dt = dt.toUTC();
const QDate date = dt.date();
const QTime time = dt.time();
OGR_F_SetFieldDateTimeEx( feature.get(), ogrAttributeId,
Expand Down Expand Up @@ -2755,10 +2757,12 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
}
case OFTDateTime:
{
const QDateTime dt = it2->toDateTime();
QDateTime dt = it2->toDateTime();
if ( dt.isValid() )
{
ok = true;
if ( mConvertLocalTimeToUTC && dt.timeSpec() == Qt::LocalTime )
dt = dt.toUTC();
const QDate date = dt.date();
const QTime time = dt.time();
OGR_F_SetFieldDateTimeEx( of.get(), f,
Expand Down Expand Up @@ -4203,6 +4207,11 @@ void QgsOgrProvider::open( OpenMode mode )
mGDALDriverName = mOgrOrigLayer->driverName();
mShareSameDatasetAmongLayers = QgsOgrProviderUtils::canDriverShareSameDatasetAmongLayers( mGDALDriverName );

// Should we set it to true unconditionally? as OGR doesn't do any time
// zone conversion for local time. For now, only do that for GeoPackage
// since it requires UTC.
mConvertLocalTimeToUTC = ( mGDALDriverName == QLatin1String( "GPKG" ) );

QgsDebugMsgLevel( "OGR opened using Driver " + mGDALDriverName, 2 );

mOgrLayer = mOgrOrigLayer.get();
Expand Down
3 changes: 3 additions & 0 deletions src/core/providers/ogr/qgsogrprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ class QgsOgrProvider final: public QgsVectorDataProvider
void invalidateNetworkCache();

bool mShapefileHadSpatialIndex = false;

//! Whether to convert Qt DateTime with local time to UTC
bool mConvertLocalTimeToUTC = false;
};

///@endcond
Expand Down
3 changes: 3 additions & 0 deletions src/gui/editorwidgets/qgsdatetimeedit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ void QgsDateTimeEdit::setDateTime( const QDateTime &dateTime )
{
// changed emits a signal, so don't allow it to be emitted from setDateTime
mBlockChangedSignal++;
// We need to set the time spec of the set datetime to the widget, otherwise
// the dateTime() getter would loose edit, and return local time.
QDateTimeEdit::setTimeSpec( dateTime.timeSpec() );
QDateTimeEdit::setDateTime( dateTime );
mBlockChangedSignal--;
changed( dateTime );
Expand Down
12 changes: 12 additions & 0 deletions src/gui/editorwidgets/qgsdatetimeeditconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,18 @@ QgsDateTimeEditConfig::QgsDateTimeEditConfig( QgsVectorLayer *vl, int fieldIdx,

void QgsDateTimeEditConfig::updateDemoWidget()
{
// Use a UTC datetime if the format string includes a Z
if ( mDisplayFormatEdit->text().indexOf( "Z" ) > 0 )
{
mDemoDateTimeEdit->setTimeSpec( Qt::UTC );
mDemoDateTimeEdit->setDateTime( QDateTime::currentDateTimeUtc() );
}
else
{
mDemoDateTimeEdit->setTimeSpec( Qt::LocalTime );
mDemoDateTimeEdit->setDateTime( QDateTime::currentDateTime() );
}

mDemoDateTimeEdit->setDisplayFormat( mDisplayFormatEdit->text() );
mDemoDateTimeEdit->setCalendarPopup( mCalendarPopupCheckBox->isChecked() );
}
Expand Down
6 changes: 6 additions & 0 deletions src/gui/editorwidgets/qgsdatetimeeditwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ void QgsDateTimeEditWrapper::updateValues( const QVariant &value, const QVariant

if ( mQgsDateTimeEdit )
{
// Convert to UTC if the format string includes a Z
if ( mQgsDateTimeEdit->displayFormat().indexOf( "Z" ) > 0 )
{
dateTime = dateTime.toUTC();
}

mQgsDateTimeEdit->setDateTime( dateTime );
}
else
Expand Down
1 change: 1 addition & 0 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,7 @@ class TestQgsExpression: public QObject
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" ) );
QTest::newRow( "formatted string with Z" ) << "format_date(to_datetime('2019-06-29T13:34:56+01:00'),'yyyy-MM-ddTHH:mm:ssZ')" << false << QVariant( QString( "2019-06-29T12:34:56Z" ) );

// Color functions
QTest::newRow( "ramp color" ) << "ramp_color('Spectral',0.3)" << false << QVariant( "253,190,116,255" );
Expand Down
28 changes: 28 additions & 0 deletions tests/src/python/test_provider_ogr_gpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2715,6 +2715,34 @@ def testDateTimeTimeZoneMilliseconds(self):
got = [feat for feat in vl.getFeatures()]
self.assertEqual(got[0]["dt"], new_dt)

def testWriteDateTimeFromLocalTime(self):

tmpfile = os.path.join(self.basetestpath, 'testWriteDateTimeFromLocalTime.gpkg')
ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
lyr = ds.CreateLayer('test', geom_type=ogr.wkbNone)
lyr.CreateField(ogr.FieldDefn('dt', ogr.OFTDateTime))
ds = None

vl = QgsVectorLayer(tmpfile, 'test', 'ogr')

f = QgsFeature(vl.fields())
dt = QDateTime(QDate(2023, 1, 28), QTime(12, 34, 56, 789), Qt.TimeSpec.LocalTime)
f.setAttribute(1, dt)
self.assertTrue(vl.startEditing())
self.assertTrue(vl.addFeatures([f]))
self.assertTrue(vl.commitChanges())

got = [feat for feat in vl.getFeatures()]
self.assertEqual(got[0]["dt"], dt.toUTC())

self.assertTrue(vl.startEditing())
dt = QDateTime(QDate(2024, 1, 1), QTime(12, 34, 56, 789), Qt.TimeSpec.LocalTime)
self.assertTrue(vl.changeAttributeValue(1, 1, dt))
self.assertTrue(vl.commitChanges())

got = [feat for feat in vl.getFeatures()]
self.assertEqual(got[0]["dt"], dt.toUTC())

def testTransactionModeAutoWithFilter(self):

temp_dir = QTemporaryDir()
Expand Down
12 changes: 12 additions & 0 deletions tests/src/python/test_qgsdatetimeedit.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
start_app()

DATE = QDateTime.fromString('2018-01-01 01:02:03', Qt.DateFormat.ISODate)
DATE_Z = QDateTime.fromString('2018-01-01 01:02:03Z', Qt.DateFormat.ISODate)


class TestQgsDateTimeEdit(QgisTestCase):
Expand All @@ -32,6 +33,17 @@ def testSettersGetters(self):
w.setDateTime(QDateTime())
self.assertEqual(w.dateTime(), DATE)

def testSettersGetters_DATE_Z(self):
""" test widget handling with Z time spec """
w = QgsDateTimeEdit()
w.setAllowNull(False)

w.setDateTime(DATE_Z)
self.assertEqual(w.dateTime(), DATE_Z)
# date should remain when setting an invalid date
w.setDateTime(QDateTime())
self.assertEqual(w.dateTime(), DATE_Z)

def testNullValueHandling(self):
""" test widget handling of null values """
w = QgsDateTimeEdit()
Expand Down
5 changes: 5 additions & 0 deletions tests/src/python/test_qgsfieldformatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,11 @@ def test_representValue(self):
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.TimeSpec.OffsetFromUTC, 3600)),
'04/03/2020 12:13:14')

config = {"display_format": "dd/MM/yyyy HH:mm:ssZ"}
self.assertEqual(field_formatter.representValue(layer, 0, config, None,
QDateTime(QDate(2020, 3, 4), QTime(12, 13, 14), Qt.TimeSpec.OffsetFromUTC, 3600)),
'04/03/2020 11:13:14Z')

locale_assertions = {
QLocale(QLocale.Language.English): {
"date_format": 'M/d/yy',
Expand Down
Loading