def convert_times(times, date_type, raise_on_invalid: bool = True) -> np.ndarray:
    """Given an array of datetimes, return the same dates in another cftime or numpy date type.

    Useful to convert between calendars in numpy and cftime or between cftime calendars.

    If raise_on_valid is True (default), invalid dates trigger a ValueError.
    Otherwise, the invalid element is replaced by np.nan for cftime types and np.NaT for np.datetime64.
    """
    if date_type in (pd.Timestamp, np.datetime64) and not is_np_datetime_like(
        times.dtype
    ):
        return cftime_to_nptime(times, raise_on_invalid=raise_on_invalid)
    if is_np_datetime_like(times.dtype):
        # Convert datetime64 objects to Timestamps since those have year, month, day, etc. attributes
        times = pd.DatetimeIndex(times)
    new = np.empty(times.shape, dtype="O")
    for i, t in enumerate(times):
        try:
            dt = date_type(
                t.year, t.month, t.day, t.hour, t.minute, t.second, t.microsecond
            )
        except ValueError as e:
            if raise_on_invalid:
                raise ValueError(
                    f"Cannot convert date {t} to a date in the "
                    f"{date_type(2000, 1, 1).calendar} calendar.  Reason: {e}."
                ) from e
            else:
                dt = np.nan

        new[i] = dt
    return new
Given an array of datetimes, return the same dates in another cftime or numpy date type.

    Useful to convert between calendars in numpy and cftime or between cftime calendars.

    If raise_on_valid is True (default), invalid dates trigger a ValueError.
    Otherwise, the invalid element is replaced by np.nan for cftime types and np.NaT for np.datetime64.
    
xarray/coding/times.py