diff --git a/python/PyQt6/core/auto_generated/qgsrange.sip.in b/python/PyQt6/core/auto_generated/qgsrange.sip.in index 45ef0c252ec1..7f70f66c86c8 100644 --- a/python/PyQt6/core/auto_generated/qgsrange.sip.in +++ b/python/PyQt6/core/auto_generated/qgsrange.sip.in @@ -385,6 +385,8 @@ Returns ``True`` if this range contains a specified ``element``. bool overlaps( const QgsTemporalRange &other ) const; %Docstring Returns ``True`` if this range overlaps another range. + +.. seealso:: :py:func:`contains` %End bool extend( const QgsTemporalRange &other ); diff --git a/python/core/auto_generated/qgsrange.sip.in b/python/core/auto_generated/qgsrange.sip.in index 45ef0c252ec1..7f70f66c86c8 100644 --- a/python/core/auto_generated/qgsrange.sip.in +++ b/python/core/auto_generated/qgsrange.sip.in @@ -385,6 +385,8 @@ Returns ``True`` if this range contains a specified ``element``. bool overlaps( const QgsTemporalRange &other ) const; %Docstring Returns ``True`` if this range overlaps another range. + +.. seealso:: :py:func:`contains` %End bool extend( const QgsTemporalRange &other ); diff --git a/src/core/qgsrange.h b/src/core/qgsrange.h index 720c38315822..8760c145fbfe 100644 --- a/src/core/qgsrange.h +++ b/src/core/qgsrange.h @@ -175,25 +175,21 @@ class QgsRange */ bool overlaps( const QgsRange &other ) const { - if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) ) - && ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) ) - return true; + // other range is completely before or completely after self range + if ( other.mUpper < mLower || other.mLower > mUpper ) + return false; - if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) ) - && ( ( mIncludeUpper && mUpper >= other.mLower ) || ( !mIncludeUpper && mUpper > other.mLower ) ) ) + // other overlaps self for sure + if ( other.mUpper > mLower && other.mLower < mUpper ) return true; - if ( ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) - && ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) ) - return true; + if ( other.mUpper == mLower ) + return other.mIncludeUpper && mIncludeLower; - if ( ( ( mIncludeLower && mLower >= other.mLower ) || ( !mIncludeLower && mLower > other.mLower ) ) - && ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) ) - return true; - - if ( mLower == other.mLower && mUpper == other.mUpper ) - return true; + if ( other.mLower == mUpper ) + return other.mIncludeLower && mIncludeUpper; + // UNREACHABLE CODE return false; } @@ -571,31 +567,47 @@ class QgsTemporalRange /** * Returns TRUE if this range overlaps another range. + * \see contains() */ bool overlaps( const QgsTemporalRange &other ) const { - if ( !mUpper.isValid() && ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) ) + // one or both range is infinite + if ( isInfinite() || other.isInfinite() ) return true; - if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) ) - && ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) ) - return true; + // all bounds are fixed + if ( mLower.isValid() && mUpper.isValid() && other.mLower.isValid() && other.mUpper.isValid() ) + { + // other range is completely before or completely after self range + if ( other.mUpper < mLower || other.mLower > mUpper ) + return false; - if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) ) - && ( ( mIncludeUpper && mUpper >= other.mLower ) || ( !mIncludeUpper && mUpper > other.mLower ) ) ) - return true; + // other overlaps self for sure + if ( other.mUpper > mLower && other.mLower < mUpper ) + return true; + } - if ( ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) - && ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) ) - return true; + // other is just before and has a bound in common + if ( other.mUpper == mLower && mLower.isValid() ) + return other.mIncludeUpper && mIncludeLower; - if ( ( ( mIncludeLower && mLower >= other.mLower ) || ( !mIncludeLower && mLower > other.mLower ) ) - && ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) ) - return true; + // other is just after and has a bound in common + if ( other.mLower == mUpper && mUpper.isValid() ) + return other.mIncludeLower && mIncludeUpper; - if ( mLower == other.mLower && mUpper == other.mUpper ) - return true; + if ( !mLower.isValid() ) + return other.mLower < mUpper || !other.mLower.isValid(); + + if ( !mUpper.isValid() ) + return other.mUpper > mLower || !other.mUpper.isValid(); + + if ( !other.mLower.isValid() ) + return other.mUpper > mLower || !mLower.isValid(); + + if ( !other.mUpper.isValid() ) + return other.mLower < mUpper || !mUpper.isValid(); + // UNREACHABLE CODE return false; } diff --git a/src/core/vector/qgsvectorlayertemporalproperties.cpp b/src/core/vector/qgsvectorlayertemporalproperties.cpp index 04c8c8aed739..bd28c4ebc845 100644 --- a/src/core/vector/qgsvectorlayertemporalproperties.cpp +++ b/src/core/vector/qgsvectorlayertemporalproperties.cpp @@ -286,7 +286,7 @@ bool QgsVectorLayerTemporalProperties::readXml( const QDomElement &element, cons const QDateTime beginDate = QDateTime::fromString( begin.toElement().text(), Qt::ISODate ); const QDateTime endDate = QDateTime::fromString( end.toElement().text(), Qt::ISODate ); - const QgsDateTimeRange range = QgsDateTimeRange( beginDate, endDate ); + const QgsDateTimeRange range = QgsDateTimeRange( beginDate, endDate, true, mLimitMode == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd ); setFixedTemporalRange( range ); return true; diff --git a/src/gui/qgsvectorlayertemporalpropertieswidget.cpp b/src/gui/qgsvectorlayertemporalpropertieswidget.cpp index a03851fac7f7..5f6d6a7621ca 100644 --- a/src/gui/qgsvectorlayertemporalpropertieswidget.cpp +++ b/src/gui/qgsvectorlayertemporalpropertieswidget.cpp @@ -122,9 +122,13 @@ void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties() properties->setIsActive( mTemporalGroupBox->isChecked() ); properties->setMode( static_cast( mModeComboBox->currentData().toInt() ) ); - properties->setLimitMode( static_cast( mLimitsComboBox->currentData().toInt() ) ); + Qgis::VectorTemporalLimitMode limitMode = static_cast( mLimitsComboBox->currentData().toInt() ); + properties->setLimitMode( limitMode ); - const QgsDateTimeRange normalRange = QgsDateTimeRange( mStartTemporalDateTimeEdit->dateTime(), mEndTemporalDateTimeEdit->dateTime() ); + const QgsDateTimeRange normalRange = QgsDateTimeRange( + mStartTemporalDateTimeEdit->dateTime(), mEndTemporalDateTimeEdit->dateTime(), + true, limitMode == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd + ); properties->setFixedTemporalRange( normalRange ); diff --git a/tests/src/python/test_qgsrange.py b/tests/src/python/test_qgsrange.py index c69bf6b1e03b..afea5ce2daf7 100644 --- a/tests/src/python/test_qgsrange.py +++ b/tests/src/python/test_qgsrange.py @@ -165,69 +165,114 @@ def testContainsElement(self): self.assertFalse(range.contains(11)) def testOverlaps(self): - # includes both ends - range = QgsIntRange(0, 10) - self.assertTrue(range.overlaps(QgsIntRange(1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(0, 9))) - self.assertTrue(range.overlaps(QgsIntRange(0, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(10, 11))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 0))) - self.assertFalse(range.overlaps(QgsIntRange(-10, -1))) - self.assertFalse(range.overlaps(QgsIntRange(11, 12))) - - # includes left end - range = QgsIntRange(0, 10, True, False) - self.assertTrue(range.overlaps(QgsIntRange(1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(0, 9))) - self.assertTrue(range.overlaps(QgsIntRange(0, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 11))) - self.assertFalse(range.overlaps(QgsIntRange(10, 11))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 0))) - self.assertFalse(range.overlaps(QgsIntRange(-10, -1))) - self.assertFalse(range.overlaps(QgsIntRange(11, 12))) - - # includes right end - range = QgsIntRange(0, 10, False, True) - self.assertTrue(range.overlaps(QgsIntRange(1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(0, 9))) - self.assertTrue(range.overlaps(QgsIntRange(0, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(10, 11))) - self.assertFalse(range.overlaps(QgsIntRange(-1, 0))) - self.assertFalse(range.overlaps(QgsIntRange(-10, -1))) - self.assertFalse(range.overlaps(QgsIntRange(11, 12))) - - # includes neither end - range = QgsIntRange(0, 10, False, False) - self.assertTrue(range.overlaps(QgsIntRange(1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(0, 9))) - self.assertTrue(range.overlaps(QgsIntRange(0, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 10))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 9))) - self.assertTrue(range.overlaps(QgsIntRange(1, 11))) - self.assertTrue(range.overlaps(QgsIntRange(-1, 11))) - self.assertFalse(range.overlaps(QgsIntRange(10, 11))) - self.assertFalse(range.overlaps(QgsIntRange(-1, 0))) - self.assertFalse(range.overlaps(QgsIntRange(-10, -1))) - self.assertFalse(range.overlaps(QgsIntRange(11, 12))) + """ + Test overlaps with every combination of include/exclude bounds + """ + + # reference range + refLower, refUpper = 0, 10 + + # other range is completely before or after reference range + for otherLower, otherUpper in [[-16, -2], [12, 32]]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsIntRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsIntRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertFalse( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + # other range overlaps reference range + for otherLower, otherUpper in [ + # parts of the ranges overlaps + [-2, 3], + [3, 14], + # full overlap and a bound in common + [-2, 10], + [0, 14], + # partial overlap and a bound in common + [0, 3], + [3, 10], + # same bounds + [0, 10], + # reference range is completely inside other range + [-2, 14], + # other range is completely inside reference range + [3, 8], + ]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsIntRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsIntRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertTrue( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + for ( + otherLower, + otherUpper, + otherIncLower, + otherIncUpper, + refIncLower, + refIncUpper, + expected, + ) in [ + # other range and reference range are contiguous + [-3, 0, True, True, True, True, True], + [-3, 0, True, True, True, False, True], + [-3, 0, True, True, False, True, False], + [-3, 0, True, True, False, False, False], + [-3, 0, True, False, True, True, False], + [-3, 0, True, False, True, False, False], + [-3, 0, True, False, False, True, False], + [-3, 0, True, False, False, False, False], + [-3, 0, False, True, True, True, True], + [-3, 0, False, True, True, False, True], + [-3, 0, False, True, False, True, False], + [-3, 0, False, True, False, False, False], + [-3, 0, False, False, True, True, False], + [-3, 0, False, False, True, False, False], + [-3, 0, False, False, False, True, False], + [-3, 0, False, False, False, False, False], + # reference range and other range are contiguous + [10, 14, True, True, True, True, True], + [10, 14, True, True, True, False, False], + [10, 14, True, True, False, True, True], + [10, 14, True, True, False, False, False], + [10, 14, True, False, True, True, True], + [10, 14, True, False, True, False, False], + [10, 14, True, False, False, True, True], + [10, 14, True, False, False, False, False], + [10, 14, False, True, True, True, False], + [10, 14, False, True, True, False, False], + [10, 14, False, True, False, True, False], + [10, 14, False, True, False, False, False], + [10, 14, False, False, True, True, False], + [10, 14, False, False, True, False, False], + [10, 14, False, False, False, True, False], + [10, 14, False, False, False, False, False], + ]: + refRange = QgsIntRange(refLower, refUpper, refIncLower, refIncUpper) + otherRange = QgsIntRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertEqual( + refRange.overlaps(otherRange), expected, f"{refRange=} {otherRange=}" + ) class TestQgsDoubleRange(unittest.TestCase): @@ -430,90 +475,336 @@ def testContainsElement(self): self.assertFalse(range.contains(QDate())) def testOverlaps(self): - # includes both ends - range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 6, 2))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2009, 4, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2017, 4, 5))) - ) - self.assertTrue(range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate()))) - self.assertTrue(range.overlaps(QgsDateRange(QDate(), QDate(2010, 4, 1)))) - self.assertFalse( - range.overlaps(QgsDateRange(QDate(2009, 4, 1), QDate(2009, 8, 5))) - ) - self.assertFalse( - range.overlaps(QgsDateRange(QDate(2019, 4, 1), QDate(2019, 8, 5))) - ) - - range = QgsDateRange(QDate(), QDate(2010, 6, 2)) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 6, 2))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2009, 4, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2017, 4, 5))) - ) - self.assertTrue(range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate()))) - self.assertTrue(range.overlaps(QgsDateRange(QDate(), QDate(2010, 4, 1)))) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2009, 4, 1), QDate(2009, 8, 5))) - ) - self.assertFalse( - range.overlaps(QgsDateRange(QDate(2019, 4, 1), QDate(2019, 8, 5))) - ) - - range = QgsDateRange(QDate(2010, 3, 1), QDate()) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 6, 2))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2009, 4, 1), QDate(2010, 4, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate(2017, 4, 5))) - ) - self.assertTrue(range.overlaps(QgsDateRange(QDate(2010, 4, 1), QDate()))) - self.assertTrue(range.overlaps(QgsDateRange(QDate(), QDate(2010, 4, 1)))) - self.assertFalse( - range.overlaps(QgsDateRange(QDate(2009, 4, 1), QDate(2009, 8, 5))) - ) - self.assertTrue( - range.overlaps(QgsDateRange(QDate(2019, 4, 1), QDate(2019, 8, 5))) - ) + """ + Test overlaps with every combination of include/exclude bounds + and infinite bounds. + """ + + # ----------------------------------------------------------- + # reference range has no infinite bounds + refLower, refUpper = QDate(2007, 4, 7), QDate(2013, 3, 2) + + # other range is completely before or after reference range + for otherLower, otherUpper in [ + [QDate(), QDate(2000, 4, 6)], + [QDate(2018, 8, 9), QDate(2020, 9, 9)], + [QDate(2000, 1, 1), QDate(2004, 5, 7)], + [QDate(2024, 4, 3), QDate()], + ]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsDateRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertFalse( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + # other range overlaps reference range + for otherLower, otherUpper in [ + # parts of the ranges overlaps + [QDate(1999, 3, 2), QDate(2010, 8, 8)], + [QDate(2010, 8, 8), QDate(2024, 5, 9)], + [QDate(), QDate(2010, 8, 8)], + [QDate(2010, 8, 8), QDate()], + # full overlap and a bound in common + [QDate(1999, 3, 2), QDate(2013, 3, 2)], + [QDate(2007, 4, 7), QDate(2024, 5, 9)], + [QDate(2007, 4, 7), QDate()], + [QDate(), QDate(2013, 3, 2)], + # partial overlap and a bound in common + [QDate(2007, 4, 7), QDate(2010, 3, 1)], + [QDate(2010, 3, 1), QDate(2013, 3, 2)], + # same bounds + [QDate(2007, 4, 7), QDate(2013, 3, 2)], + # reference range is completely inside other range + [QDate(2005, 1, 1), QDate(2021, 10, 20)], + [QDate(), QDate(2021, 10, 20)], + [QDate(2005, 1, 1), QDate()], + [QDate(), QDate()], + # other range is completely inside reference range + [QDate(2010, 3, 1), QDate(2012, 8, 11)], + ]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsDateRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertTrue( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + # other range and reference range are contiguous + otherUpper = QDate(2007, 4, 7) + for otherLower in [QDate(1999, 3, 4), QDate()]: + for ( + otherIncLower, + otherIncUpper, + refIncLower, + refIncUpper, + expectedOverlaps, + ) in [ + [True, True, True, True, True], + [True, True, True, False, True], + [True, True, False, True, False], + [True, True, False, False, False], + [True, False, True, True, False], + [True, False, True, False, False], + [True, False, False, True, False], + [True, False, False, False, False], + [False, True, True, True, True], + [False, True, True, False, True], + [False, True, False, True, False], + [False, True, False, False, False], + [False, False, True, True, False], + [False, False, True, False, False], + [False, False, False, True, False], + [False, False, False, False, False], + ]: + refRange = QgsDateRange(refLower, refUpper, refIncLower, refIncUpper) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertEqual( + refRange.overlaps(otherRange), + expectedOverlaps, + f"{refRange=} {otherRange=}", + ) + + # reference range and other range are contiguous + otherLower = QDate(2013, 3, 2) + for otherUpper in [QDate(2025, 2, 13), QDate()]: + for ( + otherIncLower, + otherIncUpper, + refIncLower, + refIncUpper, + expectedOverlaps, + ) in [ + [True, True, True, True, True], + [True, True, True, False, False], + [True, True, False, True, True], + [True, True, False, False, False], + [True, False, True, True, True], + [True, False, True, False, False], + [True, False, False, True, True], + [True, False, False, False, False], + [False, True, True, True, False], + [False, True, True, False, False], + [False, True, False, True, False], + [False, True, False, False, False], + [False, False, True, True, False], + [False, False, True, False, False], + [False, False, False, True, False], + [False, False, False, False, False], + ]: + refRange = QgsDateRange(refLower, refUpper, refIncLower, refIncUpper) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertEqual( + refRange.overlaps(otherRange), + expectedOverlaps, + f"{refRange=} {otherRange=}", + ) + + # ----------------------------------------------------------- + # reference range has a left infinite bound + refLower, refUpper = QDate(), QDate(2013, 3, 2) + + # other range is completely after reference range + for otherLower, otherUpper in [ + [QDate(2018, 8, 9), QDate(2020, 9, 9)], + [QDate(2024, 4, 3), QDate()], + ]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsDateRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertFalse( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + # other range overlaps reference range + for otherLower, otherUpper in [ + # parts of the ranges overlaps + [QDate(2010, 8, 8), QDate(2024, 5, 9)], + [QDate(2010, 8, 8), QDate()], + # a bound in common + [QDate(1999, 8, 8), QDate(2013, 3, 2)], + # same bounds + [QDate(), QDate(2013, 3, 2)], + # reference range is completely inside other range + [QDate(), QDate(2020, 6, 6)], + # other range is completely inside reference range + [QDate(2010, 3, 1), QDate(2012, 8, 11)], + [QDate(), QDate(2010, 8, 8)], + # other range infinite + [QDate(), QDate()], + ]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsDateRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertTrue( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + # reference range and other range are contiguous + otherLower = QDate(2013, 3, 2) + for otherUpper in [QDate(2025, 2, 13), QDate()]: + for ( + otherIncLower, + otherIncUpper, + refIncLower, + refIncUpper, + expectedOverlaps, + ) in [ + [True, True, True, True, True], + [True, True, True, False, False], + [True, True, False, True, True], + [True, True, False, False, False], + [True, False, True, True, True], + [True, False, True, False, False], + [True, False, False, True, True], + [True, False, False, False, False], + [False, True, True, True, False], + [False, True, True, False, False], + [False, True, False, True, False], + [False, True, False, False, False], + [False, False, True, True, False], + [False, False, True, False, False], + [False, False, False, True, False], + [False, False, False, False, False], + ]: + refRange = QgsDateRange(refLower, refUpper, refIncLower, refIncUpper) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertEqual( + refRange.overlaps(otherRange), + expectedOverlaps, + f"{refRange=} {otherRange=}", + ) + + # ----------------------------------------------------------- + # reference range has a right infinite bound + refLower, refUpper = QDate(2013, 3, 2), QDate() + + # other range is completely before reference range + for otherLower, otherUpper in [ + [QDate(1993, 5, 9), QDate(2005, 9, 9)], + [QDate(), QDate(1999, 3, 9)], + ]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsDateRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertFalse( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + # other range overlaps reference range + for otherLower, otherUpper in [ + # parts of the ranges overlaps + [QDate(2010, 5, 9), QDate(2025, 1, 8)], + [QDate(), QDate(2025, 1, 8)], + # a bound in common + [QDate(2013, 3, 2), QDate(2025, 1, 2)], + # same bounds + [QDate(2013, 3, 2), QDate()], + # reference range is completely inside other range + [QDate(2013, 1, 1), QDate()], + # other range is completely inside reference range + [QDate(2016, 3, 1), QDate(2020, 8, 11)], + [QDate(2016, 3, 1), QDate()], + # other range infinite + [QDate(), QDate()], + ]: + for refIncLower in [True, False]: + for refIncUpper in [True, False]: + for otherIncLower in [True, False]: + for otherIncUpper in [True, False]: + refRange = QgsDateRange( + refLower, refUpper, refIncLower, refIncUpper + ) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertTrue( + refRange.overlaps(otherRange), + f"{refRange=} {otherRange=}", + ) + + # other range and reference range are contiguous + otherUpper = QDate(2013, 3, 2) + for otherLower in [QDate(), QDate(1998, 4, 8)]: + for ( + otherIncLower, + otherIncUpper, + refIncLower, + refIncUpper, + expectedOverlaps, + ) in [ + [True, True, True, True, True], + [True, True, True, False, True], + [True, True, False, True, False], + [True, True, False, False, False], + [True, False, True, True, False], + [True, False, True, False, False], + [True, False, False, True, False], + [True, False, False, False, False], + [False, True, True, True, True], + [False, True, True, False, True], + [False, True, False, True, False], + [False, True, False, False, False], + [False, False, True, True, False], + [False, False, True, False, False], + [False, False, False, True, False], + [False, False, False, False, False], + ]: + refRange = QgsDateRange(refLower, refUpper, refIncLower, refIncUpper) + otherRange = QgsDateRange( + otherLower, otherUpper, otherIncLower, otherIncUpper + ) + self.assertEqual( + refRange.overlaps(otherRange), + expectedOverlaps, + f"{refRange=} {otherRange=}", + ) def testIsInstant(self): self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)).isInstant()) diff --git a/tests/src/python/test_qgsvectorlayertemporalproperties.py b/tests/src/python/test_qgsvectorlayertemporalproperties.py index 1d3575f20d8e..137238d4406f 100644 --- a/tests/src/python/test_qgsvectorlayertemporalproperties.py +++ b/tests/src/python/test_qgsvectorlayertemporalproperties.py @@ -35,10 +35,14 @@ def testReadWrite(self): props.setMode( QgsVectorLayerTemporalProperties.TemporalMode.ModeFeatureDateTimeInstantFromField ) + props.setLimitMode(Qgis.VectorTemporalLimitMode.IncludeBeginExcludeEnd) props.setFixedTemporalRange( QgsDateTimeRange( QDateTime(QDate(2019, 3, 4), QTime(11, 12, 13)), QDateTime(QDate(2020, 5, 6), QTime(8, 9, 10)), + includeBeginning=True, + includeEnd=props.limitMode() + == Qgis.VectorTemporalLimitMode.IncludeBeginIncludeEnd, ) ) props.setStartField("start") @@ -69,6 +73,7 @@ def testReadWrite(self): self.assertEqual(props2.accumulateFeatures(), props.accumulateFeatures()) self.assertEqual(props2.startExpression(), props.startExpression()) self.assertEqual(props2.endExpression(), props.endExpression()) + self.assertEqual(props2.limitMode(), props.limitMode()) def testModeFromProvider(self): caps = QgsVectorDataProviderTemporalCapabilities()