def find_next_time_expression_time(now: dt.datetime, seconds: List[int], minutes: List[int], hours: List[int]) -> dt.datetime:
    'Find the next datetime from now for which the time expression matches.\n\n    The algorithm looks at each time unit separately and tries to find the\n    next one that matches for each. If any of them would roll over, all\n    time units below that are reset to the first matching value.\n\n    Timezones are also handled (the tzinfo of the now object is used),\n    including daylight saving time.\n    '
    if ((not seconds) or (not minutes) or (not hours)):
        raise ValueError('Cannot find a next time: Time expression never matches!')

    def _lower_bound(arr: List[int], cmp: int) -> Optional[int]:
        'Return the first value in arr greater or equal to cmp.\n\n        Return None if no such value exists.\n        '
        left = 0
        right = len(arr)
        while (left < right):
            mid = ((left + right) // 2)
            if (arr[mid] < cmp):
                left = (mid + 1)
            else:
                right = mid
        if (left == len(arr)):
            return None
        return arr[left]
    result = now.replace(microsecond=0)
    next_second = _lower_bound(seconds, result.second)
    if (next_second is None):
        next_second = seconds[0]
        result += dt.timedelta(minutes=1)
    result = result.replace(second=next_second)
    next_minute = _lower_bound(minutes, result.minute)
    if (next_minute != result.minute):
        result = result.replace(second=seconds[0])
    if (next_minute is None):
        next_minute = minutes[0]
        result += dt.timedelta(hours=1)
    result = result.replace(minute=next_minute)
    next_hour = _lower_bound(hours, result.hour)
    if (next_hour != result.hour):
        result = result.replace(second=seconds[0], minute=minutes[0])
    if (next_hour is None):
        next_hour = hours[0]
        result += dt.timedelta(days=1)
    result = result.replace(hour=next_hour)
    if (result.tzinfo is None):
        return result
    tzinfo: pytzinfo.DstTzInfo = result.tzinfo
    result = result.replace(tzinfo=None)
    try:
        result = tzinfo.localize(result, is_dst=None)
    except pytzexceptions.AmbiguousTimeError:
        use_dst = bool(now.dst())
        result = tzinfo.localize(result, is_dst=use_dst)
    except pytzexceptions.NonExistentTimeError:
        result = (result.replace(tzinfo=tzinfo) + dt.timedelta(seconds=1))
        return find_next_time_expression_time(result, seconds, minutes, hours)
    result_dst = cast(dt.timedelta, result.dst())
    now_dst = cast(dt.timedelta, now.dst())
    if (result_dst >= now_dst):
        return result
    try:
        tzinfo.localize(now.replace(tzinfo=None), is_dst=None)
        return result
    except pytzexceptions.AmbiguousTimeError:
        pass
    check = (now - now_dst)
    check_result = find_next_time_expression_time(check, seconds, minutes, hours)
    try:
        tzinfo.localize(check_result.replace(tzinfo=None), is_dst=None)
        return result
    except pytzexceptions.AmbiguousTimeError:
        pass
    check_result = tzinfo.localize(check_result.replace(tzinfo=None), is_dst=False)
    return check_result
Find the next datetime from now for which the time expression matches.

The algorithm looks at each time unit separately and tries to find the
next one that matches for each. If any of them would roll over, all
time units below that are reset to the first matching value.

Timezones are also handled (the tzinfo of the now object is used),
including daylight saving time.
def find_next_time_expression_time(now: dt.datetime, seconds: List[int], minutes: List[int], hours: List[int]) -> dt.datetime:
    'Find the next datetime from now for which the time expression matches.\n\n    The algorithm looks at each time unit separately and tries to find the\n    next one that matches for each. If any of them would roll over, all\n    time units below that are reset to the first matching value.\n\n    Timezones are also handled (the tzinfo of the now object is used),\n    including daylight saving time.\n    '
    if ((not seconds) or (not minutes) or (not hours)):
        raise ValueError('Cannot find a next time: Time expression never matches!')

    def _lower_bound(arr: List[int], cmp: int) -> Optional[int]:
        'Return the first value in arr greater or equal to cmp.\n\n        Return None if no such value exists.\n        '
        left = 0
        right = len(arr)
        while (left < right):
            mid = ((left + right) // 2)
            if (arr[mid] < cmp):
                left = (mid + 1)
            else:
                right = mid
        if (left == len(arr)):
            return None
        return arr[left]
    result = now.replace(microsecond=0)
    next_second = _lower_bound(seconds, result.second)
    if (next_second is None):
        next_second = seconds[0]
        result += dt.timedelta(minutes=1)
    result = result.replace(second=next_second)
    next_minute = _lower_bound(minutes, result.minute)
    if (next_minute != result.minute):
        result = result.replace(second=seconds[0])
    if (next_minute is None):
        next_minute = minutes[0]
        result += dt.timedelta(hours=1)
    result = result.replace(minute=next_minute)
    next_hour = _lower_bound(hours, result.hour)
    if (next_hour != result.hour):
        result = result.replace(second=seconds[0], minute=minutes[0])
    if (next_hour is None):
        next_hour = hours[0]
        result += dt.timedelta(days=1)
    result = result.replace(hour=next_hour)
    if (result.tzinfo is None):
        return result
    tzinfo: pytzinfo.DstTzInfo = result.tzinfo
    result = result.replace(tzinfo=None)
    try:
        result = tzinfo.localize(result, is_dst=None)
    except pytzexceptions.AmbiguousTimeError:
        use_dst = bool(now.dst())
        result = tzinfo.localize(result, is_dst=use_dst)
    except pytzexceptions.NonExistentTimeError:
        result = (result.replace(tzinfo=tzinfo) + dt.timedelta(seconds=1))
        return find_next_time_expression_time(result, seconds, minutes, hours)
    result_dst = cast(dt.timedelta, result.dst())
    now_dst = cast(dt.timedelta, now.dst())
    if (result_dst >= now_dst):
        return result
    try:
        tzinfo.localize(now.replace(tzinfo=None), is_dst=None)
        return result
    except pytzexceptions.AmbiguousTimeError:
        pass
    check = (now - now_dst)
    check_result = find_next_time_expression_time(check, seconds, minutes, hours)
    try:
        tzinfo.localize(check_result.replace(tzinfo=None), is_dst=None)
        return result
    except pytzexceptions.AmbiguousTimeError:
        pass
    check_result = tzinfo.localize(check_result.replace(tzinfo=None), is_dst=False)
    return check_result