Skip to content

Commit aeceb3a

Browse files
rouaultnyalldawson
authored andcommittedApr 1, 2023
[OGR] Round-trip milliseconds and timezone in DateTime fields (fixes #48393, fixes #44160)
1 parent eeae183 commit aeceb3a

File tree

7 files changed

+199
-85
lines changed

7 files changed

+199
-85
lines changed
 

‎src/core/providers/ogr/qgsogrprovider.cpp

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,24 +1497,31 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags, QgsFeatureId
14971497
break;
14981498

14991499
case OFTTime:
1500-
OGR_F_SetFieldDateTime( feature.get(), ogrAttributeId,
1501-
0, 0, 0,
1502-
attrVal.toTime().hour(),
1503-
attrVal.toTime().minute(),
1504-
attrVal.toTime().second(),
1505-
0 );
1500+
{
1501+
const QTime time = attrVal.toTime();
1502+
OGR_F_SetFieldDateTimeEx( feature.get(), ogrAttributeId,
1503+
0, 0, 0,
1504+
time.hour(),
1505+
time.minute(),
1506+
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
1507+
0 );
15061508
break;
1507-
1509+
}
15081510
case OFTDateTime:
1509-
OGR_F_SetFieldDateTime( feature.get(), ogrAttributeId,
1510-
attrVal.toDateTime().date().year(),
1511-
attrVal.toDateTime().date().month(),
1512-
attrVal.toDateTime().date().day(),
1513-
attrVal.toDateTime().time().hour(),
1514-
attrVal.toDateTime().time().minute(),
1515-
attrVal.toDateTime().time().second(),
1516-
0 );
1511+
{
1512+
const QDateTime dt = attrVal.toDateTime();
1513+
const QDate date = dt.date();
1514+
const QTime time = dt.time();
1515+
OGR_F_SetFieldDateTimeEx( feature.get(), ogrAttributeId,
1516+
date.year(),
1517+
date.month(),
1518+
date.day(),
1519+
time.hour(),
1520+
time.minute(),
1521+
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
1522+
QgsOgrUtils::OGRTZFlagFromQt( dt ) );
15171523
break;
1524+
}
15181525

15191526
case OFTString:
15201527
{
@@ -2284,23 +2291,31 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
22842291
0 );
22852292
break;
22862293
case OFTTime:
2287-
OGR_F_SetFieldDateTime( of.get(), f,
2288-
0, 0, 0,
2289-
it2->toTime().hour(),
2290-
it2->toTime().minute(),
2291-
it2->toTime().second(),
2292-
0 );
2294+
{
2295+
const QTime time = it2->toTime();
2296+
OGR_F_SetFieldDateTimeEx( of.get(), f,
2297+
0, 0, 0,
2298+
time.hour(),
2299+
time.minute(),
2300+
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
2301+
0 );
22932302
break;
2303+
}
22942304
case OFTDateTime:
2295-
OGR_F_SetFieldDateTime( of.get(), f,
2296-
it2->toDateTime().date().year(),
2297-
it2->toDateTime().date().month(),
2298-
it2->toDateTime().date().day(),
2299-
it2->toDateTime().time().hour(),
2300-
it2->toDateTime().time().minute(),
2301-
it2->toDateTime().time().second(),
2302-
0 );
2305+
{
2306+
const QDateTime dt = it2->toDateTime();
2307+
const QDate date = dt.date();
2308+
const QTime time = dt.time();
2309+
OGR_F_SetFieldDateTimeEx( of.get(), f,
2310+
date.year(),
2311+
date.month(),
2312+
date.day(),
2313+
time.hour(),
2314+
time.minute(),
2315+
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
2316+
QgsOgrUtils::OGRTZFlagFromQt( dt ) );
23032317
break;
2318+
}
23042319
case OFTString:
23052320
{
23062321
QString stringValue;

‎src/core/qgsogrutils.cpp

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "qgsfontmanager.h"
4040
#include "qgsvariantutils.h"
4141

42+
#include <cmath>
4243
#include <QTextCodec>
4344
#include <QUuid>
4445
#include <cpl_error.h>
@@ -103,6 +104,29 @@ void gdal::GDALWarpOptionsDeleter::operator()( GDALWarpOptions *options ) const
103104
GDALDestroyWarpOptions( options );
104105
}
105106

107+
static void setQTTimeZoneFromOGRTZFlag( QDateTime &dt, int nTZFlag )
108+
{
109+
// Take into account time zone
110+
if ( nTZFlag == 0 )
111+
{
112+
// unknown time zone
113+
}
114+
else if ( nTZFlag == 1 )
115+
{
116+
dt.setTimeSpec( Qt::LocalTime );
117+
}
118+
else if ( nTZFlag == 100 )
119+
{
120+
dt.setTimeSpec( Qt::UTC );
121+
}
122+
else
123+
{
124+
// TZFlag = 101 ==> UTC+00:15
125+
// TZFlag = 99 ==> UTC-00:15
126+
dt.setOffsetFromUtc( ( nTZFlag - 100 ) * 15 * 60 );
127+
}
128+
}
129+
106130
QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType type )
107131
{
108132
if ( !value || OGR_RawField_IsUnset( value ) || OGR_RawField_IsNull( value ) )
@@ -130,15 +154,17 @@ QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType typ
130154
{
131155
float secondsPart = 0;
132156
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
133-
return QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) );
157+
return QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
134158
}
135159

