From 9b3d20603a750fa029597508b026c12e00bf532f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 19 May 2024 01:34:10 +0200 Subject: [PATCH] OGR provider: addFeature/changeAttribueValues: convert local time to UTC for GeoPackage Fixes #57262 --- src/core/providers/ogr/qgsogrprovider.cpp | 13 ++++++++-- src/core/providers/ogr/qgsogrprovider.h | 3 +++ tests/src/python/test_provider_ogr_gpkg.py | 28 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/core/providers/ogr/qgsogrprovider.cpp b/src/core/providers/ogr/qgsogrprovider.cpp index f607029335c0..2f76ba11800b 100644 --- a/src/core/providers/ogr/qgsogrprovider.cpp +++ b/src/core/providers/ogr/qgsogrprovider.cpp @@ -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, @@ -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, @@ -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(); diff --git a/src/core/providers/ogr/qgsogrprovider.h b/src/core/providers/ogr/qgsogrprovider.h index 1491a3d68efe..9ee944165482 100644 --- a/src/core/providers/ogr/qgsogrprovider.h +++ b/src/core/providers/ogr/qgsogrprovider.h @@ -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 diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 570bc45efee4..a6f69cb4934a 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -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()