mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-04 00:06:46 -05:00
Add some methods to parse ISO8601 duration strings
(eg '2021-03-23T00:00:00Z/2021-03-24T12:00:00Z/PT12H') to a list of datetimes
This commit is contained in:
parent
1a7fe6ae55
commit
1bb042bc78
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QgsTemporalUtils
|
class QgsTemporalUtils
|
||||||
{
|
{
|
||||||
%Docstring(signature="appended")
|
%Docstring(signature="appended")
|
||||||
@ -100,6 +102,37 @@ the number of days in the current month.
|
|||||||
|
|
||||||
.. versionadded:: 3.18
|
.. versionadded:: 3.18
|
||||||
%End
|
%End
|
||||||
|
|
||||||
|
static QList< QDateTime > calculateDateTimesUsingDuration( const QDateTime &start, const QDateTime &end, const QString &duration, bool &ok /Out/, bool &maxValuesExceeded /Out/, int maxValues = -1 );
|
||||||
|
%Docstring
|
||||||
|
Calculates a complete list of datetimes between ``start`` and ``end``, using the specified ISO8601 ``duration`` string (eg "PT12H").
|
||||||
|
|
||||||
|
:param start: start date time
|
||||||
|
:param end: end date time
|
||||||
|
:param duration: ISO8601 duration string
|
||||||
|
:param maxValues: maximum number of values to return, or -1 to return all values
|
||||||
|
|
||||||
|
:return: - calculated list of date times
|
||||||
|
- ok: will be set to ``True`` if ``duration`` was successfully parsed and date times could be calculated
|
||||||
|
- maxValuesExceeded: will be set to ``True`` if the maximum number of values to return was exceeded
|
||||||
|
|
||||||
|
.. versionadded:: 3.20
|
||||||
|
%End
|
||||||
|
|
||||||
|
static QList< QDateTime > calculateDateTimesFromISO8601( const QString &string, bool &ok /Out/, bool &maxValuesExceeded /Out/, int maxValues = -1 );
|
||||||
|
%Docstring
|
||||||
|
Calculates a complete list of datetimes from a ISO8601 ``string`` containing a duration (eg "2021-03-23T00:00:00Z/2021-03-24T12:00:00Z/PT12H").
|
||||||
|
|
||||||
|
:param string: ISO8601 compatible string
|
||||||
|
:param maxValues: maximum number of values to return, or -1 to return all values
|
||||||
|
|
||||||
|
:return: - calculated list of date times
|
||||||
|
- ok: will be set to ``True`` if ``string`` was successfully parsed and date times could be calculated
|
||||||
|
- maxValuesExceeded: will be set to ``True`` if the maximum number of values to return was exceeded
|
||||||
|
|
||||||
|
.. versionadded:: 3.20
|
||||||
|
%End
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -209,3 +209,86 @@ QDateTime QgsTemporalUtils::calculateFrameTime( const QDateTime &start, const lo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QDateTime> QgsTemporalUtils::calculateDateTimesUsingDuration( const QDateTime &start, const QDateTime &end, const QString &duration, bool &ok, bool &maxValuesExceeded, int maxValues )
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
const QgsTimeDuration timeDuration( QgsTimeDuration::fromString( duration, ok ) );
|
||||||
|
if ( !ok )
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if ( timeDuration.years == 0 && timeDuration.months == 0 && timeDuration.weeks == 0 && timeDuration.days == 0
|
||||||
|
&& timeDuration.hours == 0 && timeDuration.minutes == 0 && timeDuration.seconds == 0 )
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QDateTime> res;
|
||||||
|
QDateTime current = start;
|
||||||
|
maxValuesExceeded = false;
|
||||||
|
while ( current <= end )
|
||||||
|
{
|
||||||
|
res << current;
|
||||||
|
|
||||||
|
if ( maxValues >= 0 && res.size() > maxValues )
|
||||||
|
{
|
||||||
|
maxValuesExceeded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( timeDuration.years )
|
||||||
|
current = current.addYears( timeDuration.years );
|
||||||
|
if ( timeDuration.months )
|
||||||
|
current = current.addMonths( timeDuration.months );
|
||||||
|
if ( timeDuration.weeks || timeDuration.days )
|
||||||
|
current = current.addDays( timeDuration.weeks * 7 + timeDuration.days );
|
||||||
|
if ( timeDuration.hours || timeDuration.minutes || timeDuration.seconds )
|
||||||
|
current = current.addSecs( timeDuration.hours * 60LL * 60 + timeDuration.minutes * 60 + timeDuration.seconds );
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QDateTime> QgsTemporalUtils::calculateDateTimesFromISO8601( const QString &string, bool &ok, bool &maxValuesExceeded, int maxValues )
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
maxValuesExceeded = false;
|
||||||
|
const QStringList parts = string.split( '/' );
|
||||||
|
if ( parts.length() != 3 )
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDateTime start = QDateTime::fromString( parts.at( 0 ), Qt::ISODate );
|
||||||
|
if ( !start.isValid() )
|
||||||
|
return {};
|
||||||
|
const QDateTime end = QDateTime::fromString( parts.at( 1 ), Qt::ISODate );
|
||||||
|
if ( !end.isValid() )
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return calculateDateTimesUsingDuration( start, end, parts.at( 2 ), ok, maxValuesExceeded, maxValues );
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// QgsTimeDuration
|
||||||
|
//
|
||||||
|
|
||||||
|
QgsTimeDuration QgsTimeDuration::fromString( const QString &string, bool &ok )
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
thread_local QRegularExpression sRx( QStringLiteral( R"(P(?:([\d]+)Y)?(?:([\d]+)M)?(?:([\d]+)W)?(?:([\d]+)D)?(?:T(?:([\d]+)H)?(?:([\d]+)M)?(?:([\d\.]+)S)?)?$)" ) );
|
||||||
|
|
||||||
|
const QRegularExpressionMatch match = sRx.match( string );
|
||||||
|
QgsTimeDuration duration;
|
||||||
|
if ( match.hasMatch() )
|
||||||
|
{
|
||||||
|
ok = true;
|
||||||
|
duration.years = match.capturedView( 1 ).toInt();
|
||||||
|
duration.months = match.capturedView( 2 ).toInt();
|
||||||
|
duration.weeks = match.capturedView( 3 ).toInt();
|
||||||
|
duration.days = match.capturedView( 4 ).toInt();
|
||||||
|
duration.hours = match.capturedView( 5 ).toInt();
|
||||||
|
duration.minutes = match.capturedView( 6 ).toInt();
|
||||||
|
duration.seconds = match.capturedView( 7 ).toDouble();
|
||||||
|
}
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|||||||
@ -25,6 +25,46 @@ class QgsMapSettings;
|
|||||||
class QgsFeedback;
|
class QgsFeedback;
|
||||||
class QgsMapDecoration;
|
class QgsMapDecoration;
|
||||||
|
|
||||||
|
#ifndef SIP_RUN
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup core
|
||||||
|
* \class QgsTimeDuration
|
||||||
|
* \brief Contains utility methods for working with temporal layers and projects.
|
||||||
|
*
|
||||||
|
* Designed for storage of ISO8601 duration values.
|
||||||
|
*
|
||||||
|
* \note Not available in Python bindings
|
||||||
|
* \since QGIS 3.20
|
||||||
|
*/
|
||||||
|
class CORE_EXPORT QgsTimeDuration
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
//! Years
|
||||||
|
int years = 0;
|
||||||
|
//! Months
|
||||||
|
int months = 0;
|
||||||
|
//! Weeks
|
||||||
|
int weeks = 0;
|
||||||
|
//! Days
|
||||||
|
int days = 0;
|
||||||
|
//! Hours
|
||||||
|
int hours = 0;
|
||||||
|
//! Minutes
|
||||||
|
int minutes = 0;
|
||||||
|
//! Seconds
|
||||||
|
double seconds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a QgsTimeDuration from a \a string value.
|
||||||
|
*/
|
||||||
|
static QgsTimeDuration fromString( const QString &string, bool &ok );
|
||||||
|
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
* \class QgsTemporalUtils
|
* \class QgsTemporalUtils
|
||||||
@ -125,6 +165,31 @@ class CORE_EXPORT QgsTemporalUtils
|
|||||||
* \since QGIS 3.18
|
* \since QGIS 3.18
|
||||||
*/
|
*/
|
||||||
static QDateTime calculateFrameTime( const QDateTime &start, const long long frame, const QgsInterval interval );
|
static QDateTime calculateFrameTime( const QDateTime &start, const long long frame, const QgsInterval interval );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a complete list of datetimes between \a start and \a end, using the specified ISO8601 \a duration string (eg "PT12H").
|
||||||
|
* \param start start date time
|
||||||
|
* \param end end date time
|
||||||
|
* \param duration ISO8601 duration string
|
||||||
|
* \param ok will be set to TRUE if \a duration was successfully parsed and date times could be calculated
|
||||||
|
* \param maxValuesExceeded will be set to TRUE if the maximum number of values to return was exceeded
|
||||||
|
* \param maxValues maximum number of values to return, or -1 to return all values
|
||||||
|
* \returns calculated list of date times
|
||||||
|
* \since QGIS 3.20
|
||||||
|
*/
|
||||||
|
static QList< QDateTime > calculateDateTimesUsingDuration( const QDateTime &start, const QDateTime &end, const QString &duration, bool &ok SIP_OUT, bool &maxValuesExceeded SIP_OUT, int maxValues = -1 );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a complete list of datetimes from a ISO8601 \a string containing a duration (eg "2021-03-23T00:00:00Z/2021-03-24T12:00:00Z/PT12H").
|
||||||
|
* \param string ISO8601 compatible string
|
||||||
|
* \param ok will be set to TRUE if \a string was successfully parsed and date times could be calculated
|
||||||
|
* \param maxValuesExceeded will be set to TRUE if the maximum number of values to return was exceeded
|
||||||
|
* \param maxValues maximum number of values to return, or -1 to return all values
|
||||||
|
* \returns calculated list of date times
|
||||||
|
* \since QGIS 3.20
|
||||||
|
*/
|
||||||
|
static QList< QDateTime > calculateDateTimesFromISO8601( const QString &string, bool &ok SIP_OUT, bool &maxValuesExceeded SIP_OUT, int maxValues = -1 );
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -144,6 +144,122 @@ class TestQgsTemporalUtils(unittest.TestCase):
|
|||||||
QgsInterval(0.2, unit))
|
QgsInterval(0.2, unit))
|
||||||
self.assertEqual(f, expected3[unit])
|
self.assertEqual(f, expected3[unit])
|
||||||
|
|
||||||
|
def testCalculateDateTimesUsingDuration(self):
|
||||||
|
# invalid duration string
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 3, 24), QTime(12, 0, 0)), 'xT12H')
|
||||||
|
self.assertFalse(ok)
|
||||||
|
# null duration string
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 3, 24), QTime(12, 0, 0)), '')
|
||||||
|
self.assertFalse(ok)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 3, 24), QTime(12, 0, 0)), 'P')
|
||||||
|
self.assertFalse(ok)
|
||||||
|
|
||||||
|
# valid durations
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 3, 24), QTime(12, 0, 0)), 'PT12H')
|
||||||
|
self.assertEqual(vals, [QDateTime(2021, 3, 23, 0, 0),
|
||||||
|
QDateTime(2021, 3, 23, 12, 0),
|
||||||
|
QDateTime(2021, 3, 24, 0, 0),
|
||||||
|
QDateTime(2021, 3, 24, 12, 0)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 3, 24), QTime(12, 0, 0)), 'PT12H', maxValues=2)
|
||||||
|
self.assertEqual(vals, [QDateTime(2021, 3, 23, 0, 0),
|
||||||
|
QDateTime(2021, 3, 23, 12, 0),
|
||||||
|
QDateTime(2021, 3, 24, 0, 0)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertTrue(exceeded)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 3, 24), QTime(12, 0, 0)), 'PT10H2M5S')
|
||||||
|
self.assertEqual(vals, [QDateTime(2021, 3, 23, 0, 0), QDateTime(2021, 3, 23, 10, 2, 5),
|
||||||
|
QDateTime(2021, 3, 23, 20, 4, 10), QDateTime(2021, 3, 24, 6, 6, 15)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2010, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 5, 24), QTime(12, 0, 0)), 'P2Y')
|
||||||
|
self.assertEqual(vals,
|
||||||
|
[QDateTime(2010, 3, 23, 0, 0), QDateTime(2012, 3, 23, 0, 0), QDateTime(2014, 3, 23, 0, 0),
|
||||||
|
QDateTime(2016, 3, 23, 0, 0), QDateTime(2018, 3, 23, 0, 0), QDateTime(2020, 3, 23, 0, 0)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2020, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 5, 24), QTime(12, 0, 0)), 'P2M')
|
||||||
|
self.assertEqual(vals,
|
||||||
|
[QDateTime(2020, 3, 23, 0, 0), QDateTime(2020, 5, 23, 0, 0), QDateTime(2020, 7, 23, 0, 0),
|
||||||
|
QDateTime(2020, 9, 23, 0, 0), QDateTime(2020, 11, 23, 0, 0), QDateTime(2021, 1, 23, 0, 0),
|
||||||
|
QDateTime(2021, 3, 23, 0, 0), QDateTime(2021, 5, 23, 0, 0)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 5, 24), QTime(12, 0, 0)), 'P2W')
|
||||||
|
self.assertEqual(vals, [QDateTime(2021, 3, 23, 0, 0), QDateTime(2021, 4, 6, 0, 0), QDateTime(2021, 4, 20, 0, 0),
|
||||||
|
QDateTime(2021, 5, 4, 0, 0), QDateTime(2021, 5, 18, 0, 0)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2021, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 4, 7), QTime(12, 0, 0)), 'P2D')
|
||||||
|
self.assertEqual(vals,
|
||||||
|
[QDateTime(2021, 3, 23, 0, 0), QDateTime(2021, 3, 25, 0, 0), QDateTime(2021, 3, 27, 0, 0),
|
||||||
|
QDateTime(2021, 3, 29, 0, 0), QDateTime(2021, 3, 31, 0, 0), QDateTime(2021, 4, 2, 0, 0),
|
||||||
|
QDateTime(2021, 4, 4, 0, 0), QDateTime(2021, 4, 6, 0, 0)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
# complex mix
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesUsingDuration(
|
||||||
|
QDateTime(QDate(2010, 3, 23), QTime(0, 0, 0)),
|
||||||
|
QDateTime(QDate(2021, 5, 24), QTime(12, 0, 0)), 'P2Y1M3W4DT5H10M22S')
|
||||||
|
self.assertEqual(vals, [QDateTime(2010, 3, 23, 0, 0), QDateTime(2012, 5, 18, 5, 10, 22),
|
||||||
|
QDateTime(2014, 7, 13, 10, 20, 44), QDateTime(2016, 9, 7, 15, 31, 6),
|
||||||
|
QDateTime(2018, 11, 1, 20, 41, 28), QDateTime(2020, 12, 27, 1, 51, 50)])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
def testCalculateDateTimesFromISO8601(self):
|
||||||
|
# invalid duration string
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesFromISO8601('x')
|
||||||
|
self.assertFalse(ok)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesFromISO8601(
|
||||||
|
'a-03-23T00:00:00Z/2021-03-24T12:00:00Z/PT12H')
|
||||||
|
self.assertFalse(ok)
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesFromISO8601(
|
||||||
|
'2021-03-23T00:00:00Z/b-03-24T12:00:00Z/PT12H')
|
||||||
|
self.assertFalse(ok)
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesFromISO8601(
|
||||||
|
'2021-03-23T00:00:00Z/2021-03-24T12:00:00Z/xc')
|
||||||
|
self.assertFalse(ok)
|
||||||
|
|
||||||
|
vals, ok, exceeded = QgsTemporalUtils.calculateDateTimesFromISO8601(
|
||||||
|
'2021-03-23T00:00:00Z/2021-03-24T12:00:00Z/PT12H')
|
||||||
|
self.assertEqual(vals, [QDateTime(2021, 3, 23, 0, 0, 0, 0, Qt.TimeSpec(1)),
|
||||||
|
QDateTime(2021, 3, 23, 12, 0, 0, 0, Qt.TimeSpec(1)),
|
||||||
|
QDateTime(2021, 3, 24, 0, 0, 0, 0, Qt.TimeSpec(1)),
|
||||||
|
QDateTime(2021, 3, 24, 12, 0, 0, 0, Qt.TimeSpec(1))])
|
||||||
|
self.assertTrue(ok)
|
||||||
|
self.assertFalse(exceeded)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user