Skip to content

API reference#

earthcarekit.utils.time

Datetime utilities.

Notes#

This module depends on other internal modules:


format_time_range_text #

format_time_range_text(
    st: TimestampLike, et: TimestampLike, show_date: bool = True, show_time: bool = True
) -> str

Returns a formatted text label (str) describing the time range given by the start time st and end time et.

Source code in earthcarekit/utils/time/_time.py
def format_time_range_text(
    st: TimestampLike, et: TimestampLike, show_date: bool = True, show_time: bool = True
) -> str:
    """Returns a formatted text label (`str`) describing the time range given by the start time `st` and end time `et`."""
    st = to_timestamp(st)
    et = to_timestamp(et)

    if (
        st.year == et.year
        and st.month == et.month
        and st.day == et.day
        and st.hour == et.hour
        and st.minute == et.minute
    ):
        texts = []
        if show_date:
            texts.append(f"{st.day} {st.strftime('%b')} {st.year}")
        if show_time:
            texts.append(f"{str(st.hour).zfill(2)}:{str(st.minute).zfill(2)} UTC")
        return ", ".join(texts)

    if st.year == et.year and st.month == et.month and st.day == et.day and st.hour == et.hour:
        texts = []
        if show_date:
            texts.append(f"{st.day} {st.strftime('%b')} {st.year}")
        if show_time:
            texts.append(
                f"{str(st.hour).zfill(2)}:{str(st.minute).zfill(2)}-{str(et.minute).zfill(2)} UTC"
            )
        return ", ".join(texts)

    if st.year == et.year and st.month == et.month and st.day == et.day:
        texts = []
        if show_date:
            texts.append(f"{st.day} {st.strftime('%b')} {st.year}")
        if show_time:
            texts.append(
                f"{str(st.hour).zfill(2)}:{str(st.minute).zfill(2)}-{str(et.hour).zfill(2)}:{str(et.minute).zfill(2)} UTC"
            )
        return ", ".join(texts)

    if st.year == et.year and st.month == et.month:
        if show_time:
            return f"{st.strftime('%b')} {st.year}, {st.day} {str(st.hour).zfill(2)}:{str(st.minute).zfill(2)} - {et.day} {str(et.hour).zfill(2)}:{str(et.minute).zfill(2)} UTC"
        if show_date:
            return f"{st.day}-{et.day} {st.strftime('%b')} {st.year}"

    if st.year == et.year:
        if show_time:
            return f"{st.year}, {st.strftime('%b')} {st.day} {str(st.hour).zfill(2)}:{str(st.minute).zfill(2)} - {et.strftime('%b')} {et.day} {str(et.hour).zfill(2)}:{str(et.minute).zfill(2)} UTC"
        if show_date:
            return f"{st.day} {st.strftime('%b')} - {et.day} {et.strftime('%b')} {st.year}"
    else:
        if show_time:
            return f"{st.day} {st.strftime('%b')} {st.year} {str(st.hour).zfill(2)}:{str(st.minute).zfill(2)} - {et.day} {et.strftime('%b')} {et.year} {str(et.hour).zfill(2)}:{str(et.minute).zfill(2)} UTC"
        if show_date:
            return f"{st.strftime('%d %b %Y')} - {et.strftime('%d %b %Y')}"
        return f"{st.strftime('%d %b %Y')} - {et.strftime('%d %b %Y')}"

    return ""

get_day_night_mask #

get_day_night_mask(
    time: DatetimeIndex | ArrayLike,
    lats: ArrayLike,
    lons: ArrayLike,
    sun_altitude_threshold: float = 0.0,
) -> NDArray[bool_]

Calculates a day/night mask from UTC timestamps and lat/lon positions.

Parameters:

Name Type Description Default
times

UTC Timestamps.

required
lats ArrayLike

Latitude(s) in degrees (scalar or same length as times).

required
lons ArrayLike

Longitude(s) in degrees, positive east (scalar or same length as times).

