Parses duration/period stamp expressed in a subset of ISO8601 duration
    specification formats and returns a dictionary of time components (as
    integers). Accepted formats are:
    
     * ``PnYnMnDTnHnMnS``
     
       Note: n is positive integer! Floats are NOT supported. Any component (nX)
       is optional, but if any time component is specified, ``T`` spearator is
       mandatory. Also, ``P`` is always mandatory.
       
     * ``PnW``
     
       Note: n is positive integer representing number of weeks.
       
     * ``P<date>T<time>``
     
       Note: This format is basically standard ISO8601 timestamp format, without
       the time zone and with ``P`` prepended. We support both basic and
       extended format, which translates into following two subformats:
       
        - ``PYYYYMMDDThhmmss`` for basic, and
        - ``PYYYY-MM-DDThh:mm:ss`` for extended format.
       
       Note that all subfields are mandatory. 
    
    Note: whitespaces are ignored.
    
    Examples::
    
        from datetime import datetime
        from dateutil.relativedelta import relativedelta
        
        rel = parse_duration('P1m')     # +1 month
        rel = parse_duration('P 1y 1m T 2m 1s')
        rel = parse_duration('P12w')    # +12 weeks
        rel = parse_duration('P 0001-02-03 T 03:02:01')
        rel = parse_duration('P00010203T030201')
        
        future = datetime.now() + relativedelta(**rel)
    
    Returns: dictionary with (some of the) fields: ``years``, ``months``,
    ``weeks``, ``days``, ``hours``, ``minutes`` or ``seconds``. If nothing is
    matched, an empty dict is returned.

def parse_duration(string):
    '''
    Parses duration/period stamp expressed in a subset of ISO8601 duration
    specification formats and returns a dictionary of time components (as
    integers). Accepted formats are:
    
     * ``PnYnMnDTnHnMnS``
     
       Note: n is positive integer! Floats are NOT supported. Any component (nX)
       is optional, but if any time component is specified, ``T`` spearator is
       mandatory. Also, ``P`` is always mandatory.
       
     * ``PnW``
     
       Note: n is positive integer representing number of weeks.
       
     * ``P<date>T<time>``
     
       Note: This format is basically standard ISO8601 timestamp format, without
       the time zone and with ``P`` prepended. We support both basic and
       extended format, which translates into following two subformats:
       
        - ``PYYYYMMDDThhmmss`` for basic, and
        - ``PYYYY-MM-DDThh:mm:ss`` for extended format.
       
       Note that all subfields are mandatory. 
    
    Note: whitespaces are ignored.
    
    Examples::
    
        from datetime import datetime
        from dateutil.relativedelta import relativedelta
        
        rel = parse_duration('P1m')     # +1 month
        rel = parse_duration('P 1y 1m T 2m 1s')
        rel = parse_duration('P12w')    # +12 weeks
        rel = parse_duration('P 0001-02-03 T 03:02:01')
        rel = parse_duration('P00010203T030201')
        
        future = datetime.now() + relativedelta(**rel)
    
    Returns: dictionary with (some of the) fields: ``years``, ``months``,
    ``weeks``, ``days``, ``hours``, ``minutes`` or ``seconds``. If nothing is
    matched, an empty dict is returned.
    '''

    string = string.replace(' ', '').upper()
    
    # try `PnYnMnDTnHnMnS` form
    match = re.match(
        "^P(?:(?:(?P<years>\d+)Y)?(?:(?P<months>\d+)M)?(?:(?P<days>\d+)D)?)?" \
        "(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?$",
        string
    )
    if match:
        d = match.groupdict(0)
        return dict(zip(d.keys(), map(int, d.values())))
    
    # try `PnW` form
    match = re.match(
        "^P(?P<weeks>\d+)W$",
        string
    )
    if match:
        d = match.groupdict(0)
        return dict(zip(d.keys(), map(int, d.values())))
    
    # try `P<date>T<time>` form, subforms `PYYYYMMDDThhmmss` and `PYYYY-MM-DDThh:mm:ss`
    match = re.match(
        "^P(?P<years>\d{4})(-)?(?P<months>\d{2})(?(2)-)(?P<days>\d{2})T(?P<hours>\d{2})(?(2):)(?P<minutes>\d{2})(?(2):)(?P<seconds>\d{2})$",
        string
    )
    if match:
        d = match.groupdict(0)
        return dict(zip(d.keys(), map(int, d.values())))
    
    return {}