def parse_iso8601(text):
    """
    ISO 8601 compliant parser.

    :param text: The string to parse
    :type text: str

    :rtype: datetime.datetime or datetime.time or datetime.date
    """
    parsed = _parse_iso8601_duration(text)
    if parsed is not None:
        return parsed

    m = ISO8601_DT.match(text)
    if not m:
        raise ParserError("Invalid ISO 8601 string")

    ambiguous_date = False
    is_date = False
    is_time = False
    year = 0
    month = 1
    day = 1
    minute = 0
    second = 0
    microsecond = 0
    tzinfo = None

    if m:
        if m.group("date"):
            # A date has been specified
            is_date = True

            if m.group("isocalendar"):
                # We have a ISO 8601 string defined
                # by week number
                if (
                    m.group("weeksep")
                    and not m.group("weekdaysep")
                    and m.group("isoweekday")
                ):
                    raise ParserError("Invalid date string: {}".format(text))

                if not m.group("weeksep") and m.group("weekdaysep"):
                    raise ParserError("Invalid date string: {}".format(text))

                try:
                    date = _get_iso_8601_week(
                        m.group("isoyear"), m.group("isoweek"), m.group("isoweekday")
                    )
                except ParserError:
                    raise
                except ValueError:
                    raise ParserError("Invalid date string: {}".format(text))

                year = date["year"]
                month = date["month"]
                day = date["day"]
            else:
                # We have a classic date representation
                year = int(m.group("year"))

                if not m.group("monthday"):
                    # No month and day
                    month = 1
                    day = 1
                else:
                    if m.group("month") and m.group("day"):
                        # Month and day
                        if not m.group("daysep") and len(m.group("day")) == 1:
                            # Ordinal day
                            ordinal = int(m.group("month") + m.group("day"))
                            leap = is_leap(year)
                            months_offsets = MONTHS_OFFSETS[leap]

                            if ordinal > months_offsets[13]:
                                raise ParserError("Ordinal day is out of range")

                            for i in range(1, 14):
                                if ordinal <= months_offsets[i]:
                                    day = ordinal - months_offsets[i - 1]
                                    month = i - 1

                                    break
                        else:
                            month = int(m.group("month"))
                            day = int(m.group("day"))
                    else:
                        # Only month
                        if not m.group("monthsep"):
                            # The date looks like 201207
                            # which is invalid for a date
                            # But it might be a time in the form hhmmss
                            ambiguous_date = True

                        month = int(m.group("month"))
                        day = 1

        if not m.group("time"):
            # No time has been specified
            if ambiguous_date:
                # We can "safely" assume that the ambiguous date
                # was actually a time in the form hhmmss
                hhmmss = "{}{:0>2}".format(str(year), str(month))

                return datetime.time(int(hhmmss[:2]), int(hhmmss[2:4]), int(hhmmss[4:]))

            return datetime.date(year, month, day)

        if ambiguous_date:
            raise ParserError("Invalid date string: {}".format(text))

        if is_date and not m.group("timesep"):
            raise ParserError("Invalid date string: {}".format(text))

        if not is_date:
            is_time = True

        # Grabbing hh:mm:ss
        hour = int(m.group("hour"))
        minsep = m.group("minsep")

        if m.group("minute"):
            minute = int(m.group("minute"))
        elif minsep:
            raise ParserError("Invalid ISO 8601 time part")

        secsep = m.group("secsep")
        if secsep and not minsep and m.group("minute"):
            # minute/second separator but no hour/minute separator
            raise ParserError("Invalid ISO 8601 time part")

        if m.group("second"):
            if not secsep and minsep:
                # No minute/second separator but hour/minute separator
                raise ParserError("Invalid ISO 8601 time part")

            second = int(m.group("second"))
        elif secsep:
            raise ParserError("Invalid ISO 8601 time part")

        # Grabbing subseconds, if any
        if m.group("subsecondsection"):
            # Limiting to 6 chars
            subsecond = m.group("subsecond")[:6]

            microsecond = int("{:0<6}".format(subsecond))

        # Grabbing timezone, if any
        tz = m.group("tz")
        if tz:
            if tz == "Z":
                offset = 0
            else:
                negative = True if tz.startswith("-") else False
                tz = tz[1:]
                if ":" not in tz:
                    if len(tz) == 2:
                        tz = "{}00".format(tz)

                    off_hour = tz[0:2]
                    off_minute = tz[2:4]
                else:
                    off_hour, off_minute = tz.split(":")

                offset = ((int(off_hour) * 60) + int(off_minute)) * 60

                if negative:
                    offset = -1 * offset

            tzinfo = FixedTimezone(offset)

        if is_time:
            return datetime.time(hour, minute, second, microsecond)

        return datetime.datetime(
            year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
        )
ISO 8601 compliant parser.

    :param text: The string to parse
    :type text: str

    :rtype: datetime.datetime or datetime.time or datetime.date