required
sun_altitude_threshold float

Threshold in degrees for "day" (0 = horizon, -6 = civil twilight, -12 = nautical twilight, -18 = astronomical twilight)

0.0

Returns:

Type Description
NDArray[bool_]

Day/night mask (True = day, False = night)

References
  • NOAA Global Monitoring Laboratory. Low Accuracy Equations. NOAA. Retrieved January 21, 2025 from https://gml.noaa.gov/grad/solcalc/sollinks.html
  • Wikipedia contributors (2025). Hour angle. Wikipedia. Retrieved January 21, 2025 from https://en.wikipedia.org/wiki/Hour_angle
Source code in earthcarekit/utils/time/_day_night.py
def get_day_night_mask(
    time: pd.DatetimeIndex | ArrayLike,
    lats: ArrayLike,
    lons: ArrayLike,
    sun_altitude_threshold: float = 0.0,
) -> NDArray[np.bool_]:
    """Calculates a day/night mask from UTC timestamps and lat/lon positions.

    Args:
        times: UTC Timestamps.
        lats: Latitude(s) in degrees (scalar or same length as `times`).
        lons: Longitude(s) in degrees, positive east (scalar or same length as `times`).
        sun_altitude_threshold:
            Threshold in degrees for "day" (0 = horizon, -6 = civil twilight,
            -12 = nautical twilight, -18 = astronomical twilight)

    Returns:
        Day/night mask (True = day, False = night)

    References:
        - NOAA Global Monitoring Laboratory. Low Accuracy Equations. NOAA.
        Retrieved January 21, 2025 from https://gml.noaa.gov/grad/solcalc/sollinks.html
        - Wikipedia contributors (2025). Hour angle. Wikipedia.
        Retrieved January 21, 2025 from https://en.wikipedia.org/wiki/Hour_angle
    """

    if isinstance(time, pd.DatetimeIndex):
        time_np = time.values.astype("datetime64[s]")
    else:
        time_np = np.asarray(time).astype("datetime64[s]")

    hour = _get_hour_of_day(time_np)

    day_of_year = _get_day_of_year(time_np)

    lats = np.asarray(lats, dtype=float)
    lons = np.asarray(lons, dtype=float)
    n = len(hour)
    if lats.ndim == 0:
        lats = np.full(n, lats)
    if lons.ndim == 0:
        lons = np.full(n, lons)

    # Fractional year
    gamma_rad = 2.0 * np.pi / 365.0 * (day_of_year - 1.0 + (hour - 12.0) / 24.0)

    # Solar declination angle
    decl_rad = (
        0.006918
        - 0.399912 * np.cos(gamma_rad)
        + 0.070257 * np.sin(gamma_rad)
        - 0.006758 * np.cos(2 * gamma_rad)
        + 0.000907 * np.sin(2 * gamma_rad)
        - 0.002697 * np.cos(3 * gamma_rad)
        + 0.00148 * np.sin(3 * gamma_rad)
    )

    # Local solar time
    lst = (hour + lons / 15.0) % 24.0

    # Solar hour angle (0 deg at solar noon, 15 deg per hour, negative=morning, positive=afternoon)
    hour_angle = np.deg2rad(15.0 * (lst - 12.0))

    # Solar elevation angle
    lat_rad = np.deg2rad(lats)
    sin_elev = np.sin(lat_rad) * np.sin(decl_rad) + np.cos(lat_rad) * np.cos(decl_rad) * np.cos(
        hour_angle
    )
    elev_deg = np.rad2deg(np.arcsin(np.clip(sin_elev, -1.0, 1.0)))

    day_night_mask = elev_deg > sun_altitude_threshold
    return day_night_mask

get_time_range #

get_time_range(
    start_time: TimestampLike,
    end_time: TimestampLike,
    freq: str | None = None,
    periods: int | None = None,
) -> DatetimeIndex

Generates timestamps based on frequency or number of periods.

