Apply a `relativedelta` to a `struct_time` data structure.

    `op` is an operator function, probably always `add` or `sub`tract to
    correspond to `a_date + a_delta` and `a_date - a_delta`.

    This function is required because we cannot use standard `datetime` module
    objects for conversion when the date/time is, or will become, outside the
    boundary years 1 AD to 9999 AD.

def apply_delta(op, time_struct, delta):
    """
    Apply a `relativedelta` to a `struct_time` data structure.

    `op` is an operator function, probably always `add` or `sub`tract to
    correspond to `a_date + a_delta` and `a_date - a_delta`.

    This function is required because we cannot use standard `datetime` module
    objects for conversion when the date/time is, or will become, outside the
    boundary years 1 AD to 9999 AD.
    """
    if not delta:
        return time_struct  # No work to do

    try:
        dt_result = op(datetime(*time_struct[:6]), delta)
        return dt_to_struct_time(dt_result)
    except (OverflowError, ValueError):
        # Year is not within supported 1 to 9999 AD range
        pass

    # Here we fake the year to one in the acceptable range to avoid having to
    # write our own date rolling logic

    # Adjust the year to be close to the 2000 millenium in 1,000 year
    # increments to try and retain accurate relative leap years
    actual_year = time_struct.tm_year
    millenium = int(float(actual_year) / 1000)
    millenium_diff = (2 - millenium) * 1000
    adjusted_year = actual_year + millenium_diff
    # Apply delta to the date/time with adjusted year
    dt = datetime(*(adjusted_year,) + time_struct[1:6])
    dt_result = op(dt, delta)
    # Convert result year back to its original millenium
    final_year = dt_result.year - millenium_diff
    return struct_time(
        (final_year,) + dt_result.timetuple()[1:6] + tuple(TIME_EMPTY_EXTRAS))