Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[OGR] Round-trip milliseconds and timezone in DateTime fields (fixes #…
  • Loading branch information
rouault authored and nyalldawson committed Apr 1, 2023
1 parent eeae183 commit aeceb3a
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 85 deletions.
73 changes: 44 additions & 29 deletions src/core/providers/ogr/qgsogrprovider.cpp
Expand Up @@ -1497,24 +1497,31 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags, QgsFeatureId
break;

case OFTTime:
OGR_F_SetFieldDateTime( feature.get(), ogrAttributeId,
0, 0, 0,
attrVal.toTime().hour(),
attrVal.toTime().minute(),
attrVal.toTime().second(),
0 );
{
const QTime time = attrVal.toTime();
OGR_F_SetFieldDateTimeEx( feature.get(), ogrAttributeId,
0, 0, 0,
time.hour(),
time.minute(),
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
0 );
break;

}
case OFTDateTime:
OGR_F_SetFieldDateTime( feature.get(), ogrAttributeId,
attrVal.toDateTime().date().year(),
attrVal.toDateTime().date().month(),
attrVal.toDateTime().date().day(),
attrVal.toDateTime().time().hour(),
attrVal.toDateTime().time().minute(),
attrVal.toDateTime().time().second(),
0 );
{
const QDateTime dt = attrVal.toDateTime();
const QDate date = dt.date();
const QTime time = dt.time();
OGR_F_SetFieldDateTimeEx( feature.get(), ogrAttributeId,
date.year(),
date.month(),
date.day(),
time.hour(),
time.minute(),
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
QgsOgrUtils::OGRTZFlagFromQt( dt ) );
break;
}

case OFTString:
{
Expand Down Expand Up @@ -2284,23 +2291,31 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
0 );
break;
case OFTTime:
OGR_F_SetFieldDateTime( of.get(), f,
0, 0, 0,
it2->toTime().hour(),
it2->toTime().minute(),
it2->toTime().second(),
0 );
{
const QTime time = it2->toTime();
OGR_F_SetFieldDateTimeEx( of.get(), f,
0, 0, 0,
time.hour(),
time.minute(),
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
0 );
break;
}
case OFTDateTime:
OGR_F_SetFieldDateTime( of.get(), f,
it2->toDateTime().date().year(),
it2->toDateTime().date().month(),
it2->toDateTime().date().day(),
it2->toDateTime().time().hour(),
it2->toDateTime().time().minute(),
it2->toDateTime().time().second(),
0 );
{
const QDateTime dt = it2->toDateTime();
const QDate date = dt.date();
const QTime time = dt.time();
OGR_F_SetFieldDateTimeEx( of.get(), f,
date.year(),
date.month(),
date.day(),
time.hour(),
time.minute(),
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
QgsOgrUtils::OGRTZFlagFromQt( dt ) );
break;
}
case OFTString:
{
QString stringValue;
Expand Down
69 changes: 54 additions & 15 deletions src/core/qgsogrutils.cpp
Expand Up @@ -39,6 +39,7 @@
#include "qgsfontmanager.h"
#include "qgsvariantutils.h"

#include <cmath>
#include <QTextCodec>
#include <QUuid>
#include <cpl_error.h>
Expand Down Expand Up @@ -103,6 +104,29 @@ void gdal::GDALWarpOptionsDeleter::operator()( GDALWarpOptions *options ) const
GDALDestroyWarpOptions( options );
}

static void setQTTimeZoneFromOGRTZFlag( QDateTime &dt, int nTZFlag )
{
// Take into account time zone
if ( nTZFlag == 0 )
{
// unknown time zone
}
else if ( nTZFlag == 1 )
{
dt.setTimeSpec( Qt::LocalTime );
}
else if ( nTZFlag == 100 )
{
dt.setTimeSpec( Qt::UTC );
}
else
{
// TZFlag = 101 ==> UTC+00:15
// TZFlag = 99 ==> UTC-00:15
dt.setOffsetFromUtc( ( nTZFlag - 100 ) * 15 * 60 );
}
}

QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType type )
{
if ( !value || OGR_RawField_IsUnset( value ) || OGR_RawField_IsNull( value ) )
Expand Down Expand Up @@ -130,15 +154,17 @@ QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType typ
{
float secondsPart = 0;
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
return QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) );
return QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
}

case OFTDateTime:
{
float secondsPart = 0;
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
return QDateTime( QDate( value->Date.Year, value->Date.Month, value->Date.Day ),
QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) ) );
QDateTime dt = QDateTime( QDate( value->Date.Year, value->Date.Month, value->Date.Day ),
QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
setQTTimeZoneFromOGRTZFlag( dt, value->Date.TZFlag );
return dt;
}