Parameters:

Name Type Description Default
start_time TimestampLike

Start timestamp.

required
end_time TimestampLike

End timestamp.

required
freq str | None

Time frequency string (e.g., "1H", "30min"); rounds to nearest.

None
periods int | None

Number of evenly spaced timestamps.

None

Returns:

Type Description
DatetimeIndex

A DatetimeIndex of timestamps, either rounded or evenly spaced.

Source code in earthcarekit/utils/time/_time.py
def get_time_range(
    start_time: TimestampLike,
    end_time: TimestampLike,
    freq: str | None = None,
    periods: int | None = None,
) -> pd.DatetimeIndex:
    """Generates timestamps based on frequency or number of periods.

    Args:
        start_time: Start timestamp.
        end_time: End timestamp.
        freq: Time frequency string (e.g., "1H", "30min"); rounds to nearest.
        periods: Number of evenly spaced timestamps.

    Returns:
        A `DatetimeIndex` of timestamps, either rounded or evenly spaced.
    """
    start_time = to_timestamp(start_time, keep_tzinfo=False)
    end_time = to_timestamp(end_time, keep_tzinfo=False)

    if not isinstance(freq, str) and not isinstance(periods, int):
        raise TypeError(
            f"{get_time_range.__name__}() missing 1 required argument: 'freq' or 'periods'"
        )
    elif isinstance(freq, str) and isinstance(periods, int):
        raise TypeError(
            f"{get_time_range.__name__}() expected 1 out of the 2 required arguments 'freq' and 'periods' but got both"
        )
    elif isinstance(freq, str):
        if freq == "MS":
            start_period = start_time.to_period("M")
            end_period = end_time.to_period("M")

            start_month = start_period.to_timestamp()
            if start_month != start_time:
                start_month = start_month + pd.Timedelta(start_period.days_in_month, "D")
            end_month = end_period.to_timestamp()

            n_months = int(np.round((end_month - start_month).days / 30))

            time_range = [start_month]
            for i in range(n_months):
                _period = time_range[-1].to_period("M")
                _month = time_range[-1] + pd.Timedelta(_period.days_in_month, "D")
                time_range.append(_month)
        elif freq == "SMS":
            start_period = start_time.to_period("M")
            end_period = end_time.to_period("M")

            start_month = start_period.to_timestamp()
            if start_month != start_time:
                if start_month + pd.Timedelta(14, "D") >= start_time:
                    start_month = start_month + pd.Timedelta(14, "D")
                else:
                    start_month = start_month + pd.Timedelta(start_period.days_in_month, "D")
            end_month = end_period.to_timestamp()
            if end_month + pd.Timedelta(14, "D") <= end_time:
                end_month = end_month + pd.Timedelta(14, "D")

            n_semimonths = int(np.round((end_month - start_month).days / 15))

            time_range = [start_month]
            for i in range(n_semimonths):
                _ts = time_range[-1]
                if _ts.day == 15:
                    _period = time_range[-1].to_period("M")
                    _next = _period.to_timestamp() + pd.Timedelta(_period.days_in_month, "D")
                else:
                    _period = time_range[-1].to_period("M")
                    _next = time_range[-1] + pd.Timedelta(14, "D")
                time_range.append(_next)
        elif "W" in freq:
            w_count = 1
            if freq[:-1] != "":
                w_count = int(freq[:-1])

            start_period = start_time.to_period("W")
            end_period = end_time.to_period("W")

            start_week = start_period.to_timestamp()
            if start_week != start_time:
                start_week = start_week + pd.Timedelta(7 * w_count, "D")
            end_week = end_period.to_timestamp()

            n_weeks = int(np.round((end_week - start_week).days / (7 * w_count)))

            time_range = [start_week]
            for i in range(n_weeks):
                _period = time_range[-1].to_period("W")
                _week = time_range[-1] + pd.Timedelta(7 * w_count, "D")
                time_range.append(_week)
        elif freq == "QS":

            def next_quarter(ts):
                q_period = pd.Timestamp(ts).to_period("Q")
                m1 = q_period.to_timestamp()
                m2 = m1 + pd.Timedelta(q_period.days_in_month, "D")
                m3 = m2 + pd.Timedelta(m2.to_period("M").days_in_month, "D")
                m4 = m3 + pd.Timedelta(m3.to_period("M").days_in_month, "D")
                return m4

            start_period = start_time.to_period("Q")
            end_period = end_time.to_period("Q")

            start_quarter = start_period.to_timestamp()
            if start_quarter != start_time:
                start_quarter = next_quarter(start_quarter)
            end_quarter = end_period.to_timestamp()

            n_quarters = int(np.round((end_quarter - start_quarter).days / 90))

            time_range = [start_quarter]
            for i in range(n_quarters):
                _quarter = next_quarter(time_range[-1])
                time_range.append(_quarter)
        elif "YS" in freq:
            y_count = 1
            if freq[:-2] != "":
                y_count = int(freq[:-2])

            start_period = start_time.to_period("Y")
            end_period = end_time.to_period("Y")

            start_year = start_period.to_timestamp()
            if start_year != start_time:
                start_year = (
                    (start_year + pd.Timedelta(366 * y_count, "D")).to_period("Y").to_timestamp()
                )
            end_year = end_period.to_timestamp()
            n_years = int(np.round((end_year - start_year).days / (365 * y_count)))

            time_range = [start_year]
            for i in range(n_years):
                _period = time_range[-1].to_period("Y")
                _year = (
                    (time_range[-1] + pd.Timedelta(366 * y_count, "D"))
                    .to_period("Y")
                    .to_timestamp()
                )
                time_range.append(_year)
        else:
            time_range = pd.date_range(
                start_time.ceil(freq), end_time.floor(freq), freq=freq
            ).to_list()
    else:
        time_range = pd.date_range(start_time, end_time, periods=cast(int, periods)).to_list()
    return pd.DatetimeIndex(time_range)

