docs: def parse_time_tuple(start, end):
      """
      Parse a time tuple. These can be:
        relative in seconds,       e.g. (-4, 0)
        relative in timedelta,     e.g. (timedelta(seconds=-4), timedelta(0))
        absolute in date/datetime, e.g. (datetime(2016, 4, 28, 20, 0, 0, 0, UTC), datetime(2016, 4, 28, 21, 0, 0, 0, UTC))
        absolute in iso strings,   e.g. ("2016-04-28T20:00:00.000Z", "2016-04-28T20:01:00.000Z")
        Mixtures of relative and absolute are not allowed
  
      :param start: Start time
      :param end: End time
      :type start: int | timedelta | datetime | str
      :type end: int | timedelta | datetime | str
      :return: TimeInterval or RelativeTimeInterval object
      """
      if isinstance(start, int):
          start_time = timedelta(seconds=start)
      elif isinstance(start, timedelta):
          start_time = start
      elif start is None:
          start_time = MIN_DATE
      elif isinstance(start, (date, datetime)):
          start_time = start.replace(tzinfo=UTC)
      else:
          start_time = ciso8601.parse_datetime(start).replace(tzinfo=UTC)
  
      if isinstance(end, int):
          # TODO: add check for future (negative values) and ensure that start < end
          if not isinstance(start_time, timedelta):
              raise ValueError("Can't mix relative and absolute times")
          end_time = timedelta(seconds=end)
      elif isinstance(end, timedelta):
          if not isinstance(start_time, timedelta):
              raise ValueError("Can't mix relative and absolute times")
          end_time = end
      elif end is None:
          end_time = utcnow()  # TODO: or MAX_DATE?
      elif isinstance(end, datetime):
          end_time = end.replace(tzinfo=UTC)
      else:
          end_time = ciso8601.parse_datetime(end).replace(tzinfo=UTC)
  
      if isinstance(start_time, timedelta):
          return RelativeTimeInterval(start=start_time, end=end_time)
      else:
          return TimeInterval(start=start_time, end=end_time)