diff --git a/autotest/ogr/data/gml/datetime.gml b/autotest/ogr/data/gml/datetime.gml
new file mode 100644
index 000000000000..2db2e4037bba
--- /dev/null
+++ b/autotest/ogr/data/gml/datetime.gml
@@ -0,0 +1,24 @@
+
+
+
+
+ 23:59:60
+ 9999-12-31
+ 9999-12-31T23:59:60.999
+ 9999-12-31T23:59:60.999
+
+
+
+
+ 23:59:60.999
+ 9999-12-31
+ 9999-12-31T23:59:60Z
+ 9999-12-31T23:59:60+12:30
+
+
+
diff --git a/autotest/ogr/data/gml/datetime.xsd b/autotest/ogr/data/gml/datetime.xsd
new file mode 100644
index 000000000000..ac07c165c35b
--- /dev/null
+++ b/autotest/ogr/data/gml/datetime.xsd
@@ -0,0 +1,54 @@
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py
index 83dd1cefe429..9a7372b9a2ec 100755
--- a/autotest/ogr/ogr_gml.py
+++ b/autotest/ogr/ogr_gml.py
@@ -5214,3 +5214,38 @@ def test_ogr_gml_citygml3(tmp_vsimem, skip_resolve_as_open_option):
f.GetGeometryRef().ExportToWkt()
== "POLYHEDRALSURFACE Z (((0 0 0,1 1 1,1 0 2,0 0 0)))"
)
+
+
+###############################################################################
+
+
+def test_ogr_gml_datetime(tmp_vsimem):
+
+ ds = ogr.Open("data/gml/datetime.gml")
+ lyr = ds.GetLayer(0)
+ f = lyr.GetNextFeature()
+ assert lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTTime
+ assert f["time"] == "23:59:60"
+ assert lyr.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTDate
+ assert f["date"] == "9999/12/31"
+ assert lyr.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTDateTime
+ assert f["datetime"] == "9999/12/31 23:59:60.999"
+ assert lyr.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTDateTime
+ assert f["dtInTimePosition"] == "9999/12/31 23:59:60.999"
+
+ tmp_filename = tmp_vsimem / "out.gml"
+ with gdal.VSIFile("data/gml/datetime.gml", "rb") as fin:
+ with gdal.VSIFile(tmp_filename, "wb") as fout:
+ fout.write(fin.read())
+
+ ds = ogr.Open(tmp_filename)
+ lyr = ds.GetLayer(0)
+ f = lyr.GetNextFeature()
+ assert lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTTime
+ assert f["time"] == "23:59:60"
+ assert lyr.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTDate
+ assert f["date"] == "9999/12/31"
+ assert lyr.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTDateTime
+ assert f["datetime"] == "9999/12/31 23:59:60.999"
+ assert lyr.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTDateTime
+ assert f["timePosition"] == "9999/12/31 23:59:60.999"
diff --git a/autotest/ogr/ogr_gmlas.py b/autotest/ogr/ogr_gmlas.py
index f29ad01023e5..61d4e7bd3e9e 100755
--- a/autotest/ogr/ogr_gmlas.py
+++ b/autotest/ogr/ogr_gmlas.py
@@ -3534,3 +3534,35 @@ def test_ogr_gmlas_citygml_lod2_no_schema_location():
pytest.skip(f"cannot open {url}")
assert ds.GetLayerCount() == 1537
+
+
+###############################################################################
+
+
+@pytest.mark.require_curl()
+def test_ogr_gmlas_datetime(tmp_vsimem):
+
+ ds = ogr.Open("GMLAS:data/gml/datetime.gml")
+ lyr = ds.GetLayer("test")
+ f = lyr.GetNextFeature()
+ lyr_defn = lyr.GetLayerDefn()
+ assert (
+ lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("time")).GetType() == ogr.OFTTime
+ )
+ assert f["time"] == "23:59:60"
+ assert (
+ lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("date")).GetType() == ogr.OFTDate
+ )
+ assert f["date"] == "9999/12/31"
+ assert (
+ lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("datetime")).GetType()
+ == ogr.OFTDateTime
+ )
+ assert f["datetime"] == "9999/12/31 23:59:60.999"
+ assert (
+ lyr_defn.GetFieldDefn(
+ lyr_defn.GetFieldIndex("dtintimeposition_timeposition")
+ ).GetType()
+ == ogr.OFTDateTime
+ )
+ assert f["dtintimeposition_timeposition"] == "9999/12/31 23:59:60.999"
diff --git a/autotest/ogr/ogr_wfs.py b/autotest/ogr/ogr_wfs.py
index db893c9e7d39..914246d1e01d 100755
--- a/autotest/ogr/ogr_wfs.py
+++ b/autotest/ogr/ogr_wfs.py
@@ -3455,7 +3455,7 @@ def test_ogr_wfs_vsimem_wfs110_schema_not_understood(with_and_without_streaming)
or f.int != 123456789
or f.float != 1.2
or f.double != 1.23
- or f.dt != "2015-04-17T12:34:56Z"
+ or f.dt != "2015/04/17 12:34:56+00"
or f.GetGeometryRef().ExportToWkt() != "POINT (2 49)"
)
@@ -5342,3 +5342,88 @@ def test_ogr_wfs_no_gml_driver(
lyr.GetNextFeature()
finally:
gml_drv.Register()
+
+
+###############################################################################
+# Test fix for https://github.com/OSGeo/gdal/issues/13120
+
+
+def test_ogr_wfs_does_not_understand_schema():
+
+ getfeatures_response = """
+
+
+ foo
+
+
+ 41.5 0.5
+
+
+
+
+"""
+
+ schema = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+ with gdaltest.tempfile(
+ "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&REQUEST=GetCapabilities",
+ """
+
+
+ foo:lyr
+ urn:ogc:def:crs:EPSG::4326
+
+ -10 40
+ 15 50
+
+
+
+
+""",
+ ), gdaltest.tempfile(
+ "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=DescribeFeatureType&TYPENAME=foo:lyr",
+ schema,
+ ), gdaltest.tempfile(
+ "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=foo:lyr&COUNT=1",
+ getfeatures_response,
+ ), gdaltest.tempfile(
+ "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=foo:lyr",
+ getfeatures_response,
+ ):
+ ds = ogr.Open("WFS:/vsimem/test_ogr_wfs_does_not_understand_schema")
+ lyr = ds.GetLayer(0)
+ assert lyr.GetGeometryColumn() == "shape"
+
+ lyr.SetSpatialFilterRect(0, 41, 1, 42)
+
+ with gdaltest.tempfile(
+ "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=DescribeFeatureType&TYPENAME=foo:lyr",
+ schema,
+ ), gdaltest.tempfile(
+ "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=foo:lyr&FILTER=%3CFilter%20xmlns%3D%22http:%2F%2Fwww.opengis.net%2Ffes%2F2.0%22%20xmlns:gml%3D%22http:%2F%2Fwww.opengis.net%2Fgml%2F3.2%22%3E%3CBBOX%3E%3CValueReference%3Eshape%3C%2FValueReference%3E%3Cgml:Envelope%3E%3Cgml:lowerCorner%3E41.0000000000000000%200.0000000000000000%3C%2Fgml:lowerCorner%3E%3Cgml:upperCorner%3E42.0000000000000000%201.0000000000000000%3C%2Fgml:upperCorner%3E%3C%2Fgml:Envelope%3E%3C%2FBBOX%3E%3C%2FFilter%3E",
+ getfeatures_response,
+ ):
+ f = lyr.GetNextFeature()
+ assert f
diff --git a/ogr/ogrsf_frmts/gml/gmlhandler.cpp b/ogr/ogrsf_frmts/gml/gmlhandler.cpp
index c122e9e7c7c6..5828d7b7bcc4 100644
--- a/ogr/ogrsf_frmts/gml/gmlhandler.cpp
+++ b/ogr/ogrsf_frmts/gml/gmlhandler.cpp
@@ -1085,11 +1085,19 @@ int GMLHandler::FindRealPropertyByCheckingConditions(int nIdx, void *attr)
OGRErr GMLHandler::startElementFeatureAttribute(const char *pszName,
int nLenName, void *attr)
{
- /* Reset flag */
- m_bInCurField = false;
GMLReadState *poState = m_poReader->GetState();
+ if (m_bInCurField && m_nAttributeIndex >= 0 &&
+ std::string_view(pszName, nLenName) == "timePosition")
+ {
+ poState->PushPath(pszName, nLenName);
+ return OGRERR_NONE;
+ }
+
+ /* Reset flag */
+ m_bInCurField = false;
+
/* -------------------------------------------------------------------- */
/* If we are collecting geometry, or if we determine this is a */
/* geometry element then append to the geometry info. */
diff --git a/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h b/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h
index 68b8e9f050ed..284cd1a0f50c 100644
--- a/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h
+++ b/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h
@@ -268,6 +268,7 @@ STRING_CONST(szXS_GYEAR, "gYear");
STRING_CONST(szXS_GYEAR_MONTH, "gYearMonth");
STRING_CONST(szXS_TIME, "time");
STRING_CONST(szXS_DATETIME, "dateTime");
+STRING_CONST(szXS_TIME_INSTANT_TYPE, "TimeInstantType");
STRING_CONST(szXS_ANY_URI, "anyURI");
STRING_CONST(szXS_ANY_TYPE, "anyType");
STRING_CONST(szXS_ANY_SIMPLE_TYPE, "anySimpleType");
diff --git a/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp b/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp
index d8f0000d508a..c34eb9ec06a7 100644
--- a/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp
+++ b/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp
@@ -3344,9 +3344,19 @@ bool GMLASSchemaAnalyzer::ExploreModelGroup(
const std::vector &osNestedClassFields =
oNestedClass.GetFields();
+ const bool bIsTimeInstantType =
+ transcode(poTypeDef->getName()) ==
+ szXS_TIME_INSTANT_TYPE;
for (size_t j = 0; j < osNestedClassFields.size(); j++)
{
GMLASField oField(osNestedClassFields[j]);
+ if (bIsTimeInstantType &&
+ oField.GetType() == GMLAS_FT_ANYSIMPLETYPE &&
+ oField.GetName() == "timePosition")
+ {
+ oField.SetType(GMLAS_FT_DATETIME,
+ szXS_DATETIME);
+ }
oField.SetName(osPrefixedEltName + "_" +
oField.GetName());
if (nMinOccurs == 0 ||
@@ -3356,6 +3366,7 @@ bool GMLASSchemaAnalyzer::ExploreModelGroup(
oField.SetMinOccurs(0);
oField.SetNotNullable(false);
}
+
aoFields.push_back(std::move(oField));
}
diff --git a/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp b/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp
index 778901b0c870..22b6e820e1c4 100644
--- a/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp
+++ b/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp
@@ -148,7 +148,62 @@ void GMLPropertyDefn::AnalysePropertyValue(const GMLProperty *psGMLProperty,
}
else
{
- m_eType = GMLPT_String;
+ const auto IsDigitLowerOrEqual = [](char c, int max)
+ { return c >= '0' && c <= '0' + max; };
+
+ if ((m_eType == GMLPT_Untyped || m_eType == GMLPT_DateTime ||
+ m_eType == GMLPT_Date) &&
+ IsDigitLowerOrEqual(pszValue[0], 9) &&
+ IsDigitLowerOrEqual(pszValue[1], 9) &&
+ IsDigitLowerOrEqual(pszValue[2], 9) &&
+ IsDigitLowerOrEqual(pszValue[3], 9) && pszValue[4] == '-' &&
+ IsDigitLowerOrEqual(pszValue[5], 1) &&
+ IsDigitLowerOrEqual(pszValue[6], 9) && pszValue[7] == '-' &&
+ IsDigitLowerOrEqual(pszValue[8], 3) &&
+ IsDigitLowerOrEqual(pszValue[9], 9))
+ {
+ if (pszValue[10] == 'T' &&
+ IsDigitLowerOrEqual(pszValue[11], 2) &&
+ IsDigitLowerOrEqual(pszValue[12], 9) &&
+ pszValue[13] == ':' &&
+ IsDigitLowerOrEqual(pszValue[14], 5) &&
+ IsDigitLowerOrEqual(pszValue[15], 9) &&
+ pszValue[16] == ':' &&
+ IsDigitLowerOrEqual(pszValue[17], 6) &&
+ IsDigitLowerOrEqual(pszValue[18], 9) &&
+ (pszValue[19] == '\0' || pszValue[19] == '.' ||
+ pszValue[19] == 'Z' || pszValue[19] == '+' ||
+ pszValue[19] == '-'))
+ {
+ m_eType = GMLPT_DateTime;
+ }
+ else if (pszValue[10] == '\0')
+ {
+ if (m_eType != GMLPT_DateTime)
+ m_eType = GMLPT_Date;
+ }
+ else
+ {
+ m_eType = GMLPT_String;
+ }
+ }
+ else if ((m_eType == GMLPT_Untyped || m_eType == GMLPT_Time) &&
+ IsDigitLowerOrEqual(pszValue[0], 2) &&
+ IsDigitLowerOrEqual(pszValue[1], 9) &&
+ pszValue[2] == ':' &&
+ IsDigitLowerOrEqual(pszValue[3], 5) &&
+ IsDigitLowerOrEqual(pszValue[4], 9) &&
+ pszValue[5] == ':' &&
+ IsDigitLowerOrEqual(pszValue[6], 6) &&
+ IsDigitLowerOrEqual(pszValue[7], 9) &&
+ (pszValue[8] == '\0' || pszValue[8] == '.'))
+ {
+ m_eType = GMLPT_Time;
+ }
+ else
+ {
+ m_eType = GMLPT_String;
+ }
}
}
else
diff --git a/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp b/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp
index e1770e4bdd94..9463d99d0c91 100644
--- a/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp
+++ b/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp
@@ -129,7 +129,7 @@ static bool GetSimpleTypeProperties(CPLXMLNode *psTypeNode,
return true;
}
- else if (EQUAL(pszBase, "dateTime"))
+ else if (EQUAL(pszBase, "dateTime") || EQUAL(pszBase, "TimeInstantType"))
{
*pGMLType = GMLPT_DateTime;
return true;
@@ -425,7 +425,8 @@ static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode,
gmlType = GMLPT_Date;
else if (EQUAL(pszStrippedNSType, "time"))
gmlType = GMLPT_Time;
- else if (EQUAL(pszStrippedNSType, "dateTime"))
+ else if (EQUAL(pszStrippedNSType, "dateTime") ||
+ EQUAL(pszStrippedNSType, "TimeInstantType"))
gmlType = GMLPT_DateTime;
else if (EQUAL(pszStrippedNSType, "real") ||
EQUAL(pszStrippedNSType, "double") ||
diff --git a/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp b/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp
index 9a1118f02646..f9f4ca138627 100644
--- a/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp
+++ b/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp
@@ -1185,6 +1185,7 @@ OGRFeatureDefn *OGRWFSLayer::BuildLayerDefn(OGRFeatureDefn *poSrcFDefn)
return poFeatureDefn;
}
poSrcFDefn = l_poLayer->GetLayerDefn();
+ osGeometryColumnName = l_poLayer->GetGeometryColumn();
bGotApproximateLayerDefn = true;
/* We cannot trust width and precision based on a single feature */
bUnsetWidthPrecision = true;
diff --git a/ogr/ogrutils.cpp b/ogr/ogrutils.cpp
index d4232c0f98f1..c5fc0de7f74c 100644
--- a/ogr/ogrutils.cpp
+++ b/ogr/ogrutils.cpp
@@ -1261,7 +1261,7 @@ int OGRParseDate(const char *pszInput, OGRField *psField, int nOptions)
return FALSE;
const double dfSeconds = CPLAtof(pszInput);
// We accept second=60 for leap seconds
- if (dfSeconds > 60.0)
+ if (dfSeconds >= 61.0)
return FALSE;
psField->Date.Second = static_cast(dfSeconds);