lookup_value_by_timestamp #

lookup_value_by_timestamp(t: TimestampLike, times: NDArray, values: NDArray[Any]) -> Any

Returns the value corresponding to the closest timestamp in a monotonic series.

Parameters:

Name Type Description Default
t TimestampLike

Timestamp to look up.

required
times NDArray

Monotonically increasing timestamps.

required
values NDArray[Any]

Values corresponding to times.

required

Returns:

Type Description
Any

Value from values at the index of the closest timestamp in times.

Raises:

Type Description
ValueError

If times and values have different lengths.

Source code in earthcarekit/utils/time/_time.py
def lookup_value_by_timestamp(t: TimestampLike, times: NDArray, values: NDArray[Any]) -> Any:
    """Returns the value corresponding to the closest timestamp in a monotonic series.

    Args:
        t: Timestamp to look up.
        times: Monotonically increasing timestamps.
        values: Values corresponding to `times`.

    Returns:
        Value from `values` at the index of the closest timestamp in `times`.

    Raises:
        ValueError: If `times` and `values` have different lengths.
    """
    if t is None:
        raise ValueError(f"{lookup_value_by_timestamp.__name__}() missing `t`")
    if times is None:
        raise ValueError(f"{lookup_value_by_timestamp.__name__}() missing `times`")
    if values is None:
        raise ValueError(f"{lookup_value_by_timestamp.__name__}() missing `values`")

    t = to_timestamp(t).to_numpy()
    times = to_timestamps(times).to_numpy()
    values = np.asarray(values)

    if times.shape[0] == 0:
        raise ValueError(
            f"{lookup_value_by_timestamp.__name__}() `times` is empty but needs at least on element"
        )
    if values.shape[0] != times.shape[0]:
        raise ValueError(
            f"{lookup_value_by_timestamp.__name__}() First shapes must be the same for `values` ({values.shape[0]}) and `times` ({times.shape[0]})"
        )

    idx0 = np.searchsorted(times, t) - 1
    idx1 = np.searchsorted(times, t)

    idx0 = np.min([len(times) - 1, np.max([0, idx0])])
    idx1 = np.min([len(times) - 1, np.max([0, idx1])])

    if times[idx0] > t:
        idx0 = idx1

    total_duration = times[idx1] - times[idx0]
    duration = t - times[idx0]

    if total_duration == pd.Timedelta(0, "s"):
        frac = 0
    else:
        frac = duration / total_duration

    total_amount = values[idx1] - values[idx0]
    v = values[idx0] + total_amount * frac

    return v

