Skip to content

Commit

Permalink
Improve handling of time values in attributes
Browse files Browse the repository at this point in the history
- make QgsVectorFileWriter respect QTime field values and only
convert to string if required by output format
- list time types as native types for memory, postgres and OGR
providers (OGR support depends on file format)
  • Loading branch information
nyalldawson committed Jan 28, 2016
1 parent 082f113 commit 7dc5eac
Show file tree
Hide file tree
Showing 18 changed files with 312 additions and 19 deletions.
8 changes: 8 additions & 0 deletions src/core/qgsexpressionsorter.h
Expand Up @@ -79,6 +79,14 @@ class QgsExpressionSorter
else
return v1.toDate() > v2.toDate();

case QVariant::Time:
if ( v1.toTime() == v2.toTime() )
continue;
if ( orderBy.ascending() )
return v1.toTime() < v2.toTime();
else
return v1.toTime() > v2.toTime();

case QVariant::DateTime:
if ( v1.toDateTime() == v2.toDateTime() )
continue;
Expand Down
48 changes: 33 additions & 15 deletions src/core/qgsvectorfilewriter.cpp
Expand Up @@ -103,22 +103,21 @@ void QgsVectorFileWriter::init( QString vectorFileName, QString fileEncoding, co
return;
}

QString ogrDriverName;
if ( driverName == "MapInfo MIF" )
{
ogrDriverName = "MapInfo File";
mOgrDriverName = "MapInfo File";
}
else if ( driverName == "SpatiaLite" )
{
ogrDriverName = "SQLite";
mOgrDriverName = "SQLite";
if ( !datasourceOptions.contains( "SPATIALITE=YES" ) )
{
datasourceOptions.append( "SPATIALITE=YES" );
}
}
else if ( driverName == "DBF file" )
{
ogrDriverName = "ESRI Shapefile";
mOgrDriverName = "ESRI Shapefile";
if ( !layerOptions.contains( "SHPT=NULL" ) )
{
layerOptions.append( "SHPT=NULL" );
Expand All @@ -127,14 +126,14 @@ void QgsVectorFileWriter::init( QString vectorFileName, QString fileEncoding, co
}
else
{
ogrDriverName = driverName;
mOgrDriverName = driverName;
}

// find driver in OGR
OGRSFDriverH poDriver;
QgsApplication::registerOgrDrivers();

poDriver = OGRGetDriverByName( ogrDriverName.toLocal8Bit().data() );
poDriver = OGRGetDriverByName( mOgrDriverName.toLocal8Bit().data() );

if ( !poDriver )
{
Expand All @@ -145,7 +144,7 @@ void QgsVectorFileWriter::init( QString vectorFileName, QString fileEncoding, co
return;
}

if ( ogrDriverName == "ESRI Shapefile" )
if ( mOgrDriverName == "ESRI Shapefile" )
{
if ( layerOptions.join( "" ).toUpper().indexOf( "ENCODING=" ) == -1 )
{
Expand Down Expand Up @@ -315,7 +314,7 @@ void QgsVectorFileWriter::init( QString vectorFileName, QString fileEncoding, co

if ( srs )
{
if ( ogrDriverName == "ESRI Shapefile" )
if ( mOgrDriverName == "ESRI Shapefile" )
{
QString layerName = vectorFileName.left( vectorFileName.indexOf( ".shp", Qt::CaseInsensitive ) );
QFile prjFile( layerName + ".qpj" );
Expand Down Expand Up @@ -389,6 +388,18 @@ void QgsVectorFileWriter::init( QString vectorFileName, QString fileEncoding, co
ogrType = OFTDate;
break;

case QVariant::Time:
if ( mOgrDriverName == "ESRI Shapefile" )
{
ogrType = OFTString;
ogrWidth = 12;
}
else
{
ogrType = OFTTime;
}
break;

case QVariant::DateTime:
ogrType = OFTDateTime;
break;
Expand All @@ -403,7 +414,7 @@ void QgsVectorFileWriter::init( QString vectorFileName, QString fileEncoding, co

QString name( attrField.name() );

if ( ogrDriverName == "SQLite" && name.compare( "ogc_fid", Qt::CaseInsensitive ) == 0 )
if ( mOgrDriverName == "SQLite" && name.compare( "ogc_fid", Qt::CaseInsensitive ) == 0 )
{
int i;
for ( i = 0; i < 10; i++ )
Expand Down Expand Up @@ -1778,12 +1789,19 @@ OGRFeatureH QgsVectorFileWriter::createFeature( QgsFeature& feature )
0 );
break;
case QVariant::Time:
OGR_F_SetFieldDateTime( poFeature, ogrField,
0, 0, 0,
attrValue.toDateTime().time().hour(),
attrValue.toDateTime().time().minute(),
attrValue.toDateTime().time().second(),
0 );
if ( mOgrDriverName == "ESRI Shapefile" )
{
OGR_F_SetFieldString( poFeature, ogrField, mCodec->fromUnicode( attrValue.toString() ).data() );
}
else
{
OGR_F_SetFieldDateTime( poFeature, ogrField,
0, 0, 0,
attrValue.toTime().hour(),
attrValue.toTime().minute(),
attrValue.toTime().second(),
0 );
}
break;
case QVariant::Invalid:
break;
Expand Down
2 changes: 2 additions & 0 deletions src/core/qgsvectorfilewriter.h
Expand Up @@ -352,6 +352,8 @@ class CORE_EXPORT QgsVectorFileWriter
/** Scale for symbology export (e.g. for symbols units in map units)*/
double mSymbologyScaleDenominator;

QString mOgrDriverName;

private:
void init( QString vectorFileName, QString fileEncoding, const QgsFields& fields, QgsWKBTypes::Type geometryType, const QgsCoordinateReferenceSystem* srs, const QString& driverName, QStringList datasourceOptions, QStringList layerOptions, QString* newFilename );

Expand Down
3 changes: 3 additions & 0 deletions src/gui/attributetable/qgsattributetablefiltermodel.cpp
Expand Up @@ -80,6 +80,9 @@ bool QgsAttributeTableFilterModel::lessThan( const QModelIndex &left, const QMod
case QVariant::Date:
return leftData.toDate() < rightData.toDate();

case QVariant::Time:
return leftData.toTime() < rightData.toTime();

case QVariant::DateTime:
return leftData.toDateTime() < rightData.toDateTime();

Expand Down
10 changes: 9 additions & 1 deletion src/providers/memory/qgsmemoryprovider.cpp
Expand Up @@ -86,6 +86,7 @@ QgsMemoryProvider::QgsMemoryProvider( const QString& uri )

// date type
<< QgsVectorDataProvider::NativeType( tr( "Date" ), "date", QVariant::Date, -1, -1, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Time" ), "time", QVariant::Time, -1, -1, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Date & Time" ), "datetime", QVariant::DateTime, -1, -1, -1, -1 )

// integer types
Expand All @@ -107,7 +108,7 @@ QgsMemoryProvider::QgsMemoryProvider( const QString& uri )
{
QList<QgsField> attributes;
QRegExp reFieldDef( "\\:"
"(int|integer|real|double|string|date|datetime)" // type
"(int|integer|real|double|string|date|time|datetime)" // type
"(?:\\((\\d+)" // length
"(?:\\,(\\d+))?" // precision
"\\))?"
Expand Down Expand Up @@ -145,6 +146,12 @@ QgsMemoryProvider::QgsMemoryProvider( const QString& uri )
typeName = "date";
length = -1;
}
else if ( typeName == "time" )
{
type = QVariant::Time;
typeName = "time";
length = -1;
}
else if ( typeName == "datetime" )
{
type = QVariant::DateTime;
Expand Down Expand Up @@ -357,6 +364,7 @@ bool QgsMemoryProvider::addAttributes( const QList<QgsField> &attributes )
case QVariant::Double:
case QVariant::String:
case QVariant::Date:
case QVariant::Time:
case QVariant::DateTime:
case QVariant::LongLong:
break;
Expand Down
3 changes: 3 additions & 0 deletions src/providers/ogr/qgsogrfeatureiterator.cpp
Expand Up @@ -295,12 +295,15 @@ void QgsOgrFeatureIterator::getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature
break;
case QVariant::Date:
case QVariant::DateTime:
case QVariant::Time:
{
int year, month, day, hour, minute, second, tzf;

OGR_F_GetFieldAsDateTime( ogrFet, attindex, &year, &month, &day, &hour, &minute, &second, &tzf );
if ( mSource->mFields.at( attindex ).type() == QVariant::Date )
value = QDate( year, month, day );
else if ( mSource->mFields.at( attindex ).type() == QVariant::Time )
value = QTime( hour, minute, second );
else
value = QDateTime( QDate( year, month, day ), QTime( hour, minute, second ) );
}
Expand Down
33 changes: 33 additions & 0 deletions src/providers/ogr/qgsogrprovider.cpp
Expand Up @@ -119,6 +119,10 @@ bool QgsOgrProvider::convertField( QgsField &field, const QTextCodec &encoding )
ogrType = OFTDate;
break;
case QVariant::Time:
ogrType = OFTTime;
break;
case QVariant::DateTime:
ogrType = OFTDateTime;
break;
Expand Down Expand Up @@ -366,6 +370,7 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri )
if ( ogrDriverName != "ESRI Shapefile" )
{
mNativeTypes
<< QgsVectorDataProvider::NativeType( tr( "Time" ), "time", QVariant::Time, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Date & Time" ), "datetime", QVariant::DateTime );
}

Expand Down Expand Up @@ -721,6 +726,9 @@ void QgsOgrProvider::loadFields()
case OFTDate:
varType = QVariant::Date;
break;
case OFTTime:
varType = QVariant::Time;
break;
case OFTDateTime:
varType = QVariant::DateTime;
break;
Expand Down Expand Up @@ -1006,6 +1014,16 @@ bool QgsOgrProvider::addFeature( QgsFeature& f )
0, 0, 0,
0 );
break;

case OFTTime:
OGR_F_SetFieldDateTime( feature, targetAttributeId,
0, 0, 0,
attrVal.toTime().hour(),
attrVal.toTime().minute(),
attrVal.toTime().second(),
0 );
break;

case OFTDateTime:
OGR_F_SetFieldDateTime( feature, targetAttributeId,
attrVal.toDateTime().date().year(),
Expand Down Expand Up @@ -1099,6 +1117,9 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
case QVariant::Date:
type = OFTDate;
break;
case QVariant::Time:
type = OFTTime;
break;
case QVariant::DateTime:
type = OFTDateTime;
break;
Expand Down Expand Up @@ -1227,6 +1248,14 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
0, 0, 0,
0 );
break;
case OFTTime:
OGR_F_SetFieldDateTime( of, f,
0, 0, 0,
it2->toTime().hour(),
it2->toTime().minute(),
it2->toTime().second(),
0 );
break;
case OFTDateTime:
OGR_F_SetFieldDateTime( of, f,
it2->toDateTime().date().year(),
Expand Down Expand Up @@ -2285,6 +2314,10 @@ QGISEXTERN bool createEmptyDataSource( const QString &uri,
{
field = OGR_Fld_Create( codec->fromUnicode( it->first ).constData(), OFTDate );
}
else if ( fields[0] == "Time" )
{
field = OGR_Fld_Create( codec->fromUnicode( it->first ).constData(), OFTTime );
}
else if ( fields[0] == "DateTime" )
{
field = OGR_Fld_Create( codec->fromUnicode( it->first ).constData(), OFTDateTime );
Expand Down
6 changes: 6 additions & 0 deletions src/providers/postgres/qgspostgresprovider.cpp
Expand Up @@ -179,6 +179,7 @@ QgsPostgresProvider::QgsPostgresProvider( QString const & uri )

// date type
<< QgsVectorDataProvider::NativeType( tr( "Date" ), "date", QVariant::Date, -1, -1, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Time" ), "time", QVariant::Time, -1, -1, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Date & Time" ), "timestamp without time zone", QVariant::DateTime, -1, -1, -1, -1 )
;

Expand Down Expand Up @@ -875,6 +876,11 @@ bool QgsPostgresProvider::loadFields()
fieldType = QVariant::Date;
fieldSize = -1;
}
else if ( fieldTypeName == "time" )
{
fieldType = QVariant::Time;
fieldSize = -1;
}
else if ( fieldTypeName == "timestamp" )
{
fieldType = QVariant::DateTime;
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -55,6 +55,7 @@ ADD_PYTHON_TEST(PyQgsRelation test_qgsrelation.py)
ADD_PYTHON_TEST(PyQgsRulebasedRenderer test_qgsrulebasedrenderer.py)
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
ADD_PYTHON_TEST(PyQgsTabfileProvider test_provider_tabfile.py)
ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py)
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py)
ADD_PYTHON_TEST(PyQgsSymbolLayerV2 test_qgssymbollayerv2.py)
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/test_provider_memory.py
Expand Up @@ -193,6 +193,7 @@ def testSaveFields(self):
QgsField('TestDbl', QVariant.Double, 'double', 8, 6),
QgsField('TestString', QVariant.String, 'string', 50, 0),
QgsField('TestDate', QVariant.Date, 'date'),
QgsField('TestTime', QVariant.Time, 'time'),
QgsField('TestDateTime', QVariant.DateTime, 'datetime')]
assert myMemoryLayer.startEditing()
for f in myFields:
Expand Down
20 changes: 19 additions & 1 deletion tests/src/python/test_provider_postgres.py
Expand Up @@ -18,7 +18,7 @@
from qgis.core import NULL

from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsProviderRegistry
from PyQt4.QtCore import QSettings
from PyQt4.QtCore import QSettings, QDate, QTime, QDateTime, QVariant
from utilities import (unitTestDataPath,
getQgisTestApp,
unittest,
Expand Down Expand Up @@ -59,6 +59,24 @@ def testDefaultValue(self):
assert self.provider.defaultValue(1) == NULL
assert self.provider.defaultValue(2) == '\'qgis\'::text'

def testDateTimeTypes(self):
vl = QgsVectorLayer('%s table="qgis_test"."date_times" sql=' % (self.dbconn), "testdatetimes", "postgres")
assert(vl.isValid())

fields = vl.dataProvider().fields()
self.assertEqual(fields.at(fields.indexFromName('date_field')).type(), QVariant.Date)
self.assertEqual(fields.at(fields.indexFromName('time_field')).type(), QVariant.Time)
self.assertEqual(fields.at(fields.indexFromName('datetime_field')).type(), QVariant.DateTime)

f = vl.getFeatures(QgsFeatureRequest()).next()

date_idx = vl.fieldNameIndex('date_field')
assert isinstance(f.attributes()[date_idx], QDate)
time_idx = vl.fieldNameIndex('time_field')
assert isinstance(f.attributes()[time_idx], QTime)
datetime_idx = vl.fieldNameIndex('datetime_field')
assert isinstance(f.attributes()[datetime_idx], QDateTime)

def testQueryLayers(self):
def test_query(dbconn, query, key):
ql = QgsVectorLayer('%s srid=4326 table="%s" (geom) key=\'%s\' sql=' % (dbconn, query.replace('"', '\\"'), key), "testgeom", "postgres")
Expand Down

0 comments on commit 7dc5eac

Please sign in to comment.