diff --git a/python/auto_sip.blacklist b/python/auto_sip.blacklist index 95dddf62e57..d9082770f16 100644 --- a/python/auto_sip.blacklist +++ b/python/auto_sip.blacklist @@ -94,6 +94,7 @@ core/qgspropertycollection.sip core/qgsprovidermetadata.sip core/qgsproviderregistry.sip core/qgspythonrunner.sip +core/qgsrange.sip core/qgsrelation.sip core/qgsrelationmanager.sip core/qgsrenderchecker.sip diff --git a/python/core/core.sip b/python/core/core.sip index 7d26e850f15..b1b6234acb2 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -127,6 +127,7 @@ %Include qgsprovidermetadata.sip %Include qgsproviderregistry.sip %Include qgspythonrunner.sip +%Include qgsrange.sip %Include qgsrelation.sip %Include qgsrelationmanager.sip %Include qgsrenderchecker.sip diff --git a/python/core/qgsrange.sip b/python/core/qgsrange.sip new file mode 100644 index 00000000000..46ba0f0ed78 --- /dev/null +++ b/python/core/qgsrange.sip @@ -0,0 +1,57 @@ +class QgsDoubleRange +{ +%TypeHeaderCode +#include +%End + + public: + + QgsDoubleRange( double lower, double upper, bool includeLower = true, bool includeUpper = true ); + double lower() const; + double upper() const; + bool includeLower() const; + bool includeUpper() const; + bool isEmpty() const; + bool contains( const QgsDoubleRange &other ) const; + bool contains( double element ) const; + bool overlaps( const QgsDoubleRange &other ) const; +}; + +class QgsIntRange +{ +%TypeHeaderCode +#include +%End + + public: + + QgsIntRange( int lower, int upper, bool includeLower = true, bool includeUpper = true ); + int lower() const; + int upper() const; + bool includeLower() const; + bool includeUpper() const; + bool isEmpty() const; + bool contains( const QgsIntRange &other ) const; + bool contains( int element ) const; + bool overlaps( const QgsIntRange &other ) const; +}; + +class QgsDateRange +{ +%TypeHeaderCode +#include +%End + + public: + + QgsDateRange( QDate lower, QDate upper, bool includeLower = true, bool includeUpper = true ); + QDate lower() const; + QDate upper() const; + bool includeLower() const; + bool includeUpper() const; + bool isEmpty() const; + bool contains( const QgsDateRange &other ) const; + bool contains( QDate element ) const; + bool overlaps( const QgsDateRange &other ) const; +}; + diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 92121f0d294..cecf3d96052 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -762,6 +762,7 @@ SET(QGIS_CORE_HDRS qgsprovidermetadata.h qgsproviderregistry.h qgspythonrunner.h + qgsrange.h qgsrenderchecker.h qgsrendercontext.h qgsruntimeprofiler.h diff --git a/src/core/qgsrange.h b/src/core/qgsrange.h new file mode 100644 index 00000000000..9d70bb41e22 --- /dev/null +++ b/src/core/qgsrange.h @@ -0,0 +1,307 @@ +/*************************************************************************** + qgsrange.h + ---------- + begin : April 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSRANGE_H +#define QGSRANGE_H + +#include "qgis.h" +#include "qgis_core.h" + +/** + * \class QgsRange + * \ingroup core + * A template based class for storing ranges (lower to upper values). + * + * QgsRange classes represent a range of values of some element type. For instance, + * ranges of QDateTime might be used to represent the ranges of timestamp ranges. + * + * Ranges can indicate whether the upper and lower values are inclusive or exclusive. + * The inclusivity or exclusivity of bounds is considered when determining things like + * whether ranges overlap or during calculation of range intersections. + * + * \since QGIS 3.0 + * \see QgsDoubleRange + * \see QgsIntRange + * \note not available in Python bindings + */ +template class CORE_EXPORT QgsRange +{ + public: + + /** + * Constructor for QgsRange. The \a lower and \a upper bounds are specified, + * and optionally whether or not these bounds are included in the range. + */ + QgsRange( T lower, T upper, bool includeLower = true, bool includeUpper = true ) + : mLower( lower ) + , mUpper( upper ) + , mIncludeLower( includeLower ) + , mIncludeUpper( includeUpper ) + {} + + /** + * Returns the lower bound of the range. + * \see upper() + * \see includeLower() + */ + T lower() const { return mLower; } + + /** + * Returns the upper bound of the range. + * \see lower() + * \see includeUpper() + */ + T upper() const { return mUpper; } + + /** + * Returns true if the lower bound is inclusive, or false if the lower + * bound is exclusive. + * \see lower() + * \see includeUpper() + */ + bool includeLower() const { return mIncludeLower; } + + /** + * Returns true if the upper bound is inclusive, or false if the upper + * bound is exclusive. + * \see upper() + * \see includeLower() + */ + bool includeUpper() const { return mIncludeUpper; } + + /** + * Returns true if the range is empty, ie the lower bound equals (or exceeds) the upper bound + * and either the bounds are exclusive. + */ + bool isEmpty() const { return mLower > mUpper || ( mUpper == mLower && !( mIncludeLower || mIncludeUpper ) ); } + + /** + * Returns true if this range contains another range. + * \see overlaps() + */ + bool contains( const QgsRange &other ) const + { + bool lowerOk = ( mIncludeLower && mLower <= other.mLower ) + || ( !mIncludeLower && mLower < other.mLower ) + || ( !mIncludeLower && !other.mIncludeLower && mLower <= other.mLower ); + if ( !lowerOk ) + return false; + + bool upperOk = ( mIncludeUpper && mUpper >= other.mUpper ) + || ( !mIncludeUpper && mUpper > other.mUpper ) + || ( !mIncludeUpper && !other.mIncludeUpper && mUpper >= other.mUpper ); + if ( !upperOk ) + return false; + + return true; + } + + /** + * Returns true if this range contains a specified \a element. + */ + bool contains( T element ) const + { + bool lowerOk = ( mIncludeLower && mLower <= element ) + || ( !mIncludeLower && mLower < element ); + if ( !lowerOk ) + return false; + + bool upperOk = ( mIncludeUpper && mUpper >= element ) + || ( !mIncludeUpper && mUpper > element ); + if ( !upperOk ) + return false; + + return true; + } + + /** + * Returns true if this range overlaps another range. + * \see contains() + */ + 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; + + if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) ) + && ( ( mIncludeUpper && mUpper >= other.mLower ) || ( !mIncludeUpper && mUpper > other.mLower ) ) ) + return true; + + if ( ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) + && ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) ) + return true; + + 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; + + return false; + } + + + private: + + T mLower; + T mUpper; + bool mIncludeLower = true; + bool mIncludeUpper = true; + +}; + + +/** + * QgsRange which stores a range of double values. + * \since QGIS 3.0 + * \see QgsIntRange + * \see QgsDateRange + * \see QgsDateTimeRange + */ +typedef QgsRange< double > QgsDoubleRange; + +/** + * QgsRange which stores a range of integer values. + * \since QGIS 3.0 + * \see QgsDoubleRange + * \see QgsDateRange + * \see QgsDateTimeRange + */ +typedef QgsRange< int > QgsIntRange; + + +// specialization required to handle invalid QDate bounds +template<> +bool QgsRange::isEmpty() const +{ + if ( !mLower.isValid() && !mUpper.isValid() ) + return true; + + if ( mLower.isValid() != mUpper.isValid() ) + return false; + + if ( mLower > mUpper ) + return true; + + if ( mLower == mUpper && !( mIncludeLower || mIncludeUpper ) ) + return true; + + return false; +} + +template<> +bool QgsRange::contains( const QgsRange &other ) const +{ + if ( !other.mLower.isValid() && mLower.isValid() ) + return false; + + if ( mLower.isValid() ) + { + bool lowerOk = ( mIncludeLower && mLower <= other.mLower ) + || ( !mIncludeLower && mLower < other.mLower ) + || ( !mIncludeLower && !other.mIncludeLower && mLower <= other.mLower ); + if ( !lowerOk ) + return false; + } + + if ( !other.mUpper.isValid() && mUpper.isValid() ) + return false; + + if ( mUpper.isValid() ) + { + bool upperOk = ( mIncludeUpper && mUpper >= other.mUpper ) + || ( !mIncludeUpper && mUpper > other.mUpper ) + || ( !mIncludeUpper && !other.mIncludeUpper && mUpper >= other.mUpper ); + if ( !upperOk ) + return false; + } + + return true; +} + +template<> +bool QgsRange::contains( QDate element ) const +{ + if ( !element.isValid() ) + return false; + + if ( mLower.isValid() ) + { + bool lowerOk = ( mIncludeLower && mLower <= element ) + || ( !mIncludeLower && mLower < element ); + if ( !lowerOk ) + return false; + } + + if ( mUpper.isValid() ) + { + bool upperOk = ( mIncludeUpper && mUpper >= element ) + || ( !mIncludeUpper && mUpper > element ); + if ( !upperOk ) + return false; + } + + return true; +} + +template<> +bool QgsRange::overlaps( const QgsRange &other ) const +{ + if ( !mUpper.isValid() && ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) ) + return true; + + if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) ) + && ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) ) + return true; + + if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) ) + && ( ( mIncludeUpper && mUpper >= other.mLower ) || ( !mIncludeUpper && mUpper > other.mLower ) ) ) + return true; + + if ( ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) + && ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) ) + return true; + + 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; + + return false; +} + +/** + * QgsRange which stores a range of dates. + * + * Invalid QDates as the lower or upper bound are permitted. In this case, + * the bound is considered to be infinite. E.g. QgsDateRange(QDate(),QDate(2017,1,1)) + * is treated as a range containing all dates before 2017-1-1. + * QgsDateRange(QDate(2017,1,1),QDate()) is treated as a range containing all dates after 2017-1-1. + * \since QGIS 3.0 + * \see QgsIntRange + * \see QgsDoubleRange + * \see QgsDateTimeRange + */ +typedef QgsRange< QDate > QgsDateRange; + + + +#endif // QGSRANGE_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 3577c92ca67..3b5a35b11ea 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -98,6 +98,7 @@ ADD_PYTHON_TEST(PyQgsPoint test_qgspoint.py) ADD_PYTHON_TEST(PyQgsPointClusterRenderer test_qgspointclusterrenderer.py) ADD_PYTHON_TEST(PyQgsPointDisplacementRenderer test_qgspointdisplacementrenderer.py) ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidgets.py) +ADD_PYTHON_TEST(PyQgsRange test_qgsrange.py) ADD_PYTHON_TEST(PyQgsRangeWidgets test_qgsrangewidgets.py) ADD_PYTHON_TEST(PyQgsRasterFileWriter test_qgsrasterfilewriter.py) ADD_PYTHON_TEST(PyQgsRasterFileWriterTask test_qgsrasterfilewritertask.py) diff --git a/tests/src/python/test_qgsrange.py b/tests/src/python/test_qgsrange.py new file mode 100644 index 00000000000..15959c8ea8e --- /dev/null +++ b/tests/src/python/test_qgsrange.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsRange + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '11.04.2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.testing import unittest +from qgis.core import (QgsIntRange, + QgsDateRange) +from qgis.PyQt.QtCore import QDate + + +class TestQgsIntRange(unittest.TestCase): + + def testGetters(self): + range = QgsIntRange(1, 11) + self.assertEqual(range.lower(), 1) + self.assertEqual(range.upper(), 11) + self.assertTrue(range.includeLower()) + self.assertTrue(range.includeUpper()) + + range = QgsIntRange(-1, 3, False, False) + self.assertEqual(range.lower(), -1) + self.assertEqual(range.upper(), 3) + self.assertFalse(range.includeLower()) + self.assertFalse(range.includeUpper()) + + def testIsEmpty(self): + range = QgsIntRange(1, 1) + # should not be empty because 1 is included + self.assertFalse(range.isEmpty()) + + range = QgsIntRange(1, 1, False, False) + # should be empty because 1 is NOT included + self.assertTrue(range.isEmpty()) + + # invalid range is empty + range = QgsIntRange(1, -1) + self.assertTrue(range.isEmpty()) + + def testContains(self): + # includes both ends + range = QgsIntRange(0, 10) + self.assertTrue(range.contains(QgsIntRange(1, 9))) + self.assertTrue(range.contains(QgsIntRange(1, 10))) + self.assertTrue(range.contains(QgsIntRange(0, 9))) + self.assertTrue(range.contains(QgsIntRange(0, 10))) + self.assertFalse(range.contains(QgsIntRange(-1, 9))) + self.assertFalse(range.contains(QgsIntRange(1, 11))) + + # does not include left end + range = QgsIntRange(0, 10, False, True) + self.assertTrue(range.contains(QgsIntRange(1, 9))) + self.assertTrue(range.contains(QgsIntRange(1, 10))) + self.assertFalse(range.contains(QgsIntRange(0, 9))) + self.assertFalse(range.contains(QgsIntRange(0, 10))) + self.assertFalse(range.contains(QgsIntRange(-1, 9))) + self.assertFalse(range.contains(QgsIntRange(1, 11))) + + # does not include right end + range = QgsIntRange(0, 10, True, False) + self.assertTrue(range.contains(QgsIntRange(1, 9))) + self.assertFalse(range.contains(QgsIntRange(1, 10))) + self.assertTrue(range.contains(QgsIntRange(0, 9))) + self.assertFalse(range.contains(QgsIntRange(0, 10))) + self.assertFalse(range.contains(QgsIntRange(-1, 9))) + self.assertFalse(range.contains(QgsIntRange(1, 11))) + + def testContainsElement(self): + # includes both ends + range = QgsIntRange(0, 10) + self.assertTrue(range.contains(0)) + self.assertTrue(range.contains(5)) + self.assertTrue(range.contains(10)) + self.assertFalse(range.contains(-1)) + self.assertFalse(range.contains(11)) + + # includes left end + range = QgsIntRange(0, 10, True, False) + self.assertTrue(range.contains(0)) + self.assertTrue(range.contains(5)) + self.assertFalse(range.contains(10)) + self.assertFalse(range.contains(-1)) + self.assertFalse(range.contains(11)) + + # includes right end + range = QgsIntRange(0, 10, False, True) + self.assertFalse(range.contains(0)) + self.assertTrue(range.contains(5)) + self.assertTrue(range.contains(10)) + self.assertFalse(range.contains(-1)) + self.assertFalse(range.contains(11)) + + # includes neither end + range = QgsIntRange(0, 10, False, False) + self.assertFalse(range.contains(0)) + self.assertTrue(range.contains(5)) + self.assertFalse(range.contains(10)) + self.assertFalse(range.contains(-1)) + 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))) + + +class TestQgsDateRange(unittest.TestCase): + + def testGetters(self): + range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)) + self.assertEqual(range.lower(), QDate(2010, 3, 1)) + self.assertEqual(range.upper(), QDate(2010, 6, 2)) + self.assertTrue(range.includeLower()) + self.assertTrue(range.includeUpper()) + + range = QgsDateRange(QDate(), QDate(2010, 6, 2)) + self.assertFalse(range.lower().isValid()) + self.assertEqual(range.upper(), QDate(2010, 6, 2)) + + range = QgsDateRange(QDate(2010, 3, 1), QDate()) + self.assertEqual(range.lower(), QDate(2010, 3, 1)) + self.assertFalse(range.upper().isValid()) + + def testIsEmpty(self): + range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)) + self.assertFalse(range.isEmpty()) + + range = QgsDateRange(QDate(), QDate(2010, 6, 2)) + self.assertFalse(range.isEmpty()) + + range = QgsDateRange(QDate(2010, 3, 1), QDate()) + self.assertFalse(range.isEmpty()) + + range = QgsDateRange(QDate(), QDate()) + self.assertTrue(range.isEmpty()) + + range = QgsDateRange(QDate(2017, 3, 1), QDate(2010, 6, 2)) + self.assertTrue(range.isEmpty()) + + range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1)) + self.assertFalse(range.isEmpty()) + + range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1), False, False) + self.assertTrue(range.isEmpty()) + + def testContains(self): + # includes both ends + range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 6, 2)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)))) + self.assertFalse(range.contains(QgsDateRange(QDate(2009, 4, 1), QDate(2010, 4, 5)))) + self.assertFalse(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2017, 4, 5)))) + self.assertFalse(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate()))) + self.assertFalse(range.contains(QgsDateRange(QDate(), QDate(2010, 4, 1)))) + + # infinite left end + range = QgsDateRange(QDate(), QDate(2010, 6, 2)) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 6, 2)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2009, 4, 1), QDate(2010, 4, 5)))) + self.assertFalse(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2017, 4, 5)))) + self.assertFalse(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate()))) + self.assertTrue(range.contains(QgsDateRange(QDate(), QDate(2010, 4, 1)))) + + # infinite right end + range = QgsDateRange(QDate(2010, 3, 1), QDate()) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2010, 6, 2)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)))) + self.assertFalse(range.contains(QgsDateRange(QDate(2009, 4, 1), QDate(2010, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate(2017, 4, 5)))) + self.assertTrue(range.contains(QgsDateRange(QDate(2010, 4, 1), QDate()))) + self.assertFalse(range.contains(QgsDateRange(QDate(), QDate(2010, 4, 1)))) + + def testContainsElement(self): + # includes both ends + range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)) + self.assertTrue(range.contains(QDate(2010, 3, 1))) + self.assertTrue(range.contains(QDate(2010, 5, 2))) + self.assertTrue(range.contains(QDate(2010, 6, 2))) + self.assertFalse(range.contains(QDate(2009, 6, 2))) + self.assertFalse(range.contains(QDate(2017, 6, 2))) + self.assertFalse(range.contains(QDate())) + + # infinite left end + range = QgsDateRange(QDate(), QDate(2010, 6, 2)) + self.assertTrue(range.contains(QDate(2010, 3, 1))) + self.assertTrue(range.contains(QDate(2010, 5, 2))) + self.assertTrue(range.contains(QDate(2010, 6, 2))) + self.assertTrue(range.contains(QDate(2009, 6, 2))) + self.assertFalse(range.contains(QDate(2017, 6, 2))) + self.assertFalse(range.contains(QDate())) + + # infinite right end + range = QgsDateRange(QDate(2010, 3, 1), QDate()) + self.assertTrue(range.contains(QDate(2010, 3, 1))) + self.assertTrue(range.contains(QDate(2010, 5, 2))) + self.assertTrue(range.contains(QDate(2010, 6, 2))) + self.assertFalse(range.contains(QDate(2009, 6, 2))) + self.assertTrue(range.contains(QDate(2017, 6, 2))) + 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)))) + + +if __name__ == "__main__": + unittest.main()