num_to_time #

num_to_time(num: NDArray | Iterable, epoch: TimestampLike, format: TimeUnit = 'ns') -> NDArray

Converts numerical time values back to datetime values relative to a given epoch.

Parameters:

Name Type Description Default
num NDArray | Iterable

Array or iterable of numeric values representing time offsets.

required
epoch TimestampLike

Reference time to which the numeric values are relative.

required
format TimeUnit

Time unit of the numeric values (e.g., "s", "ms", "us", "ns"); defaults to "ns".

'ns'

Returns:

Type Description
NDArray

An array of datetime64 values corresponding to the numeric offsets from the epoch.

Source code in earthcarekit/utils/time/_time.py
def num_to_time(
    num: NDArray | Iterable,
    epoch: TimestampLike,
    format: TimeUnit = "ns",
) -> NDArray:
    """Converts numerical time values back to datetime values relative to a given epoch.

    Args:
        num: Array or iterable of numeric values representing time offsets.
        epoch: Reference time to which the numeric values are relative.
        format: Time unit of the numeric values (e.g., "s", "ms", "us", "ns"); defaults to "ns".

    Returns:
        An array of datetime64 values corresponding to the numeric offsets from the epoch.
    """
    num = np.asarray(num)
    epoch = to_timestamp(epoch).to_numpy()
    return (num * np.timedelta64(1, format)) + epoch

time_to_iso #

time_to_iso(
    time: TimestampLike, format: Literal["datetime", "date", "time"] | str = "datetime"
) -> str

Converts timestamp to an ISO-formatted string.

Parameters:

Name Type Description Default
time TimestampLike

Input timestamp.

required
format Literal['datetime', 'date', 'time'] | str

The type of ISO string to return

  • "datetime" -> YYYY-MM-DDTHH:MM:SS (default)
  • "date" -> YYYY-MM-DD
  • "time" -> HH:MM:SS
'datetime'

Returns:

Type Description
str

An ISO-formatted time string.

Source code in earthcarekit/utils/time/_time.py
def time_to_iso(
    time: TimestampLike,
    format: Literal["datetime", "date", "time"] | str = "datetime",
) -> str:
    """Converts timestamp to an ISO-formatted string.

    Args:
        time: Input timestamp.
        format: The type of ISO string to return

            - "datetime" -> YYYY-MM-DDTHH:MM:SS (default)
            - "date" -> YYYY-MM-DD
            - "time" -> HH:MM:SS

    Returns:
        An ISO-formatted time string.
    """
    if not isinstance(format, str):
        raise TypeError(
            f"""Invalid type for output_format '{type(format).__name__}', expected Literal["date", "time", "datetime"]"""
        )
    if format == "date":
        return time_to_str(time, "%Y-%m-%d")
    elif format == "time":
        return time_to_str(time, "%H:%M:%S")
    elif format == "datetime":
        return time_to_str(time, "%Y-%m-%dT%H:%M:%S")
    else:
        return time_to_str(time, format)

time_to_num #

time_to_num(
    time: NDArray | Iterable[TimestampLike], epoch: TimestampLike, format: TimeUnit = "ns"
) -> NDArray

Converts datetime-like values to numerical values relative to a given epoch.

Parameters:

