# -*- coding: utf-8 -*-

# Copyright (c) 2025, 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"]))
