Return `since` and `until` date time tuple from string representations of
    time_range, since, until and time_shift.

    This functiom supports both reading the keys separately (from `since` and
    `until`), as well as the new `time_range` key. Valid formats are:

        - ISO 8601
        - X days/years/hours/day/year/weeks
        - X days/years/hours/day/year/weeks ago
        - X days/years/hours/day/year/weeks from now
        - freeform

    Additionally, for `time_range` (these specify both `since` and `until`):

        - Last day
        - Last week
        - Last month
        - Last quarter
        - Last year
        - No filter
        - Last X seconds/minutes/hours/days/weeks/months/years
        - Next X seconds/minutes/hours/days/weeks/months/years

def get_since_until(time_range: Optional[str] = None,
                    since: Optional[str] = None,
                    until: Optional[str] = None,
                    time_shift: Optional[str] = None,
                    relative_end: Optional[str] = None) -> Tuple[datetime, datetime]:
    """Return `since` and `until` date time tuple from string representations of
    time_range, since, until and time_shift.

    This functiom supports both reading the keys separately (from `since` and
    `until`), as well as the new `time_range` key. Valid formats are:

        - ISO 8601
        - X days/years/hours/day/year/weeks
        - X days/years/hours/day/year/weeks ago
        - X days/years/hours/day/year/weeks from now
        - freeform

    Additionally, for `time_range` (these specify both `since` and `until`):

        - Last day
        - Last week
        - Last month
        - Last quarter
        - Last year
        - No filter
        - Last X seconds/minutes/hours/days/weeks/months/years
        - Next X seconds/minutes/hours/days/weeks/months/years

    """
    separator = ' : '
    relative_end = parse_human_datetime(relative_end if relative_end else 'today')
    common_time_frames = {
        'Last day': (relative_end - relativedelta(days=1), relative_end),  # noqa: T400
        'Last week': (relative_end - relativedelta(weeks=1), relative_end),  # noqa: T400
        'Last month': (relative_end - relativedelta(months=1), relative_end),  # noqa: E501, T400
        'Last quarter': (relative_end - relativedelta(months=3), relative_end),  # noqa: E501, T400
        'Last year': (relative_end - relativedelta(years=1), relative_end),  # noqa: T400
    }

    if time_range:
        if separator in time_range:
            since, until = time_range.split(separator, 1)
            if since and since not in common_time_frames:
                since = add_ago_to_since(since)
            since = parse_human_datetime(since)
            until = parse_human_datetime(until)
        elif time_range in common_time_frames:
            since, until = common_time_frames[time_range]
        elif time_range == 'No filter':
            since = until = None
        else:
            rel, num, grain = time_range.split()
            if rel == 'Last':
                since = relative_end - relativedelta(**{grain: int(num)})  # noqa: T400
                until = relative_end
            else:  # rel == 'Next'
                since = relative_end
                until = relative_end + relativedelta(**{grain: int(num)})  # noqa: T400
    else:
        since = since or ''
        if since:
            since = add_ago_to_since(since)
        since = parse_human_datetime(since)
        until = parse_human_datetime(until) if until else relative_end

    if time_shift:
        time_shift = parse_human_timedelta(time_shift)
        since = since if since is None else (since - time_shift)  # noqa: T400
        until = until if until is None else (until - time_shift)  # noqa: T400

    if since and until and since > until:
        raise ValueError(_('From date cannot be larger than to date'))

    return since, until