diff options
Diffstat (limited to 'libs/aniso8601/tests')
-rw-r--r-- | libs/aniso8601/tests/__init__.py | 7 | ||||
-rw-r--r-- | libs/aniso8601/tests/compat.py | 16 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_compat.py | 27 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_date.py | 303 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_decimalfraction.py | 19 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_duration.py | 1402 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_init.py | 49 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_interval.py | 1675 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_time.py | 539 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_timezone.py | 123 | ||||
-rw-r--r-- | libs/aniso8601/tests/test_utcoffset.py | 56 |
11 files changed, 4216 insertions, 0 deletions
diff --git a/libs/aniso8601/tests/__init__.py b/libs/aniso8601/tests/__init__.py new file mode 100644 index 000000000..1a94e017a --- /dev/null +++ b/libs/aniso8601/tests/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. diff --git a/libs/aniso8601/tests/compat.py b/libs/aniso8601/tests/compat.py new file mode 100644 index 000000000..6c5266589 --- /dev/null +++ b/libs/aniso8601/tests/compat.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import sys + +PY2 = sys.version_info[0] == 2 + +if PY2: + import mock # pylint: disable=import-error +else: + from unittest import mock diff --git a/libs/aniso8601/tests/test_compat.py b/libs/aniso8601/tests/test_compat.py new file mode 100644 index 000000000..2f7988331 --- /dev/null +++ b/libs/aniso8601/tests/test_compat.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +from aniso8601.compat import PY2, is_string + + +class TestCompatFunctions(unittest.TestCase): + def test_is_string(self): + self.assertTrue(is_string("asdf")) + self.assertTrue(is_string("")) + + # pylint: disable=undefined-variable + if PY2 is True: + self.assertTrue(is_string(unicode("asdf"))) + + self.assertFalse(is_string(None)) + self.assertFalse(is_string(123)) + self.assertFalse(is_string(4.56)) + self.assertFalse(is_string([])) + self.assertFalse(is_string({})) diff --git a/libs/aniso8601/tests/test_date.py b/libs/aniso8601/tests/test_date.py new file mode 100644 index 000000000..54e3076eb --- /dev/null +++ b/libs/aniso8601/tests/test_date.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +import aniso8601 +from aniso8601.date import get_date_resolution, parse_date +from aniso8601.exceptions import DayOutOfBoundsError, ISOFormatError +from aniso8601.resolution import DateResolution +from aniso8601.tests.compat import mock + + +class TestDateResolutionFunctions(unittest.TestCase): + def test_get_date_resolution_year(self): + self.assertEqual(get_date_resolution("2013"), DateResolution.Year) + self.assertEqual(get_date_resolution("0001"), DateResolution.Year) + self.assertEqual(get_date_resolution("19"), DateResolution.Year) + + def test_get_date_resolution_month(self): + self.assertEqual(get_date_resolution("1981-04"), DateResolution.Month) + + def test_get_date_resolution_week(self): + self.assertEqual(get_date_resolution("2004-W53"), DateResolution.Week) + self.assertEqual(get_date_resolution("2009-W01"), DateResolution.Week) + self.assertEqual(get_date_resolution("2004W53"), DateResolution.Week) + + def test_get_date_resolution_day(self): + self.assertEqual(get_date_resolution("2004-04-11"), DateResolution.Day) + self.assertEqual(get_date_resolution("20090121"), DateResolution.Day) + + def test_get_date_resolution_year_weekday(self): + self.assertEqual(get_date_resolution("2004-W53-6"), DateResolution.Weekday) + self.assertEqual(get_date_resolution("2004W536"), DateResolution.Weekday) + + def test_get_date_resolution_year_ordinal(self): + self.assertEqual(get_date_resolution("1981-095"), DateResolution.Ordinal) + self.assertEqual(get_date_resolution("1981095"), DateResolution.Ordinal) + + def test_get_date_resolution_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + get_date_resolution(testtuple) + + def test_get_date_resolution_extended_year(self): + testtuples = ("+2000", "+30000") + + for testtuple in testtuples: + with self.assertRaises(NotImplementedError): + get_date_resolution(testtuple) + + def test_get_date_resolution_badweek(self): + testtuples = ("2004-W1", "2004W1") + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + get_date_resolution(testtuple) + + def test_get_date_resolution_badweekday(self): + testtuples = ("2004-W53-67", "2004W5367") + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + get_date_resolution(testtuple) + + def test_get_date_resolution_badstr(self): + testtuples = ( + "W53", + "2004-W", + "2014-01-230", + "2014-012-23", + "201-01-23", + "201401230", + "201401", + "", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + get_date_resolution(testtuple) + + +class TestDateParserFunctions(unittest.TestCase): + def test_parse_date(self): + testtuples = ( + ( + "2013", + { + "YYYY": "2013", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + ), + ( + "0001", + { + "YYYY": "0001", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + ), + ( + "19", + { + "YYYY": "19", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + ), + ( + "1981-04-05", + { + "YYYY": "1981", + "MM": "04", + "DD": "05", + "Www": None, + "D": None, + "DDD": None, + }, + ), + ( + "19810405", + { + "YYYY": "1981", + "MM": "04", + "DD": "05", + "Www": None, + "D": None, + "DDD": None, + }, + ), + ( + "1981-04", + { + "YYYY": "1981", + "MM": "04", + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + ), + ( + "2004-W53", + { + "YYYY": "2004", + "MM": None, + "DD": None, + "Www": "53", + "D": None, + "DDD": None, + }, + ), + ( + "2009-W01", + { + "YYYY": "2009", + "MM": None, + "DD": None, + "Www": "01", + "D": None, + "DDD": None, + }, + ), + ( + "2004-W53-6", + { + "YYYY": "2004", + "MM": None, + "DD": None, + "Www": "53", + "D": "6", + "DDD": None, + }, + ), + ( + "2004W53", + { + "YYYY": "2004", + "MM": None, + "DD": None, + "Www": "53", + "D": None, + "DDD": None, + }, + ), + ( + "2004W536", + { + "YYYY": "2004", + "MM": None, + "DD": None, + "Www": "53", + "D": "6", + "DDD": None, + }, + ), + ( + "1981-095", + { + "YYYY": "1981", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": "095", + }, + ), + ( + "1981095", + { + "YYYY": "1981", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": "095", + }, + ), + ( + "1980366", + { + "YYYY": "1980", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": "366", + }, + ), + ) + + for testtuple in testtuples: + with mock.patch.object( + aniso8601.date.PythonTimeBuilder, "build_date" + ) as mockBuildDate: + mockBuildDate.return_value = testtuple[1] + + result = parse_date(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + mockBuildDate.assert_called_once_with(**testtuple[1]) + + def test_parse_date_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + parse_date(testtuple, builder=None) + + def test_parse_date_badstr(self): + testtuples = ( + "W53", + "2004-W", + "2014-01-230", + "2014-012-23", + "201-01-23", + "201401230", + "201401", + "9999 W53", + "20.50230", + "198104", + "bad", + "", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_date(testtuple, builder=None) + + def test_parse_date_mockbuilder(self): + mockBuilder = mock.Mock() + + expectedargs = { + "YYYY": "1981", + "MM": "04", + "DD": "05", + "Www": None, + "D": None, + "DDD": None, + } + + mockBuilder.build_date.return_value = expectedargs + + result = parse_date("1981-04-05", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_date.assert_called_once_with(**expectedargs) diff --git a/libs/aniso8601/tests/test_decimalfraction.py b/libs/aniso8601/tests/test_decimalfraction.py new file mode 100644 index 000000000..dc52f2406 --- /dev/null +++ b/libs/aniso8601/tests/test_decimalfraction.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +from aniso8601.decimalfraction import normalize + + +class TestDecimalFractionFunctions(unittest.TestCase): + def test_normalize(self): + self.assertEqual(normalize(""), "") + self.assertEqual(normalize("12.34"), "12.34") + self.assertEqual(normalize("123,45"), "123.45") + self.assertEqual(normalize("123,45,67"), "123.45.67") diff --git a/libs/aniso8601/tests/test_duration.py b/libs/aniso8601/tests/test_duration.py new file mode 100644 index 000000000..0d7d40a4d --- /dev/null +++ b/libs/aniso8601/tests/test_duration.py @@ -0,0 +1,1402 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +import aniso8601 +from aniso8601.duration import ( + _has_any_component, + _parse_duration_combined, + _parse_duration_prescribed, + _parse_duration_prescribed_notime, + _parse_duration_prescribed_time, + get_duration_resolution, + parse_duration, +) +from aniso8601.exceptions import ISOFormatError +from aniso8601.resolution import DurationResolution +from aniso8601.tests.compat import mock + + +class TestDurationParserFunctions(unittest.TestCase): + def test_get_duration_resolution_years(self): + self.assertEqual(get_duration_resolution("P1Y"), DurationResolution.Years) + self.assertEqual(get_duration_resolution("P1,5Y"), DurationResolution.Years) + self.assertEqual(get_duration_resolution("P1.5Y"), DurationResolution.Years) + + def test_get_duration_resolution_months(self): + self.assertEqual(get_duration_resolution("P1Y2M"), DurationResolution.Months) + self.assertEqual(get_duration_resolution("P1M"), DurationResolution.Months) + self.assertEqual(get_duration_resolution("P1,5M"), DurationResolution.Months) + self.assertEqual(get_duration_resolution("P1.5M"), DurationResolution.Months) + + def test_get_duration_resolution_weeks(self): + self.assertEqual(get_duration_resolution("P1W"), DurationResolution.Weeks) + self.assertEqual(get_duration_resolution("P1,5W"), DurationResolution.Weeks) + self.assertEqual(get_duration_resolution("P1.5W"), DurationResolution.Weeks) + + def test_get_duration_resolution_days(self): + self.assertEqual(get_duration_resolution("P1Y2M3D"), DurationResolution.Days) + self.assertEqual(get_duration_resolution("P1Y2M3,5D"), DurationResolution.Days) + self.assertEqual(get_duration_resolution("P1Y2M3.5D"), DurationResolution.Days) + self.assertEqual(get_duration_resolution("P1D"), DurationResolution.Days) + self.assertEqual(get_duration_resolution("P1,5D"), DurationResolution.Days) + self.assertEqual(get_duration_resolution("P1.5D"), DurationResolution.Days) + + def test_get_duration_resolution_hours(self): + self.assertEqual( + get_duration_resolution("P1Y2M3DT4H"), DurationResolution.Hours + ) + self.assertEqual(get_duration_resolution("PT4H"), DurationResolution.Hours) + + def test_get_duration_resolution_minutes(self): + self.assertEqual( + get_duration_resolution("P1Y2M3DT4H5M"), DurationResolution.Minutes + ) + self.assertEqual(get_duration_resolution("PT4H5M"), DurationResolution.Minutes) + + def test_get_duration_resolution_seconds(self): + self.assertEqual( + get_duration_resolution("P1Y2M3DT4H54M6S"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("P1Y2M3DT4H54M6,5S"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("P1Y2M3DT4H54M6.5S"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("PT4H54M6,5S"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("PT4H54M6.5S"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("PT0.0000001S"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("PT2.0000048S"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("P0003-06-04T12:30:05"), DurationResolution.Seconds + ) + self.assertEqual( + get_duration_resolution("P0003-06-04T12:30:05.5"), + DurationResolution.Seconds, + ) + self.assertEqual( + get_duration_resolution("P0001-02-03T14:43:59.9999997"), + DurationResolution.Seconds, + ) + + def test_parse_duration(self): + testtuples = ( + ( + "P1Y2M3DT4H54M6S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1Y2M3DT4H54M6,5S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "P1Y2M3DT4H54M6.5S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "P1YT4H", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "P1YT54M", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "54", + "TnS": None, + }, + ), + ( + "P1YT6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "P1YT4H54M", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": None, + }, + ), + ( + "P1YT4H6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": "6", + }, + ), + ( + "P1YT54M6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1YT4H54M6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P2MT4H", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "P2MT54M", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "54", + "TnS": None, + }, + ), + ( + "P2MT6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "P2MT4H54M", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": None, + }, + ), + ( + "P2MT4H6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": "6", + }, + ), + ( + "P2MT54M6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P2MT4H54M6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P3DT4H", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "P3DT54M", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": "54", + "TnS": None, + }, + ), + ( + "P3DT6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "P3DT4H54M", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": None, + }, + ), + ( + "P3DT4H6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": None, + "TnS": "6", + }, + ), + ( + "P3DT54M6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P3DT4H54M6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1Y2MT4H", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "P1Y2MT54M", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "54", + "TnS": None, + }, + ), + ( + "P1Y2MT6S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "P1Y2MT4H54M", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": None, + }, + ), + ( + "P1Y2MT4H6S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": "6", + }, + ), + ( + "P1Y2MT54M6S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1Y2MT4H54M6S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1Y3DT4H", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "P1Y3DT54M", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": "54", + "TnS": None, + }, + ), + ( + "P1Y3DT6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "P1Y3DT4H54M", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": None, + }, + ), + ( + "P1Y3DT4H6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": None, + "TnS": "6", + }, + ), + ( + "P1Y3DT54M6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1Y3DT4H54M6S", + { + "PnY": "1", + "PnM": None, + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P2M3DT4H", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "P2M3DT54M", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": "54", + "TnS": None, + }, + ), + ( + "P2M3DT6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "P2M3DT4H54M", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": None, + }, + ), + ( + "P2M3DT4H6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": None, + "TnS": "6", + }, + ), + ( + "P2M3DT54M6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": None, + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P2M3DT4H54M6S", + { + "PnY": None, + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "PT4H54M6,5S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "PT4H54M6.5S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "PT4H", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "PT5M", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "5", + "TnS": None, + }, + ), + ( + "PT6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "PT1H2M", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "1", + "TnM": "2", + "TnS": None, + }, + ), + ( + "PT3H4S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "3", + "TnM": None, + "TnS": "4", + }, + ), + ( + "PT5M6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "5", + "TnS": "6", + }, + ), + ( + "PT0.0000001S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": None, + "TnS": "0.0000001", + }, + ), + ( + "PT2.0000048S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": None, + "TnS": "2.0000048", + }, + ), + ("P1Y", {"PnY": "1", "PnM": None, "PnW": None, "PnD": None}), + ("P1,5Y", {"PnY": "1.5", "PnM": None, "PnW": None, "PnD": None}), + ("P1.5Y", {"PnY": "1.5", "PnM": None, "PnW": None, "PnD": None}), + ("P1M", {"PnY": None, "PnM": "1", "PnW": None, "PnD": None}), + ("P1,5M", {"PnY": None, "PnM": "1.5", "PnW": None, "PnD": None}), + ("P1.5M", {"PnY": None, "PnM": "1.5", "PnW": None, "PnD": None}), + ("P1W", {"PnY": None, "PnM": None, "PnW": "1", "PnD": None}), + ("P1,5W", {"PnY": None, "PnM": None, "PnW": "1.5", "PnD": None}), + ("P1.5W", {"PnY": None, "PnM": None, "PnW": "1.5", "PnD": None}), + ("P1D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1"}), + ("P1,5D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1.5"}), + ("P1.5D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1.5"}), + ("P1Y2M3D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3"}), + ("P1Y2M3,5D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3.5"}), + ("P1Y2M3.5D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3.5"}), + ("P1Y2M", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": None}), + ( + "P0003-06-04T12:30:05", + { + "PnY": "0003", + "PnM": "06", + "PnD": "04", + "TnH": "12", + "TnM": "30", + "TnS": "05", + }, + ), + ( + "P0003-06-04T12:30:05.5", + { + "PnY": "0003", + "PnM": "06", + "PnD": "04", + "TnH": "12", + "TnM": "30", + "TnS": "05.5", + }, + ), + ( + "P0001-02-03T14:43:59.9999997", + { + "PnY": "0001", + "PnM": "02", + "PnD": "03", + "TnH": "14", + "TnM": "43", + "TnS": "59.9999997", + }, + ), + ) + + for testtuple in testtuples: + with mock.patch.object( + aniso8601.duration.PythonTimeBuilder, "build_duration" + ) as mockBuildDuration: + mockBuildDuration.return_value = testtuple[1] + + result = parse_duration(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + mockBuildDuration.assert_called_once_with(**testtuple[1]) + + def test_parse_duration_mockbuilder(self): + mockBuilder = mock.Mock() + + expectedargs = { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + } + + mockBuilder.build_duration.return_value = expectedargs + + result = parse_duration("P1Y2M3DT4H54M6S", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_duration.assert_called_once_with(**expectedargs) + + def test_parse_duration_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + parse_duration(testtuple, builder=None) + + def test_parse_duration_nop(self): + with self.assertRaises(ISOFormatError): + # Duration must start with a P + parse_duration("1Y2M3DT4H54M6S", builder=None) + + def test_parse_duration_weekcombination(self): + # Week designator cannot be combined with other time designators + # https://bitbucket.org/nielsenb/aniso8601/issues/2/week-designators-should-not-be-combinable + + with self.assertRaises(ISOFormatError): + parse_duration("P1Y2W", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1M2W", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P2W3D", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1Y2W3D", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1M2W3D", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1Y1M2W3D", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P7WT4H", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P7WT54M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P7WT6S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P7WT4H54M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P7WT4H6S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P7WT54M6S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P7WT4H54M6S", builder=None) + + def test_parse_duration_negative(self): + with self.assertRaises(ISOFormatError): + parse_duration("P-1Y", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P-2M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P-3D", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P-T4H", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P-T54M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P-T6S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P-7W", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P-1Y2M3DT4H54M6S", builder=None) + + def test_parse_duration_outoforder(self): + # Ensure durations are required to be in the correct order + # https://bitbucket.org/nielsenb/aniso8601/issues/7/durations-with-time-components-before-t + # https://bitbucket.org/nielsenb/aniso8601/issues/8/durations-with-components-in-wrong-order + with self.assertRaises(ISOFormatError): + parse_duration("P1S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1D1S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1H1M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("1Y2M3D1SPT1M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1Y2M3D2MT1S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P2M3D1ST1Y1M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1Y2M2MT3D1S", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("P1D1Y1M", builder=None) + + with self.assertRaises(ISOFormatError): + parse_duration("PT1S1H", builder=None) + + def test_parse_duration_badstr(self): + testtuples = ( + "PPPPPPPPPPPPPPPPPPPPPPPPPPPP", + "PTT", + "PX7DDDTX8888UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" + "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU8888888888888888H$H", + "P1Y2M3X.4D", + "P1Y2M3.4XD", + "P1Y2M3DT4H5M6XS", + "PT4H5M6X.2S", + "bad", + "", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_duration(testtuple, builder=None) + + def test_parse_duration_prescribed(self): + testtuples = ( + ( + "P1Y2M3DT4H54M6S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1Y2M3DT4H54M6,5S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "P1Y2M3DT4H54M6.5S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "PT4H54M6,5S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "PT4H54M6.5S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ("P1Y2M3D", {"PnY": "1", "PnM": "2", "" "PnW": None, "PnD": "3"}), + ("P1Y2M3,5D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3.5"}), + ("P1Y2M3.5D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3.5"}), + ("P1Y2M", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": None}), + ("P1Y", {"PnY": "1", "PnM": None, "PnW": None, "PnD": None}), + ("P1,5Y", {"PnY": "1.5", "PnM": None, "PnW": None, "PnD": None}), + ("P1.5Y", {"PnY": "1.5", "PnM": None, "PnW": None, "PnD": None}), + ("P1M", {"PnY": None, "PnM": "1", "PnW": None, "PnD": None}), + ("P1,5M", {"PnY": None, "PnM": "1.5", "PnW": None, "PnD": None}), + ("P1.5M", {"PnY": None, "PnM": "1.5", "PnW": None, "PnD": None}), + ("P1W", {"PnY": None, "PnM": None, "PnW": "1", "PnD": None}), + ("P1,5W", {"PnY": None, "PnM": None, "PnW": "1.5", "PnD": None}), + ("P1.5W", {"PnY": None, "PnM": None, "PnW": "1.5", "PnD": None}), + ("P1D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1"}), + ("P1,5D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1.5"}), + ("P1.5D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1.5"}), + ) + + for testtuple in testtuples: + result = _parse_duration_prescribed(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + + def test_parse_duration_prescribed_negative(self): + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-1Y") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-2M") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-3D") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-4W") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-1Y2M3D") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-T1H") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-T2M") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-T3S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P-1Y2M3DT4H54M6S") + + def test_parse_duration_prescribed_multiplefractions(self): + with self.assertRaises(ISOFormatError): + # Multiple fractions are not allowed + _parse_duration_prescribed("P1Y2M3DT4H5.1234M6.1234S") + + def test_parse_duration_prescribed_middlefraction(self): + with self.assertRaises(ISOFormatError): + # Fraction only allowed on final component + _parse_duration_prescribed("P1Y2M3DT4H5.1234M6S") + + def test_parse_duration_prescribed_suffixgarbage(self): + # Don't allow garbage after the duration + # https://bitbucket.org/nielsenb/aniso8601/issues/9/durations-with-trailing-garbage-are-parsed + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed("P1Dasdfasdf") + + def test_parse_duration_prescribed_notime(self): + testtuples = ( + ("P1Y2M3D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3"}), + ("P1Y2M3,5D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3.5"}), + ("P1Y2M3.5D", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": "3.5"}), + ("P1Y3D", {"PnY": "1", "PnM": None, "PnW": None, "PnD": "3"}), + ("P1Y2M", {"PnY": "1", "PnM": "2", "PnW": None, "PnD": None}), + ("P2M3D", {"PnY": None, "PnM": "2", "PnW": None, "PnD": "3"}), + ("P1Y", {"PnY": "1", "PnM": None, "PnW": None, "PnD": None}), + ("P1,5Y", {"PnY": "1.5", "PnM": None, "PnW": None, "PnD": None}), + ("P1.5Y", {"PnY": "1.5", "PnM": None, "PnW": None, "PnD": None}), + ("P1M", {"PnY": None, "PnM": "1", "PnW": None, "PnD": None}), + ("P1,5M", {"PnY": None, "PnM": "1.5", "PnW": None, "PnD": None}), + ("P1.5M", {"PnY": None, "PnM": "1.5", "PnW": None, "PnD": None}), + ("P1W", {"PnY": None, "PnM": None, "PnW": "1", "PnD": None}), + ("P1,5W", {"PnY": None, "PnM": None, "PnW": "1.5", "PnD": None}), + ("P1.5W", {"PnY": None, "PnM": None, "PnW": "1.5", "PnD": None}), + ("P1D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1"}), + ("P1,5D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1.5"}), + ("P1.5D", {"PnY": None, "PnM": None, "PnW": None, "PnD": "1.5"}), + ) + + for testtuple in testtuples: + result = _parse_duration_prescribed_notime(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + + def test_parse_duration_prescribed_notime_timepart(self): + # Ensure no time part is allowed + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1D1S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1H1M") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1Y2M3D4H") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1Y2M3D4H5S") + + def test_parse_duration_prescribed_notime_outoforder(self): + # Ensure durations are required to be in the correct order + # https://bitbucket.org/nielsenb/aniso8601/issues/8/durations-with-components-in-wrong-order + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1H1M") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1D1Y1M") + + def test_parse_duration_prescribed_notime_badstr(self): + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_notime("P1D1S") + + def test_parse_duration_prescribed_time(self): + testtuples = ( + ( + "P1Y2M3DT4H54M6S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + ), + ( + "P1Y2M3DT4H54M6,5S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "P1Y2M3DT4H54M6.5S", + { + "PnY": "1", + "PnM": "2", + "PnW": None, + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "PT4H54M6,5S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "PT4H54M6.5S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + ), + ( + "PT4H", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "4", + "TnM": None, + "TnS": None, + }, + ), + ( + "PT5M", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "5", + "TnS": None, + }, + ), + ( + "PT6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": None, + "TnS": "6", + }, + ), + ( + "PT1H2M", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "1", + "TnM": "2", + "TnS": None, + }, + ), + ( + "PT3H4S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": "3", + "TnM": None, + "TnS": "4", + }, + ), + ( + "PT5M6S", + { + "PnY": None, + "PnM": None, + "PnW": None, + "PnD": None, + "TnH": None, + "TnM": "5", + "TnS": "6", + }, + ), + ) + + for testtuple in testtuples: + result = _parse_duration_prescribed_time(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + + def test_parse_duration_prescribed_time_timeindate(self): + # Don't allow time components in date half + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P1Y2M3D4HT54M6S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P1Y2M3D6ST4H54M") + + def test_parse_duration_prescribed_time_dateintime(self): + # Don't allow date components in time half + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P2M3DT1Y4H54M6S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P1Y2MT3D4H54M6S") + + def test_parse_duration_prescribed_time_outoforder(self): + # Ensure durations are required to be in the correct order + # https://bitbucket.org/nielsenb/aniso8601/issues/7/durations-with-time-components-before-t + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("1Y2M3D1SPT1M") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P1Y2M3D2MT1S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P2M3D1ST1Y1M") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P1Y2M2MT3D1S") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("PT1S1H") + + def test_parse_duration_prescribed_time_badstr(self): + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P1Y") + + with self.assertRaises(ISOFormatError): + _parse_duration_prescribed_time("P1Y1M") + + def test_parse_duration_combined(self): + testtuples = ( + ( + "P0003-06-04T12:30:05", + { + "PnY": "0003", + "PnM": "06", + "PnD": "04", + "TnH": "12", + "TnM": "30", + "TnS": "05", + }, + ), + ( + "P0003-06-04T12:30:05,5", + { + "PnY": "0003", + "PnM": "06", + "PnD": "04", + "TnH": "12", + "TnM": "30", + "TnS": "05.5", + }, + ), + ( + "P0003-06-04T12:30:05.5", + { + "PnY": "0003", + "PnM": "06", + "PnD": "04", + "TnH": "12", + "TnM": "30", + "TnS": "05.5", + }, + ), + ( + "P0001-02-03T14:43:59.9999997", + { + "PnY": "0001", + "PnM": "02", + "PnD": "03", + "TnH": "14", + "TnM": "43", + "TnS": "59.9999997", + }, + ), + ) + + for testtuple in testtuples: + result = _parse_duration_combined(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + + def test_parse_duration_combined_suffixgarbage(self): + # Don't allow garbage after the duration + # https://bitbucket.org/nielsenb/aniso8601/issues/9/durations-with-trailing-garbage-are-parsed + with self.assertRaises(ISOFormatError): + _parse_duration_combined("P0003-06-04T12:30:05.5asdfasdf") + + def test_has_any_component(self): + self.assertTrue(_has_any_component("P1Y", ["Y", "M"])) + self.assertFalse(_has_any_component("P1Y", ["M", "D"])) diff --git a/libs/aniso8601/tests/test_init.py b/libs/aniso8601/tests/test_init.py new file mode 100644 index 000000000..d5604c6b9 --- /dev/null +++ b/libs/aniso8601/tests/test_init.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +import aniso8601 + + +class TestInitFunctions(unittest.TestCase): + def test_import(self): + # Verify the function mappings + self.assertEqual(aniso8601.parse_datetime, aniso8601.time.parse_datetime) + self.assertEqual(aniso8601.parse_time, aniso8601.time.parse_time) + self.assertEqual( + aniso8601.get_time_resolution, aniso8601.time.get_time_resolution + ) + self.assertEqual( + aniso8601.get_datetime_resolution, aniso8601.time.get_datetime_resolution + ) + + self.assertEqual(aniso8601.parse_date, aniso8601.date.parse_date) + self.assertEqual( + aniso8601.get_date_resolution, aniso8601.date.get_date_resolution + ) + + self.assertEqual(aniso8601.parse_duration, aniso8601.duration.parse_duration) + self.assertEqual( + aniso8601.get_duration_resolution, + aniso8601.duration.get_duration_resolution, + ) + + self.assertEqual(aniso8601.parse_interval, aniso8601.interval.parse_interval) + self.assertEqual( + aniso8601.parse_repeating_interval, + aniso8601.interval.parse_repeating_interval, + ) + self.assertEqual( + aniso8601.get_interval_resolution, + aniso8601.interval.get_interval_resolution, + ) + self.assertEqual( + aniso8601.get_repeating_interval_resolution, + aniso8601.interval.get_repeating_interval_resolution, + ) diff --git a/libs/aniso8601/tests/test_interval.py b/libs/aniso8601/tests/test_interval.py new file mode 100644 index 000000000..f01d15112 --- /dev/null +++ b/libs/aniso8601/tests/test_interval.py @@ -0,0 +1,1675 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +import aniso8601 +from aniso8601.builders import ( + DatetimeTuple, + DateTuple, + DurationTuple, + IntervalTuple, + TimeTuple, + TimezoneTuple, +) +from aniso8601.exceptions import ISOFormatError +from aniso8601.interval import ( + _get_interval_component_resolution, + _get_interval_resolution, + _parse_interval, + _parse_interval_end, + get_interval_resolution, + get_repeating_interval_resolution, + parse_interval, + parse_repeating_interval, +) +from aniso8601.resolution import IntervalResolution +from aniso8601.tests.compat import mock + + +class TestIntervalParser_UtilityFunctions(unittest.TestCase): + def test_get_interval_resolution(self): + self.assertEqual( + _get_interval_resolution( + IntervalTuple( + start=DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + end=DatetimeTuple( + DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + TimeTuple(hh="04", mm="05", ss="06", tz=None), + ), + duration=None, + ) + ), + IntervalResolution.Seconds, + ) + self.assertEqual( + _get_interval_resolution( + IntervalTuple( + start=DatetimeTuple( + DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + TimeTuple(hh="04", mm="05", ss="06", tz=None), + ), + end=DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + duration=None, + ) + ), + IntervalResolution.Seconds, + ) + + self.assertEqual( + _get_interval_resolution( + IntervalTuple( + start=DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + end=None, + duration=DurationTuple( + PnY="1", PnM="2", PnW=None, PnD="3", TnH="4", TnM="5", TnS="6" + ), + ) + ), + IntervalResolution.Seconds, + ) + self.assertEqual( + _get_interval_resolution( + IntervalTuple( + start=DatetimeTuple( + DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + TimeTuple(hh="04", mm="05", ss="06", tz=None), + ), + end=None, + duration=DurationTuple( + PnY="1", + PnM="2", + PnW=None, + PnD="3", + TnH=None, + TnM=None, + TnS=None, + ), + ) + ), + IntervalResolution.Seconds, + ) + + self.assertEqual( + _get_interval_resolution( + IntervalTuple( + start=None, + end=DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + duration=DurationTuple( + PnY="1", PnM="2", PnW=None, PnD="3", TnH="4", TnM="5", TnS="6" + ), + ) + ), + IntervalResolution.Seconds, + ) + self.assertEqual( + _get_interval_resolution( + IntervalTuple( + start=None, + end=DatetimeTuple( + DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + TimeTuple(hh="04", mm="05", ss="06", tz=None), + ), + duration=DurationTuple( + PnY="1", + PnM="2", + PnW=None, + PnD="3", + TnH=None, + TnM=None, + TnS=None, + ), + ) + ), + IntervalResolution.Seconds, + ) + + def test_get_interval_component_resolution(self): + self.assertEqual( + _get_interval_component_resolution( + DateTuple(YYYY="2001", MM=None, DD=None, Www=None, D=None, DDD="123") + ), + IntervalResolution.Ordinal, + ) + self.assertEqual( + _get_interval_component_resolution( + DateTuple(YYYY="2001", MM=None, DD=None, Www="12", D="3", DDD=None) + ), + IntervalResolution.Weekday, + ) + self.assertEqual( + _get_interval_component_resolution( + DateTuple(YYYY="2001", MM=None, DD=None, Www="12", D=None, DDD=None) + ), + IntervalResolution.Week, + ) + self.assertEqual( + _get_interval_component_resolution( + DateTuple(YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None) + ), + IntervalResolution.Day, + ) + self.assertEqual( + _get_interval_component_resolution( + DateTuple(YYYY="2001", MM="02", DD=None, Www=None, D=None, DDD=None) + ), + IntervalResolution.Month, + ) + self.assertEqual( + _get_interval_component_resolution( + DateTuple(YYYY="2001", MM=None, DD=None, Www=None, D=None, DDD=None) + ), + IntervalResolution.Year, + ) + + self.assertEqual( + _get_interval_component_resolution( + DatetimeTuple( + DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + TimeTuple(hh="04", mm="05", ss="06", tz=None), + ) + ), + IntervalResolution.Seconds, + ) + self.assertEqual( + _get_interval_component_resolution( + DatetimeTuple( + DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + TimeTuple(hh="04", mm="05", ss=None, tz=None), + ) + ), + IntervalResolution.Minutes, + ) + self.assertEqual( + _get_interval_component_resolution( + DatetimeTuple( + DateTuple( + YYYY="2001", MM="02", DD="03", Www=None, D=None, DDD=None + ), + TimeTuple(hh="04", mm=None, ss=None, tz=None), + ) + ), + IntervalResolution.Hours, + ) + + self.assertEqual( + _get_interval_component_resolution( + DurationTuple( + PnY="1", PnM="2", PnW=None, PnD="3", TnH="4", TnM="5", TnS="6" + ) + ), + IntervalResolution.Seconds, + ) + self.assertEqual( + _get_interval_component_resolution( + DurationTuple( + PnY="1", PnM="2", PnW=None, PnD="3", TnH="4", TnM="5", TnS=None + ) + ), + IntervalResolution.Minutes, + ) + self.assertEqual( + _get_interval_component_resolution( + DurationTuple( + PnY="1", PnM="2", PnW=None, PnD="3", TnH="4", TnM=None, TnS=None + ) + ), + IntervalResolution.Hours, + ) + self.assertEqual( + _get_interval_component_resolution( + DurationTuple( + PnY="1", PnM="2", PnW=None, PnD="3", TnH=None, TnM=None, TnS=None + ) + ), + IntervalResolution.Day, + ) + self.assertEqual( + _get_interval_component_resolution( + DurationTuple( + PnY="1", PnM="2", PnW=None, PnD=None, TnH=None, TnM=None, TnS=None + ) + ), + IntervalResolution.Month, + ) + self.assertEqual( + _get_interval_component_resolution( + DurationTuple( + PnY="1", PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None + ) + ), + IntervalResolution.Year, + ) + self.assertEqual( + _get_interval_component_resolution( + DurationTuple( + PnY=None, PnM=None, PnW="3", PnD=None, TnH=None, TnM=None, TnS=None + ) + ), + IntervalResolution.Week, + ) + + +class TestIntervalParserFunctions(unittest.TestCase): + def test_get_interval_resolution_date(self): + self.assertEqual(get_interval_resolution("P1.5Y/2018"), IntervalResolution.Year) + self.assertEqual( + get_interval_resolution("P1.5Y/2018-03"), IntervalResolution.Month + ) + self.assertEqual( + get_interval_resolution("P1.5Y/2018-03-06"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("P1.5Y/2018W01"), IntervalResolution.Week + ) + self.assertEqual( + get_interval_resolution("P1.5Y/2018-306"), IntervalResolution.Ordinal + ) + self.assertEqual( + get_interval_resolution("P1.5Y/2018W012"), IntervalResolution.Weekday + ) + + self.assertEqual(get_interval_resolution("2018/P1.5Y"), IntervalResolution.Year) + self.assertEqual( + get_interval_resolution("2018-03/P1.5Y"), IntervalResolution.Month + ) + self.assertEqual( + get_interval_resolution("2018-03-06/P1.5Y"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("2018W01/P1.5Y"), IntervalResolution.Week + ) + self.assertEqual( + get_interval_resolution("2018-306/P1.5Y"), IntervalResolution.Ordinal + ) + self.assertEqual( + get_interval_resolution("2018W012/P1.5Y"), IntervalResolution.Weekday + ) + + def test_get_interval_resolution_time(self): + self.assertEqual( + get_interval_resolution("P1M/1981-04-05T01"), IntervalResolution.Hours + ) + self.assertEqual( + get_interval_resolution("P1M/1981-04-05T01:01"), IntervalResolution.Minutes + ) + self.assertEqual( + get_interval_resolution("P1M/1981-04-05T01:01:00"), + IntervalResolution.Seconds, + ) + + self.assertEqual( + get_interval_resolution("1981-04-05T01/P1M"), IntervalResolution.Hours + ) + self.assertEqual( + get_interval_resolution("1981-04-05T01:01/P1M"), IntervalResolution.Minutes + ) + self.assertEqual( + get_interval_resolution("1981-04-05T01:01:00/P1M"), + IntervalResolution.Seconds, + ) + + def test_get_interval_resolution_duration(self): + self.assertEqual( + get_interval_resolution("2014-11-12/P1Y2M3D"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("2014-11-12/P1Y2M"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("2014-11-12/P1Y"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("2014-11-12/P1W"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("2014-11-12/P1Y2M3DT4H"), IntervalResolution.Hours + ) + self.assertEqual( + get_interval_resolution("2014-11-12/P1Y2M3DT4H54M"), + IntervalResolution.Minutes, + ) + self.assertEqual( + get_interval_resolution("2014-11-12/P1Y2M3DT4H54M6S"), + IntervalResolution.Seconds, + ) + + self.assertEqual( + get_interval_resolution("P1Y2M3D/2014-11-12"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("P1Y2M/2014-11-12"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("P1Y/2014-11-12"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("P1W/2014-11-12"), IntervalResolution.Day + ) + self.assertEqual( + get_interval_resolution("P1Y2M3DT4H/2014-11-12"), IntervalResolution.Hours + ) + self.assertEqual( + get_interval_resolution("P1Y2M3DT4H54M/2014-11-12"), + IntervalResolution.Minutes, + ) + self.assertEqual( + get_interval_resolution("P1Y2M3DT4H54M6S/2014-11-12"), + IntervalResolution.Seconds, + ) + + def test_parse_interval(self): + testtuples = ( + ( + "P1M/1981-04-05T01:01:00", + { + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "duration": DurationTuple(None, "1", None, None, None, None, None), + }, + ), + ( + "P1M/1981-04-05", + { + "end": DateTuple("1981", "04", "05", None, None, None), + "duration": DurationTuple(None, "1", None, None, None, None, None), + }, + ), + ( + "P1,5Y/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + "1.5", None, None, None, None, None, None + ), + }, + ), + ( + "P1.5Y/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + "1.5", None, None, None, None, None, None + ), + }, + ), + ( + "PT1H/2014-11-12", + { + "end": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "1", None, None), + }, + ), + ( + "PT4H54M6.5S/2014-11-12", + { + "end": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), + }, + ), + ( + "PT10H/2050-03-01T13:00:00Z", + { + "end": DatetimeTuple( + DateTuple("2050", "03", "01", None, None, None), + TimeTuple( + "13", + "00", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "duration": DurationTuple(None, None, None, None, "10", None, None), + }, + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + "PT0.0000001S/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "0.0000001" + ), + }, + ), + ( + "PT2.0000048S/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "2.0000048" + ), + }, + ), + ( + "1981-04-05T01:01:00/P1M1DT1M", + { + "start": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "duration": DurationTuple(None, "1", None, "1", None, "1", None), + }, + ), + ( + "1981-04-05/P1M1D", + { + "start": DateTuple("1981", "04", "05", None, None, None), + "duration": DurationTuple(None, "1", None, "1", None, None, None), + }, + ), + ( + "2018-03-06/P2,5M", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, "2.5", None, None, None, None, None + ), + }, + ), + ( + "2018-03-06/P2.5M", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, "2.5", None, None, None, None, None + ), + }, + ), + ( + "2014-11-12/PT1H", + { + "start": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "1", None, None), + }, + ), + ( + "2014-11-12/PT4H54M6.5S", + { + "start": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), + }, + ), + ( + "2050-03-01T13:00:00Z/PT10H", + { + "start": DatetimeTuple( + DateTuple("2050", "03", "01", None, None, None), + TimeTuple( + "13", + "00", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "duration": DurationTuple(None, None, None, None, "10", None, None), + }, + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + "2018-03-06/PT0.0000001S", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "0.0000001" + ), + }, + ), + ( + "2018-03-06/PT2.0000048S", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "2.0000048" + ), + }, + ), + ( + "1980-03-05T01:01:00/1981-04-05T01:01:00", + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + }, + ), + ( + "1980-03-05T01:01:00/1981-04-05", + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DateTuple("1981", "04", "05", None, None, None), + }, + ), + ( + "1980-03-05/1981-04-05T01:01:00", + { + "start": DateTuple("1980", "03", "05", None, None, None), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + }, + ), + ( + "1980-03-05/1981-04-05", + { + "start": DateTuple("1980", "03", "05", None, None, None), + "end": DateTuple("1981", "04", "05", None, None, None), + }, + ), + ( + "1981-04-05/1980-03-05", + { + "start": DateTuple("1981", "04", "05", None, None, None), + "end": DateTuple("1980", "03", "05", None, None, None), + }, + ), + ( + "2050-03-01T13:00:00Z/2050-05-11T15:30:00Z", + { + "start": DatetimeTuple( + DateTuple("2050", "03", "01", None, None, None), + TimeTuple( + "13", + "00", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "end": DatetimeTuple( + DateTuple("2050", "05", "11", None, None, None), + TimeTuple( + "15", + "30", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + }, + ), + # Test concise interval + ( + "2020-01-01/02", + { + "start": DateTuple("2020", "01", "01", None, None, None), + "end": DateTuple(None, None, "02", None, None, None), + }, + ), + ( + "2008-02-15/03-14", + { + "start": DateTuple("2008", "02", "15", None, None, None), + "end": DateTuple(None, "03", "14", None, None, None), + }, + ), + ( + "2007-12-14T13:30/15:30", + { + "start": DatetimeTuple( + DateTuple("2007", "12", "14", None, None, None), + TimeTuple("13", "30", None, None), + ), + "end": TimeTuple("15", "30", None, None), + }, + ), + ( + "2007-11-13T09:00/15T17:00", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("09", "00", None, None), + ), + "end": DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + }, + ), + ( + "2007-11-13T00:00/16T00:00", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + "end": DatetimeTuple( + DateTuple(None, None, "16", None, None, None), + TimeTuple("00", "00", None, None), + ), + }, + ), + ( + "2007-11-13T09:00Z/15T17:00", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple( + "09", + "00", + None, + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "end": DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + }, + ), + ( + "2007-11-13T00:00/12:34.567", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + "end": TimeTuple("12", "34.567", None, None), + }, + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + "1980-03-05T01:01:00.0000001/" "1981-04-05T14:43:59.9999997", + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00.0000001", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("14", "43", "59.9999997", None), + ), + }, + ), + ) + + for testtuple in testtuples: + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_interval" + ) as mockBuildInterval: + mockBuildInterval.return_value = testtuple[1] + + result = parse_interval(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + mockBuildInterval.assert_called_once_with(**testtuple[1]) + + # Test different separators + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_interval" + ) as mockBuildInterval: + expectedargs = { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + } + + mockBuildInterval.return_value = expectedargs + + result = parse_interval( + "1980-03-05T01:01:00--1981-04-05T01:01:00", intervaldelimiter="--" + ) + + self.assertEqual(result, expectedargs) + mockBuildInterval.assert_called_once_with(**expectedargs) + + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_interval" + ) as mockBuildInterval: + expectedargs = { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + } + + mockBuildInterval.return_value = expectedargs + + result = parse_interval( + "1980-03-05 01:01:00/1981-04-05 01:01:00", datetimedelimiter=" " + ) + + self.assertEqual(result, expectedargs) + mockBuildInterval.assert_called_once_with(**expectedargs) + + def test_parse_interval_mockbuilder(self): + mockBuilder = mock.Mock() + + expectedargs = { + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "duration": DurationTuple(None, "1", None, None, None, None, None), + } + + mockBuilder.build_interval.return_value = expectedargs + + result = parse_interval("P1M/1981-04-05T01:01:00", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_interval.assert_called_once_with(**expectedargs) + + mockBuilder = mock.Mock() + + expectedargs = { + "start": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "1", None, None), + } + + mockBuilder.build_interval.return_value = expectedargs + + result = parse_interval("2014-11-12/PT1H", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_interval.assert_called_once_with(**expectedargs) + + mockBuilder = mock.Mock() + + expectedargs = { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + } + + mockBuilder.build_interval.return_value = expectedargs + + result = parse_interval( + "1980-03-05T01:01:00/1981-04-05T01:01:00", builder=mockBuilder + ) + + self.assertEqual(result, expectedargs) + mockBuilder.build_interval.assert_called_once_with(**expectedargs) + + def test_parse_interval_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + parse_interval(testtuple, builder=None) + + def test_parse_interval_baddelimiter(self): + testtuples = ( + "1980-03-05T01:01:00,1981-04-05T01:01:00", + "P1M 1981-04-05T01:01:00", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_interval(testtuple, builder=None) + + def test_parse_interval_badstr(self): + testtuples = ("/", "0/0/0", "20.50230/0", "5/%", "1/21", "bad", "") + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_interval(testtuple, builder=None) + + def test_parse_interval_repeating(self): + # Parse interval can't parse repeating intervals + with self.assertRaises(ISOFormatError): + parse_interval("R3/1981-04-05/P1D") + + with self.assertRaises(ISOFormatError): + parse_interval("R3/1981-04-05/P0003-06-04T12:30:05.5") + + with self.assertRaises(ISOFormatError): + parse_interval("R/PT1H2M/1980-03-05T01:01:00") + + def test_parse_interval_suffixgarbage(self): + # Don't allow garbage after the duration + # https://bitbucket.org/nielsenb/aniso8601/issues/9/durations-with-trailing-garbage-are-parsed + with self.assertRaises(ISOFormatError): + parse_interval("2001/P1Dasdf", builder=None) + + with self.assertRaises(ISOFormatError): + parse_interval("P1Dasdf/2001", builder=None) + + with self.assertRaises(ISOFormatError): + parse_interval("2001/P0003-06-04T12:30:05.5asdfasdf", builder=None) + + with self.assertRaises(ISOFormatError): + parse_interval("P0003-06-04T12:30:05.5asdfasdf/2001", builder=None) + + def test_parse_interval_internal(self): + # Test the internal _parse_interval function + testtuples = ( + ( + "P1M/1981-04-05T01:01:00", + { + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "duration": DurationTuple(None, "1", None, None, None, None, None), + }, + ), + ( + "P1M/1981-04-05", + { + "end": DateTuple("1981", "04", "05", None, None, None), + "duration": DurationTuple(None, "1", None, None, None, None, None), + }, + ), + ( + "P1,5Y/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + "1.5", None, None, None, None, None, None + ), + }, + ), + ( + "P1.5Y/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + "1.5", None, None, None, None, None, None + ), + }, + ), + ( + "PT1H/2014-11-12", + { + "end": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "1", None, None), + }, + ), + ( + "PT4H54M6.5S/2014-11-12", + { + "end": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), + }, + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + "PT0.0000001S/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "0.0000001" + ), + }, + ), + ( + "PT2.0000048S/2018-03-06", + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "2.0000048" + ), + }, + ), + ( + "1981-04-05T01:01:00/P1M1DT1M", + { + "start": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "duration": DurationTuple(None, "1", None, "1", None, "1", None), + }, + ), + ( + "1981-04-05/P1M1D", + { + "start": DateTuple("1981", "04", "05", None, None, None), + "duration": DurationTuple(None, "1", None, "1", None, None, None), + }, + ), + ( + "2018-03-06/P2,5M", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, "2.5", None, None, None, None, None + ), + }, + ), + ( + "2018-03-06/P2.5M", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, "2.5", None, None, None, None, None + ), + }, + ), + ( + "2014-11-12/PT1H", + { + "start": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "1", None, None), + }, + ), + ( + "2014-11-12/PT4H54M6.5S", + { + "start": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), + }, + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + "2018-03-06/PT0.0000001S", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "0.0000001" + ), + }, + ), + ( + "2018-03-06/PT2.0000048S", + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "2.0000048" + ), + }, + ), + ( + "1980-03-05T01:01:00/1981-04-05T01:01:00", + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + }, + ), + ( + "1980-03-05T01:01:00/1981-04-05", + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DateTuple("1981", "04", "05", None, None, None), + }, + ), + ( + "1980-03-05/1981-04-05T01:01:00", + { + "start": DateTuple("1980", "03", "05", None, None, None), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + }, + ), + ( + "1980-03-05/1981-04-05", + { + "start": DateTuple("1980", "03", "05", None, None, None), + "end": DateTuple("1981", "04", "05", None, None, None), + }, + ), + ( + "1981-04-05/1980-03-05", + { + "start": DateTuple("1981", "04", "05", None, None, None), + "end": DateTuple("1980", "03", "05", None, None, None), + }, + ), + # Test concise interval + ( + "2020-01-01/02", + { + "start": DateTuple("2020", "01", "01", None, None, None), + "end": DateTuple(None, None, "02", None, None, None), + }, + ), + ( + "2008-02-15/03-14", + { + "start": DateTuple("2008", "02", "15", None, None, None), + "end": DateTuple(None, "03", "14", None, None, None), + }, + ), + ( + "2007-12-14T13:30/15:30", + { + "start": DatetimeTuple( + DateTuple("2007", "12", "14", None, None, None), + TimeTuple("13", "30", None, None), + ), + "end": TimeTuple("15", "30", None, None), + }, + ), + ( + "2007-11-13T09:00/15T17:00", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("09", "00", None, None), + ), + "end": DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + }, + ), + ( + "2007-11-13T00:00/16T00:00", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + "end": DatetimeTuple( + DateTuple(None, None, "16", None, None, None), + TimeTuple("00", "00", None, None), + ), + }, + ), + ( + "2007-11-13T09:00Z/15T17:00", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple( + "09", + "00", + None, + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "end": DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + }, + ), + ( + "2007-11-13T00:00/12:34.567", + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + "end": TimeTuple("12", "34.567", None, None), + }, + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + "1980-03-05T01:01:00.0000001/" "1981-04-05T14:43:59.9999997", + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00.0000001", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("14", "43", "59.9999997", None), + ), + }, + ), + ) + + for testtuple in testtuples: + mockBuilder = mock.Mock() + mockBuilder.build_interval.return_value = testtuple[1] + + result = _parse_interval(testtuple[0], mockBuilder) + + self.assertEqual(result, testtuple[1]) + mockBuilder.build_interval.assert_called_once_with(**testtuple[1]) + + # Test different separators + expectedargs = { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + } + + mockBuilder = mock.Mock() + mockBuilder.build_interval.return_value = expectedargs + + result = _parse_interval( + "1980-03-05T01:01:00--1981-04-05T01:01:00", + mockBuilder, + intervaldelimiter="--", + ) + + self.assertEqual(result, expectedargs) + mockBuilder.build_interval.assert_called_once_with(**expectedargs) + + expectedargs = { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + } + + mockBuilder = mock.Mock() + mockBuilder.build_interval.return_value = expectedargs + + _parse_interval( + "1980-03-05 01:01:00/1981-04-05 01:01:00", + mockBuilder, + datetimedelimiter=" ", + ) + + self.assertEqual(result, expectedargs) + mockBuilder.build_interval.assert_called_once_with(**expectedargs) + + def test_parse_interval_end(self): + self.assertEqual( + _parse_interval_end( + "02", DateTuple("2020", "01", "01", None, None, None), "T" + ), + DateTuple(None, None, "02", None, None, None), + ) + + self.assertEqual( + _parse_interval_end( + "03-14", DateTuple("2008", "02", "15", None, None, None), "T" + ), + DateTuple(None, "03", "14", None, None, None), + ) + + self.assertEqual( + _parse_interval_end( + "0314", DateTuple("2008", "02", "15", None, None, None), "T" + ), + DateTuple(None, "03", "14", None, None, None), + ) + + self.assertEqual( + _parse_interval_end( + "15:30", + DatetimeTuple( + DateTuple("2007", "12", "14", None, None, None), + TimeTuple("13", "30", None, None), + ), + "T", + ), + TimeTuple("15", "30", None, None), + ) + + self.assertEqual( + _parse_interval_end( + "15T17:00", + DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("09", "00", None, None), + ), + "T", + ), + DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + ) + + self.assertEqual( + _parse_interval_end( + "16T00:00", + DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + "T", + ), + DatetimeTuple( + DateTuple(None, None, "16", None, None, None), + TimeTuple("00", "00", None, None), + ), + ) + + self.assertEqual( + _parse_interval_end( + "15 17:00", + DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("09", "00", None, None), + ), + " ", + ), + DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + ) + + self.assertEqual( + _parse_interval_end( + "12:34.567", + DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + "T", + ), + TimeTuple("12", "34.567", None, None), + ) + + +class TestRepeatingIntervalParserFunctions(unittest.TestCase): + def test_get_interval_resolution_date(self): + self.assertEqual( + get_repeating_interval_resolution("R/P1.5Y/2018"), IntervalResolution.Year + ) + self.assertEqual( + get_repeating_interval_resolution("R1/P1.5Y/2018-03"), + IntervalResolution.Month, + ) + self.assertEqual( + get_repeating_interval_resolution("R2/P1.5Y/2018-03-06"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R3/P1.5Y/2018W01"), + IntervalResolution.Week, + ) + self.assertEqual( + get_repeating_interval_resolution("R4/P1.5Y/2018-306"), + IntervalResolution.Ordinal, + ) + self.assertEqual( + get_repeating_interval_resolution("R5/P1.5Y/2018W012"), + IntervalResolution.Weekday, + ) + + self.assertEqual( + get_repeating_interval_resolution("R/2018/P1.5Y"), IntervalResolution.Year + ) + self.assertEqual( + get_repeating_interval_resolution("R1/2018-03/P1.5Y"), + IntervalResolution.Month, + ) + self.assertEqual( + get_repeating_interval_resolution("R2/2018-03-06/P1.5Y"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R3/2018W01/P1.5Y"), + IntervalResolution.Week, + ) + self.assertEqual( + get_repeating_interval_resolution("R4/2018-306/P1.5Y"), + IntervalResolution.Ordinal, + ) + self.assertEqual( + get_repeating_interval_resolution("R5/2018W012/P1.5Y"), + IntervalResolution.Weekday, + ) + + def test_get_interval_resolution_time(self): + self.assertEqual( + get_repeating_interval_resolution("R/P1M/1981-04-05T01"), + IntervalResolution.Hours, + ) + self.assertEqual( + get_repeating_interval_resolution("R1/P1M/1981-04-05T01:01"), + IntervalResolution.Minutes, + ) + self.assertEqual( + get_repeating_interval_resolution("R2/P1M/1981-04-05T01:01:00"), + IntervalResolution.Seconds, + ) + + self.assertEqual( + get_repeating_interval_resolution("R/1981-04-05T01/P1M"), + IntervalResolution.Hours, + ) + self.assertEqual( + get_repeating_interval_resolution("R1/1981-04-05T01:01/P1M"), + IntervalResolution.Minutes, + ) + self.assertEqual( + get_repeating_interval_resolution("R2/1981-04-05T01:01:00/P1M"), + IntervalResolution.Seconds, + ) + + def test_get_interval_resolution_duration(self): + self.assertEqual( + get_repeating_interval_resolution("R/2014-11-12/P1Y2M3D"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R1/2014-11-12/P1Y2M"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R2/2014-11-12/P1Y"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R3/2014-11-12/P1W"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R4/2014-11-12/P1Y2M3DT4H"), + IntervalResolution.Hours, + ) + self.assertEqual( + get_repeating_interval_resolution("R5/2014-11-12/P1Y2M3DT4H54M"), + IntervalResolution.Minutes, + ) + self.assertEqual( + get_repeating_interval_resolution("R6/2014-11-12/P1Y2M3DT4H54M6S"), + IntervalResolution.Seconds, + ) + + self.assertEqual( + get_repeating_interval_resolution("R/P1Y2M3D/2014-11-12"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R1/P1Y2M/2014-11-12"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R2/P1Y/2014-11-12"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R3/P1W/2014-11-12"), + IntervalResolution.Day, + ) + self.assertEqual( + get_repeating_interval_resolution("R4/P1Y2M3DT4H/2014-11-12"), + IntervalResolution.Hours, + ) + self.assertEqual( + get_repeating_interval_resolution("R5/P1Y2M3DT4H54M/2014-11-12"), + IntervalResolution.Minutes, + ) + self.assertEqual( + get_repeating_interval_resolution("R6/P1Y2M3DT4H54M6S/2014-11-12"), + IntervalResolution.Seconds, + ) + + def test_parse_repeating_interval(self): + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_repeating_interval" + ) as mockBuilder: + expectedargs = { + "R": False, + "Rnn": "3", + "interval": IntervalTuple( + DateTuple("1981", "04", "05", None, None, None), + None, + DurationTuple(None, None, None, "1", None, None, None), + ), + } + + mockBuilder.return_value = expectedargs + + result = parse_repeating_interval("R3/1981-04-05/P1D") + + self.assertEqual(result, expectedargs) + mockBuilder.assert_called_once_with(**expectedargs) + + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_repeating_interval" + ) as mockBuilder: + expectedargs = { + "R": False, + "Rnn": "11", + "interval": IntervalTuple( + None, + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DurationTuple(None, None, None, None, "1", "2", None), + ), + } + + mockBuilder.return_value = expectedargs + + result = parse_repeating_interval("R11/PT1H2M/1980-03-05T01:01:00") + + self.assertEqual(result, expectedargs) + mockBuilder.assert_called_once_with(**expectedargs) + + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_repeating_interval" + ) as mockBuilder: + expectedargs = { + "R": False, + "Rnn": "2", + "interval": IntervalTuple( + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + None, + ), + } + + mockBuilder.return_value = expectedargs + + result = parse_repeating_interval( + "R2--1980-03-05T01:01:00--" "1981-04-05T01:01:00", + intervaldelimiter="--", + ) + + self.assertEqual(result, expectedargs) + mockBuilder.assert_called_once_with(**expectedargs) + + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_repeating_interval" + ) as mockBuilder: + expectedargs = { + "R": False, + "Rnn": "2", + "interval": IntervalTuple( + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + None, + ), + } + + mockBuilder.return_value = expectedargs + + result = parse_repeating_interval( + "R2/" "1980-03-05 01:01:00/" "1981-04-05 01:01:00", + datetimedelimiter=" ", + ) + + self.assertEqual(result, expectedargs) + mockBuilder.assert_called_once_with(**expectedargs) + + with mock.patch.object( + aniso8601.interval.PythonTimeBuilder, "build_repeating_interval" + ) as mockBuilder: + expectedargs = { + "R": True, + "Rnn": None, + "interval": IntervalTuple( + None, + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DurationTuple(None, None, None, None, "1", "2", None), + ), + } + + mockBuilder.return_value = expectedargs + + result = parse_repeating_interval("R/PT1H2M/1980-03-05T01:01:00") + + self.assertEqual(result, expectedargs) + mockBuilder.assert_called_once_with(**expectedargs) + + def test_parse_repeating_interval_mockbuilder(self): + mockBuilder = mock.Mock() + + args = { + "R": False, + "Rnn": "3", + "interval": IntervalTuple( + DateTuple("1981", "04", "05", None, None, None), + None, + DurationTuple(None, None, None, "1", None, None, None), + ), + } + + mockBuilder.build_repeating_interval.return_value = args + + result = parse_repeating_interval("R3/1981-04-05/P1D", builder=mockBuilder) + + self.assertEqual(result, args) + mockBuilder.build_repeating_interval.assert_called_once_with(**args) + + mockBuilder = mock.Mock() + + args = { + "R": False, + "Rnn": "11", + "interval": IntervalTuple( + None, + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DurationTuple(None, None, None, None, "1", "2", None), + ), + } + + mockBuilder.build_repeating_interval.return_value = args + + result = parse_repeating_interval( + "R11/PT1H2M/1980-03-05T01:01:00", builder=mockBuilder + ) + + self.assertEqual(result, args) + mockBuilder.build_repeating_interval.assert_called_once_with(**args) + + mockBuilder = mock.Mock() + + args = { + "R": True, + "Rnn": None, + "interval": IntervalTuple( + None, + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DurationTuple(None, None, None, None, "1", "2", None), + ), + } + + mockBuilder.build_repeating_interval.return_value = args + + result = parse_repeating_interval( + "R/PT1H2M/1980-03-05T01:01:00", builder=mockBuilder + ) + + self.assertEqual(result, args) + mockBuilder.build_repeating_interval.assert_called_once_with(**args) + + def test_parse_repeating_interval_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + parse_repeating_interval(testtuple, builder=None) + + def test_parse_repeating_interval_baddelimiter(self): + testtuples = ("R,PT1H2M,1980-03-05T01:01:00", "R3 1981-04-05 P1D") + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_repeating_interval(testtuple, builder=None) + + def test_parse_repeating_interval_suffixgarbage(self): + # Don't allow garbage after the duration + # https://bitbucket.org/nielsenb/aniso8601/issues/9/durations-with-trailing-garbage-are-parsed + with self.assertRaises(ISOFormatError): + parse_repeating_interval("R3/1981-04-05/P1Dasdf", builder=None) + + with self.assertRaises(ISOFormatError): + parse_repeating_interval( + "R3/" "1981-04-05/" "P0003-06-04T12:30:05.5asdfasdf", builder=None + ) + + def test_parse_repeating_interval_badstr(self): + testtuples = ("bad", "") + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_repeating_interval(testtuple, builder=None) diff --git a/libs/aniso8601/tests/test_time.py b/libs/aniso8601/tests/test_time.py new file mode 100644 index 000000000..dcee5e03f --- /dev/null +++ b/libs/aniso8601/tests/test_time.py @@ -0,0 +1,539 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +import aniso8601 +from aniso8601.builders import DatetimeTuple, DateTuple, TimeTuple, TimezoneTuple +from aniso8601.exceptions import ISOFormatError +from aniso8601.resolution import TimeResolution +from aniso8601.tests.compat import mock +from aniso8601.time import ( + _get_time_resolution, + get_datetime_resolution, + get_time_resolution, + parse_datetime, + parse_time, +) + + +class TestTimeResolutionFunctions(unittest.TestCase): + def test_get_time_resolution(self): + self.assertEqual(get_time_resolution("01:23:45"), TimeResolution.Seconds) + self.assertEqual(get_time_resolution("24:00:00"), TimeResolution.Seconds) + self.assertEqual(get_time_resolution("23:21:28,512400"), TimeResolution.Seconds) + self.assertEqual(get_time_resolution("23:21:28.512400"), TimeResolution.Seconds) + self.assertEqual(get_time_resolution("01:23"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("24:00"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("01:23,4567"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("01:23.4567"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("012345"), TimeResolution.Seconds) + self.assertEqual(get_time_resolution("240000"), TimeResolution.Seconds) + self.assertEqual(get_time_resolution("0123"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("2400"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("01"), TimeResolution.Hours) + self.assertEqual(get_time_resolution("24"), TimeResolution.Hours) + self.assertEqual(get_time_resolution("12,5"), TimeResolution.Hours) + self.assertEqual(get_time_resolution("12.5"), TimeResolution.Hours) + self.assertEqual( + get_time_resolution("232128.512400+00:00"), TimeResolution.Seconds + ) + self.assertEqual(get_time_resolution("0123.4567+00:00"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("01.4567+00:00"), TimeResolution.Hours) + self.assertEqual(get_time_resolution("01:23:45+00:00"), TimeResolution.Seconds) + self.assertEqual(get_time_resolution("24:00:00+00:00"), TimeResolution.Seconds) + self.assertEqual( + get_time_resolution("23:21:28.512400+00:00"), TimeResolution.Seconds + ) + self.assertEqual(get_time_resolution("01:23+00:00"), TimeResolution.Minutes) + self.assertEqual(get_time_resolution("24:00+00:00"), TimeResolution.Minutes) + self.assertEqual( + get_time_resolution("01:23.4567+00:00"), TimeResolution.Minutes + ) + self.assertEqual( + get_time_resolution("23:21:28.512400+11:15"), TimeResolution.Seconds + ) + self.assertEqual( + get_time_resolution("23:21:28.512400-12:34"), TimeResolution.Seconds + ) + self.assertEqual( + get_time_resolution("23:21:28.512400Z"), TimeResolution.Seconds + ) + self.assertEqual( + get_time_resolution("06:14:00.000123Z"), TimeResolution.Seconds + ) + + def test_get_datetime_resolution(self): + self.assertEqual( + get_datetime_resolution("2019-06-05T01:03:11.858714"), + TimeResolution.Seconds, + ) + self.assertEqual( + get_datetime_resolution("2019-06-05T01:03:11"), TimeResolution.Seconds + ) + self.assertEqual( + get_datetime_resolution("2019-06-05T01:03"), TimeResolution.Minutes + ) + self.assertEqual(get_datetime_resolution("2019-06-05T01"), TimeResolution.Hours) + + def test_get_time_resolution_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + get_time_resolution(testtuple) + + def test_get_time_resolution_badstr(self): + testtuples = ("A6:14:00.000123Z", "06:14:0B", "bad", "") + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + get_time_resolution(testtuple) + + def test_get_time_resolution_internal(self): + self.assertEqual( + _get_time_resolution(TimeTuple(hh="01", mm="02", ss="03", tz=None)), + TimeResolution.Seconds, + ) + self.assertEqual( + _get_time_resolution(TimeTuple(hh="01", mm="02", ss=None, tz=None)), + TimeResolution.Minutes, + ) + self.assertEqual( + _get_time_resolution(TimeTuple(hh="01", mm=None, ss=None, tz=None)), + TimeResolution.Hours, + ) + + +class TestTimeParserFunctions(unittest.TestCase): + def test_parse_time(self): + testtuples = ( + ("01:23:45", {"hh": "01", "mm": "23", "ss": "45", "tz": None}), + ("24:00:00", {"hh": "24", "mm": "00", "ss": "00", "tz": None}), + ( + "23:21:28,512400", + {"hh": "23", "mm": "21", "ss": "28.512400", "tz": None}, + ), + ( + "23:21:28.512400", + {"hh": "23", "mm": "21", "ss": "28.512400", "tz": None}, + ), + ( + "01:03:11.858714", + {"hh": "01", "mm": "03", "ss": "11.858714", "tz": None}, + ), + ( + "14:43:59.9999997", + {"hh": "14", "mm": "43", "ss": "59.9999997", "tz": None}, + ), + ("01:23", {"hh": "01", "mm": "23", "ss": None, "tz": None}), + ("24:00", {"hh": "24", "mm": "00", "ss": None, "tz": None}), + ("01:23,4567", {"hh": "01", "mm": "23.4567", "ss": None, "tz": None}), + ("01:23.4567", {"hh": "01", "mm": "23.4567", "ss": None, "tz": None}), + ("012345", {"hh": "01", "mm": "23", "ss": "45", "tz": None}), + ("240000", {"hh": "24", "mm": "00", "ss": "00", "tz": None}), + ("232128,512400", {"hh": "23", "mm": "21", "ss": "28.512400", "tz": None}), + ("232128.512400", {"hh": "23", "mm": "21", "ss": "28.512400", "tz": None}), + ("010311.858714", {"hh": "01", "mm": "03", "ss": "11.858714", "tz": None}), + ( + "144359.9999997", + {"hh": "14", "mm": "43", "ss": "59.9999997", "tz": None}, + ), + ("0123", {"hh": "01", "mm": "23", "ss": None, "tz": None}), + ("2400", {"hh": "24", "mm": "00", "ss": None, "tz": None}), + ("01", {"hh": "01", "mm": None, "ss": None, "tz": None}), + ("24", {"hh": "24", "mm": None, "ss": None, "tz": None}), + ("12,5", {"hh": "12.5", "mm": None, "ss": None, "tz": None}), + ("12.5", {"hh": "12.5", "mm": None, "ss": None, "tz": None}), + ( + "232128,512400+00:00", + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "232128.512400+00:00", + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "0123,4567+00:00", + { + "hh": "01", + "mm": "23.4567", + "ss": None, + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "0123.4567+00:00", + { + "hh": "01", + "mm": "23.4567", + "ss": None, + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "01,4567+00:00", + { + "hh": "01.4567", + "mm": None, + "ss": None, + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "01.4567+00:00", + { + "hh": "01.4567", + "mm": None, + "ss": None, + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "01:23:45+00:00", + { + "hh": "01", + "mm": "23", + "ss": "45", + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "24:00:00+00:00", + { + "hh": "24", + "mm": "00", + "ss": "00", + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "23:21:28.512400+00:00", + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "01:23+00:00", + { + "hh": "01", + "mm": "23", + "ss": None, + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "24:00+00:00", + { + "hh": "24", + "mm": "00", + "ss": None, + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "01:23.4567+00:00", + { + "hh": "01", + "mm": "23.4567", + "ss": None, + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + ), + ( + "23:21:28.512400+11:15", + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "11", "15", "+11:15"), + }, + ), + ( + "23:21:28.512400-12:34", + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(True, None, "12", "34", "-12:34"), + }, + ), + ( + "23:21:28.512400Z", + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, True, None, None, "Z"), + }, + ), + ( + "06:14:00.000123Z", + { + "hh": "06", + "mm": "14", + "ss": "00.000123", + "tz": TimezoneTuple(False, True, None, None, "Z"), + }, + ), + ) + + for testtuple in testtuples: + with mock.patch.object( + aniso8601.time.PythonTimeBuilder, "build_time" + ) as mockBuildTime: + + mockBuildTime.return_value = testtuple[1] + + result = parse_time(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + mockBuildTime.assert_called_once_with(**testtuple[1]) + + def test_parse_time_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + parse_time(testtuple, builder=None) + + def test_parse_time_badstr(self): + testtuples = ( + "A6:14:00.000123Z", + "06:14:0B", + "06:1 :02", + "0000,70:24,9", + "00.27:5332", + "bad", + "", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_time(testtuple, builder=None) + + def test_parse_time_mockbuilder(self): + mockBuilder = mock.Mock() + + expectedargs = {"hh": "01", "mm": "23", "ss": "45", "tz": None} + + mockBuilder.build_time.return_value = expectedargs + + result = parse_time("01:23:45", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_time.assert_called_once_with(**expectedargs) + + mockBuilder = mock.Mock() + + expectedargs = { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + } + + mockBuilder.build_time.return_value = expectedargs + + result = parse_time("232128.512400+00:00", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_time.assert_called_once_with(**expectedargs) + + mockBuilder = mock.Mock() + + expectedargs = { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "11", "15", "+11:15"), + } + + mockBuilder.build_time.return_value = expectedargs + + result = parse_time("23:21:28.512400+11:15", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_time.assert_called_once_with(**expectedargs) + + def test_parse_datetime(self): + testtuples = ( + ( + "2019-06-05T01:03:11,858714", + ( + DateTuple("2019", "06", "05", None, None, None), + TimeTuple("01", "03", "11.858714", None), + ), + ), + ( + "2019-06-05T01:03:11.858714", + ( + DateTuple("2019", "06", "05", None, None, None), + TimeTuple("01", "03", "11.858714", None), + ), + ), + ( + "1981-04-05T23:21:28.512400Z", + ( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple( + "23", + "21", + "28.512400", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + ), + ( + "1981095T23:21:28.512400-12:34", + ( + DateTuple("1981", None, None, None, None, "095"), + TimeTuple( + "23", + "21", + "28.512400", + TimezoneTuple(True, None, "12", "34", "-12:34"), + ), + ), + ), + ( + "19810405T23:21:28+00", + ( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple( + "23", "21", "28", TimezoneTuple(False, None, "00", None, "+00") + ), + ), + ), + ( + "19810405T23:21:28+00:00", + ( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple( + "23", + "21", + "28", + TimezoneTuple(False, None, "00", "00", "+00:00"), + ), + ), + ), + ) + + for testtuple in testtuples: + with mock.patch.object( + aniso8601.time.PythonTimeBuilder, "build_datetime" + ) as mockBuildDateTime: + + mockBuildDateTime.return_value = testtuple[1] + + result = parse_datetime(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + mockBuildDateTime.assert_called_once_with(*testtuple[1]) + + def test_parse_datetime_spacedelimited(self): + expectedargs = ( + DateTuple("2004", None, None, "53", "6", None), + TimeTuple( + "23", "21", "28.512400", TimezoneTuple(True, None, "12", "34", "-12:34") + ), + ) + + with mock.patch.object( + aniso8601.time.PythonTimeBuilder, "build_datetime" + ) as mockBuildDateTime: + + mockBuildDateTime.return_value = expectedargs + + result = parse_datetime("2004-W53-6 23:21:28.512400-12:34", delimiter=" ") + + self.assertEqual(result, expectedargs) + mockBuildDateTime.assert_called_once_with(*expectedargs) + + def test_parse_datetime_commadelimited(self): + expectedargs = ( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple( + "23", "21", "28.512400", TimezoneTuple(False, True, None, None, "Z") + ), + ) + + with mock.patch.object( + aniso8601.time.PythonTimeBuilder, "build_datetime" + ) as mockBuildDateTime: + + mockBuildDateTime.return_value = expectedargs + + result = parse_datetime("1981-04-05,23:21:28,512400Z", delimiter=",") + + self.assertEqual(result, expectedargs) + mockBuildDateTime.assert_called_once_with(*expectedargs) + + def test_parse_datetime_baddelimiter(self): + testtuples = ( + "1981-04-05,23:21:28,512400Z", + "2004-W53-6 23:21:28.512400-12:3", + "1981040523:21:28", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_datetime(testtuple, builder=None) + + def test_parse_datetime_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + parse_datetime(testtuple, builder=None) + + def test_parse_datetime_badstr(self): + testtuples = ( + "1981-04-05TA6:14:00.000123Z", + "2004-W53-6T06:14:0B", + "2014-01-230T23:21:28+00", + "201401230T01:03:11.858714", + "9999 W53T49", + "9T0000,70:24,9", + "bad", + "", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_datetime(testtuple, builder=None) + + def test_parse_datetime_mockbuilder(self): + mockBuilder = mock.Mock() + + expectedargs = ( + DateTuple("1981", None, None, None, None, "095"), + TimeTuple( + "23", "21", "28.512400", TimezoneTuple(True, None, "12", "34", "-12:34") + ), + ) + + mockBuilder.build_datetime.return_value = expectedargs + + result = parse_datetime("1981095T23:21:28.512400-12:34", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_datetime.assert_called_once_with(*expectedargs) diff --git a/libs/aniso8601/tests/test_timezone.py b/libs/aniso8601/tests/test_timezone.py new file mode 100644 index 000000000..5df9671f1 --- /dev/null +++ b/libs/aniso8601/tests/test_timezone.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +import aniso8601 +from aniso8601.exceptions import ISOFormatError +from aniso8601.tests.compat import mock +from aniso8601.timezone import parse_timezone + + +class TestTimezoneParserFunctions(unittest.TestCase): + def test_parse_timezone(self): + testtuples = ( + ("Z", {"negative": False, "Z": True, "name": "Z"}), + ("+00:00", {"negative": False, "hh": "00", "mm": "00", "name": "+00:00"}), + ("+01:00", {"negative": False, "hh": "01", "mm": "00", "name": "+01:00"}), + ("-01:00", {"negative": True, "hh": "01", "mm": "00", "name": "-01:00"}), + ("+00:12", {"negative": False, "hh": "00", "mm": "12", "name": "+00:12"}), + ("+01:23", {"negative": False, "hh": "01", "mm": "23", "name": "+01:23"}), + ("-01:23", {"negative": True, "hh": "01", "mm": "23", "name": "-01:23"}), + ("+0000", {"negative": False, "hh": "00", "mm": "00", "name": "+0000"}), + ("+0100", {"negative": False, "hh": "01", "mm": "00", "name": "+0100"}), + ("-0100", {"negative": True, "hh": "01", "mm": "00", "name": "-0100"}), + ("+0012", {"negative": False, "hh": "00", "mm": "12", "name": "+0012"}), + ("+0123", {"negative": False, "hh": "01", "mm": "23", "name": "+0123"}), + ("-0123", {"negative": True, "hh": "01", "mm": "23", "name": "-0123"}), + ("+00", {"negative": False, "hh": "00", "mm": None, "name": "+00"}), + ("+01", {"negative": False, "hh": "01", "mm": None, "name": "+01"}), + ("-01", {"negative": True, "hh": "01", "mm": None, "name": "-01"}), + ("+12", {"negative": False, "hh": "12", "mm": None, "name": "+12"}), + ("-12", {"negative": True, "hh": "12", "mm": None, "name": "-12"}), + ) + + for testtuple in testtuples: + with mock.patch.object( + aniso8601.timezone.PythonTimeBuilder, "build_timezone" + ) as mockBuildTimezone: + + mockBuildTimezone.return_value = testtuple[1] + + result = parse_timezone(testtuple[0]) + + self.assertEqual(result, testtuple[1]) + mockBuildTimezone.assert_called_once_with(**testtuple[1]) + + def test_parse_timezone_badtype(self): + testtuples = (None, 1, False, 1.234) + + for testtuple in testtuples: + with self.assertRaises(ValueError): + parse_timezone(testtuple, builder=None) + + def test_parse_timezone_badstr(self): + testtuples = ( + "+1", + "-00", + "-0000", + "-00:00", + "01", + "0123", + "@12:34", + "Y", + " Z", + "Z ", + " Z ", + "bad", + "", + ) + + for testtuple in testtuples: + with self.assertRaises(ISOFormatError): + parse_timezone(testtuple, builder=None) + + def test_parse_timezone_mockbuilder(self): + mockBuilder = mock.Mock() + + expectedargs = {"negative": False, "Z": True, "name": "Z"} + + mockBuilder.build_timezone.return_value = expectedargs + + result = parse_timezone("Z", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_timezone.assert_called_once_with(**expectedargs) + + mockBuilder = mock.Mock() + + expectedargs = {"negative": False, "hh": "00", "mm": "00", "name": "+00:00"} + + mockBuilder.build_timezone.return_value = expectedargs + + result = parse_timezone("+00:00", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_timezone.assert_called_once_with(**expectedargs) + + mockBuilder = mock.Mock() + + expectedargs = {"negative": True, "hh": "01", "mm": "23", "name": "-01:23"} + + mockBuilder.build_timezone.return_value = expectedargs + + result = parse_timezone("-01:23", builder=mockBuilder) + + self.assertEqual(result, expectedargs) + mockBuilder.build_timezone.assert_called_once_with(**expectedargs) + + def test_parse_timezone_negativezero(self): + # A 0 offset cannot be negative + with self.assertRaises(ISOFormatError): + parse_timezone("-00:00", builder=None) + + with self.assertRaises(ISOFormatError): + parse_timezone("-0000", builder=None) + + with self.assertRaises(ISOFormatError): + parse_timezone("-00", builder=None) diff --git a/libs/aniso8601/tests/test_utcoffset.py b/libs/aniso8601/tests/test_utcoffset.py new file mode 100644 index 000000000..11fa4f7d9 --- /dev/null +++ b/libs/aniso8601/tests/test_utcoffset.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import datetime +import pickle +import unittest + +from aniso8601.utcoffset import UTCOffset + + +class TestUTCOffset(unittest.TestCase): + def test_pickle(self): + # Make sure timezone objects are pickleable + testutcoffset = UTCOffset(name="UTC", minutes=0) + + utcoffsetpickle = pickle.dumps(testutcoffset) + + resultutcoffset = pickle.loads(utcoffsetpickle) + + self.assertEqual(resultutcoffset._name, testutcoffset._name) + self.assertEqual(resultutcoffset._utcdelta, testutcoffset._utcdelta) + + def test_repr(self): + self.assertEqual(str(UTCOffset(minutes=0)), "+0:00:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=60)), "+1:00:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=-60)), "-1:00:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=12)), "+0:12:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=-12)), "-0:12:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=83)), "+1:23:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=-83)), "-1:23:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=1440)), "+1 day, 0:00:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=-1440)), "-1 day, 0:00:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=2967)), "+2 days, 1:27:00 UTC") + + self.assertEqual(str(UTCOffset(minutes=-2967)), "-2 days, 1:27:00 UTC") + + def test_dst(self): + tzinfoobject = UTCOffset(minutes=240) + # This would raise ISOFormatError or a TypeError if dst info is invalid + result = datetime.datetime.now(tzinfoobject) + # Hacky way to make sure the tzinfo is what we'd expect + self.assertEqual(result.tzinfo.utcoffset(None), datetime.timedelta(hours=4)) |