136160
case OFTDateTime:
137161
{
138162
float secondsPart = 0;
139163
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
140-
return QDateTime( QDate( value->Date.Year, value->Date.Month, value->Date.Day ),
141-
QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) ) );
164+
QDateTime dt = QDateTime( QDate( value->Date.Year, value->Date.Month, value->Date.Day ),
165+
QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
166+
setQTTimeZoneFromOGRTZFlag( dt, value->Date.TZFlag );
167+
return dt;
142168
}
143169

144170
case OFTBinary:
@@ -186,6 +212,13 @@ QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType typ
186212
return QVariant();
187213
}
188214

215+
int QgsOgrUtils::OGRTZFlagFromQt( const QDateTime &datetime )
216+
{
217+
if ( datetime.timeSpec() == Qt::LocalTime )
218+
return 1;
219+
return 100 + datetime.offsetFromUtc() / ( 60 * 15 );
220+
}
221+
189222
std::unique_ptr< OGRField > QgsOgrUtils::variantToOGRField( const QVariant &value )
190223
{
191224
std::unique_ptr< OGRField > res = std::make_unique< OGRField >();
@@ -225,20 +258,22 @@ std::unique_ptr< OGRField > QgsOgrUtils::variantToOGRField( const QVariant &valu
225258
const QTime time = value.toTime();
226259
res->Date.Hour = time.hour();
227260
res->Date.Minute = time.minute();
228-
res->Date.Second = time.second() + static_cast< double >( time.msec() ) / 1000;
261+
res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
229262
res->Date.TZFlag = 0;
230263
break;
231264
}
232265
case QVariant::DateTime:
233266
{
234-
const QDateTime dateTime = value.toDateTime();
235-
res->Date.Day = dateTime.date().day();
236-
res->Date.Month = dateTime.date().month();
237-
res->Date.Year = dateTime.date().year();
238-
res->Date.Hour = dateTime.time().hour();
239-
res->Date.Minute = dateTime.time().minute();
240-
res->Date.Second = dateTime.time().second() + static_cast< double >( dateTime.time().msec() ) / 1000;
241-
res->Date.TZFlag = 0;
267+
const QDateTime dt = value.toDateTime();
268+
const QDate date = dt.date();
269+
res->Date.Day = date.day();
270+
res->Date.Month = date.month();
271+
res->Date.Year = static_cast<GInt16>( date.year() );
272+
const QTime time = dt.time();
273+
res->Date.Hour = time.hour();
274+
res->Date.Minute = time.minute();
275+
res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
276+
res->Date.TZFlag = OGRTZFlagFromQt( dt );
242277
break;
243278
}
244279

@@ -417,10 +452,14 @@ QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField
417452
if ( field.type() == QVariant::Date )
418453
value = QDate( year, month, day );
419454
else if ( field.type() == QVariant::Time )
420-
value = QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) );
455+
value = QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
421456
else
422-
value = QDateTime( QDate( year, month, day ),
423-
QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) ) );
457+
{
458+
QDateTime dt = QDateTime( QDate( year, month, day ),
459+
QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
460+
setQTTimeZoneFromOGRTZFlag( dt, tzf );
461+
value = dt;
462+
}
424463
}
425464
break;
426465

‎src/core/qgsogrutils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ class CORE_EXPORT QgsOgrUtils
181181
*/
182182
static std::unique_ptr<OGRField> variantToOGRField( const QVariant &value );
183183

184+
/**
185+
* Gets the value of OGRField::Date::TZFlag from the timezone of a QDateTime.
186+
*
187+
* \since QGIS 3.30
188+
*/
189+
static int OGRTZFlagFromQt( const QDateTime &datetime );
190+
184191
/**
185192
* Reads an OGR feature and converts it to a QgsFeature.
186193
* \param ogrFet OGR feature handle

‎src/core/qgsvectorfilewriter.cpp

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2702,14 +2702,17 @@ gdal::ogr_feature_unique_ptr QgsVectorFileWriter::createFeature( const QgsFeatur
27022702
}
27032703
else
27042704
{
2705-
OGR_F_SetFieldDateTime( poFeature.get(), ogrField,
2706-
attrValue.toDateTime().date().year(),
2707-
attrValue.toDateTime().date().month(),
2708-
attrValue.toDateTime().date().day(),
2709-
attrValue.toDateTime().time().hour(),
2710-
attrValue.toDateTime().time().minute(),
2711-
attrValue.toDateTime().time().second(),
2712-
0 );
2705+
const QDateTime dt = attrValue.toDateTime();
2706+
const QDate date = dt.date();
2707+
const QTime time = dt.time();
2708+
OGR_F_SetFieldDateTimeEx( poFeature.get(), ogrField,
2709+
date.year(),
2710+
date.month(),
2711+
date.day(),
2712+
time.hour(),
2713+
time.minute(),
2714+
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
2715+
QgsOgrUtils::OGRTZFlagFromQt( dt ) );
27132716
}
27142717
break;
27152718
case QVariant::Time:
@@ -2719,12 +2722,13 @@ gdal::ogr_feature_unique_ptr QgsVectorFileWriter::createFeature( const QgsFeatur
27192722
}
27202723
else
27212724
{
2722-
OGR_F_SetFieldDateTime( poFeature.get(), ogrField,
2723-
0, 0, 0,
2724-
attrValue.toTime().hour(),
2725-
attrValue.toTime().minute(),
2726-
attrValue.toTime().second(),
2727-
0 );
2725+
const QTime time = attrValue.toTime();
2726+
OGR_F_SetFieldDateTimeEx( poFeature.get(), ogrField,
2727+
0, 0, 0,
2728+
time.hour(),
2729+
time.minute(),
2730+
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
2731+
0 );
27282732
}
27292733
break;
27302734

0 commit comments

Comments
 (0)
Please sign in to comment.