# -*- 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"]))