summaryrefslogtreecommitdiffhomepage
path: root/libs/aniso8601/tests
diff options
context:
space:
mode:
Diffstat (limited to 'libs/aniso8601/tests')
-rw-r--r--libs/aniso8601/tests/__init__.py7
-rw-r--r--libs/aniso8601/tests/compat.py16
-rw-r--r--libs/aniso8601/tests/test_compat.py27
-rw-r--r--libs/aniso8601/tests/test_date.py303
-rw-r--r--libs/aniso8601/tests/test_decimalfraction.py19
-rw-r--r--libs/aniso8601/tests/test_duration.py1402
-rw-r--r--libs/aniso8601/tests/test_init.py49
-rw-r--r--libs/aniso8601/tests/test_interval.py1675
-rw-r--r--libs/aniso8601/tests/test_time.py539
-rw-r--r--libs/aniso8601/tests/test_timezone.py123
-rw-r--r--libs/aniso8601/tests/test_utcoffset.py56
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))