Name Type Description Default
time NDArray | Iterable[TimestampLike]

Array or iterable of datetime-like values.

required
epoch TimestampLike

Reference time from which the numerical values are computed.

required
format TimeUnit

Time unit for conversion (e.g., "s", "ms", "us", "ns"); defaults to "ns".

'ns'

Returns:

Type Description
NDArray

An array of floats representing the time difference from the epoch in the given unit.

Source code in earthcarekit/utils/time/_time.py
def time_to_num(
    time: NDArray | Iterable[TimestampLike],
    epoch: TimestampLike,
    format: TimeUnit = "ns",
) -> NDArray:
    """Converts datetime-like values to numerical values relative to a given epoch.

    Args:
        time: Array or iterable of datetime-like values.
        epoch: Reference time from which the numerical values are computed.
        format: Time unit for conversion (e.g., "s", "ms", "us", "ns"); defaults to "ns".

    Returns:
        An array of floats representing the time difference from the epoch in the given unit.
    """
    time = to_timestamps(time).to_numpy()
    epoch = to_timestamp(epoch).to_numpy()
    return (time - epoch) / np.timedelta64(1, format)

time_to_str #

time_to_str(time: TimestampLike, format: str = '%Y-%m-%dT%H:%M:%S') -> str

Converts timestamp to a formatted string (defaults to YYYY-MM-DDTHH:MM:SS).

Source code in earthcarekit/utils/time/_time.py
def time_to_str(time: TimestampLike, format: str = "%Y-%m-%dT%H:%M:%S") -> str:
    """Converts timestamp to a formatted string (defaults to YYYY-MM-DDTHH:MM:SS)."""
    timestamp = to_timestamp(time)
    if isinstance(timestamp, pd.Timestamp):
        return timestamp.strftime(format)
    else:
        return ""

times_to_iso #

times_to_iso(
    times: Iterable[TimestampLike], format: Literal["datetime", "date", "time"] | str = "datetime"
) -> list[str]

Converts timestamps to a list of ISO-formatted strings.

Parameters:

Name Type Description Default
times Iterable[TimestampLike]

Input timestamps.

required
format Literal['datetime', 'date', 'time'] | str

The type of ISO strings to return

  • "datetime" -> YYYY-MM-DDTHH:MM:SS (default)
  • "date" -> YYYY-MM-DD
  • "time" -> HH:MM:SS
'datetime'

Returns:

Type Description
list[str]

A list of ISO-formatted time strings.

Source code in earthcarekit/utils/time/_time.py
def times_to_iso(
    times: Iterable[TimestampLike],
    format: Literal["datetime", "date", "time"] | str = "datetime",
) -> list[str]:
    """Converts timestamps to a list of ISO-formatted strings.

    Args:
        times: Input timestamps.
        format: The type of ISO strings to return

            - "datetime" -> YYYY-MM-DDTHH:MM:SS (default)
            - "date" -> YYYY-MM-DD
            - "time" -> HH:MM:SS

    Returns:
        A list of ISO-formatted time strings.
    """
    return [time_to_iso(t, format) for t in to_timestamps(times)]

times_to_str #

times_to_str(times: Iterable[TimestampLike], format: str = '%Y-%m-%dT%H:%M:%S') -> list[str]

Converts timestamps to a list of formatted string (defaults to YYYY-MM-DDTHH:MM:SS).

Source code in earthcarekit/utils/time/_time.py
def times_to_str(times: Iterable[TimestampLike], format: str = "%Y-%m-%dT%H:%M:%S") -> list[str]:
    """Converts timestamps to a list of formatted string (defaults to YYYY-MM-DDTHH:MM:SS)."""
    return [time_to_str(t, format) for t in to_timestamps(times)]

to_timedelta #

to_timedelta(t: TimedeltaLike) -> Timedelta

Converts input to pandas.Timedelta.