case OFTBinary:
Expand Down Expand Up @@ -186,6 +212,13 @@ QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType typ
return QVariant();
}

int QgsOgrUtils::OGRTZFlagFromQt( const QDateTime &datetime )
{
if ( datetime.timeSpec() == Qt::LocalTime )
return 1;
return 100 + datetime.offsetFromUtc() / ( 60 * 15 );
}

std::unique_ptr< OGRField > QgsOgrUtils::variantToOGRField( const QVariant &value )
{
std::unique_ptr< OGRField > res = std::make_unique< OGRField >();
Expand Down Expand Up @@ -225,20 +258,22 @@ std::unique_ptr< OGRField > QgsOgrUtils::variantToOGRField( const QVariant &valu
const QTime time = value.toTime();
res->Date.Hour = time.hour();
res->Date.Minute = time.minute();
res->Date.Second = time.second() + static_cast< double >( time.msec() ) / 1000;
res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
res->Date.TZFlag = 0;
break;
}
case QVariant::DateTime:
{
const QDateTime dateTime = value.toDateTime();
res->Date.Day = dateTime.date().day();
res->Date.Month = dateTime.date().month();
res->Date.Year = dateTime.date().year();
res->Date.Hour = dateTime.time().hour();
res->Date.Minute = dateTime.time().minute();
res->Date.Second = dateTime.time().second() + static_cast< double >( dateTime.time().msec() ) / 1000;
res->Date.TZFlag = 0;
const QDateTime dt = value.toDateTime();
const QDate date = dt.date();
res->Date.Day = date.day();
res->Date.Month = date.month();
res->Date.Year = static_cast<GInt16>( date.year() );
const QTime time = dt.time();
res->Date.Hour = time.hour();
res->Date.Minute = time.minute();
res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
res->Date.TZFlag = OGRTZFlagFromQt( dt );
break;
}

Expand Down Expand Up @@ -417,10 +452,14 @@ QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField
if ( field.type() == QVariant::Date )
value = QDate( year, month, day );
else if ( field.type() == QVariant::Time )
value = QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) );
value = QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
else
value = QDateTime( QDate( year, month, day ),
QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) ) );
{
QDateTime dt = QDateTime( QDate( year, month, day ),
QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
setQTTimeZoneFromOGRTZFlag( dt, tzf );
value = dt;
}
}
break;

Expand Down
7 changes: 7 additions & 0 deletions src/core/qgsogrutils.h
Expand Up @@ -181,6 +181,13 @@ class CORE_EXPORT QgsOgrUtils
*/
static std::unique_ptr<OGRField> variantToOGRField( const QVariant &value );

/**
* Gets the value of OGRField::Date::TZFlag from the timezone of a QDateTime.
*
* \since QGIS 3.30
*/
static int OGRTZFlagFromQt( const QDateTime &datetime );

/**
* Reads an OGR feature and converts it to a QgsFeature.
* \param ogrFet OGR feature handle
Expand Down
32 changes: 18 additions & 14 deletions src/core/qgsvectorfilewriter.cpp
Expand Up @@ -2702,14 +2702,17 @@ gdal::ogr_feature_unique_ptr QgsVectorFileWriter::createFeature( const QgsFeatur
}
else
{
OGR_F_SetFieldDateTime( poFeature.get(), ogrField,
attrValue.toDateTime().date().year(),
attrValue.toDateTime().date().month(),
attrValue.toDateTime().date().day(),
attrValue.toDateTime().time().hour(),
attrValue.toDateTime().time().minute(),
attrValue.toDateTime().time().second(),
0 );
const QDateTime dt = attrValue.toDateTime();
const QDate date = dt.date();
const QTime time = dt.time();
OGR_F_SetFieldDateTimeEx( poFeature.get(), ogrField,
date.year(),
date.month(),
date.day(),
time.hour(),
time.minute(),
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
QgsOgrUtils::OGRTZFlagFromQt( dt ) );
}
break;
case QVariant::Time:
Expand All @@ -2719,12 +2722,13 @@ gdal::ogr_feature_unique_ptr QgsVectorFileWriter::createFeature( const QgsFeatur
}
else
{
OGR_F_SetFieldDateTime( poFeature.get(), ogrField,
0, 0, 0,
attrValue.toTime().hour(),
attrValue.toTime().minute(),
attrValue.toTime().second(),
0 );
const QTime time = attrValue.toTime();
OGR_F_SetFieldDateTimeEx( poFeature.get(), ogrField,
0, 0, 0,
time.hour(),
time.minute(),
static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 ),
0 );
}
break;

Expand Down

0 comments on commit aeceb3a

Please sign in to comment.