Source code in earthcarekit/utils/time/_time.py
def to_timedelta(t: TimedeltaLike) -> pd.Timedelta:
    """Converts input to `pandas.Timedelta`."""
    if isinstance(t, np.str_):
        t = str(t)
    if isinstance(t, TimedeltaLike):
        return pd.Timedelta(t)  # type: ignore
    raise TypeError(f"Input timedelta has invalud type ({type(t)}: {t})")

to_timedeltas #

to_timedeltas(times: TimedeltasLike) -> TimedeltaIndex

Converts inputs to pandas.Timedeltas and returns them as pandas.TimedeltaIndex.

Source code in earthcarekit/utils/time/_time.py
def to_timedeltas(times: TimedeltasLike) -> pd.TimedeltaIndex:
    """Converts inputs to `pandas.Timedelta`s and returns them as `pandas.TimedeltaIndex`."""
    if isinstance(times, pd.TimedeltaIndex):
        return times
    if isinstance(times, Iterable) and not isinstance(times, str):
        return pd.TimedeltaIndex([to_timedelta(cast(TimedeltaLike, t)) for t in times])
    raise TypeError(f"Input timestamps has invalud type ({type(times)}: {times})")

to_timestamp #

to_timestamp(t: TimestampLike, keep_tzinfo: bool = False) -> Timestamp

Converts input to pandas.Timestamp.

Source code in earthcarekit/utils/time/_time.py
def to_timestamp(t: TimestampLike, keep_tzinfo: bool = False) -> pd.Timestamp:
    """Converts input to `pandas.Timestamp`."""
    if isinstance(t, np.str_):
        t = str(t)
    if isinstance(t, TimestampLike):
        new_t = pd.Timestamp(t)  # type: ignore
        if not keep_tzinfo and new_t.tzinfo is not None:
            new_t = new_t.tz_convert(None)
        return new_t
    raise TypeError(f"Input timestamp has invalud type ({type(t)}: {t})")

to_timestamps #

to_timestamps(times: TimestampsLike, keep_tzinfo: bool = False) -> DatetimeIndex

Converts inputs to pandas.Timestamps and returns them as pandas.DatetimeIndex.

Source code in earthcarekit/utils/time/_time.py
def to_timestamps(times: TimestampsLike, keep_tzinfo: bool = False) -> pd.DatetimeIndex:
    """Converts inputs to `pandas.Timestamp`s and returns them as `pandas.DatetimeIndex`."""
    if isinstance(times, pd.DatetimeIndex):
        return times
    if isinstance(times, Iterable) and not isinstance(times, str):
        return pd.DatetimeIndex([to_timestamp(t, keep_tzinfo=keep_tzinfo) for t in times])  # type: ignore
    raise TypeError(f"Input timestamps has invalid type ({type(times)}: {times})")

validate_time_range #

validate_time_range(time_range: TimeRangeLike | None) -> tuple[Timestamp, Timestamp]

Checks if given time_range is valid, if True returns is as a tuple of two pandas.Timestamps, if False raises ValueError.

Source code in earthcarekit/utils/time/_time.py
def validate_time_range(
    time_range: TimeRangeLike | None,
) -> tuple[pd.Timestamp, pd.Timestamp]:
    """
    Checks if given `time_range` is valid, if True returns is as a `tuple`
    of two `pandas.Timestamp`s, if False raises `ValueError`.
    """
    if isinstance(time_range, (Iterable, np.ndarray)):
        if len(time_range) == 2:
            if all([isinstance(t, TimestampLike) for t in time_range]):
                result_time_range: tuple[pd.Timestamp, pd.Timestamp] = (
                    to_timestamp(time_range[0]),
                    to_timestamp(time_range[1]),
                )
                if result_time_range[0] <= result_time_range[1]:
                    return result_time_range
                else:
                    raise ValueError(
                        f"time range needs to increase monotonically, got instead: {tuple([str(t) for t in time_range])}"
                    )
    raise ValueError(f"invalid time range: {time_range}")