def datetime_attribute_timeseries(
    time_index: Union[pd.DatetimeIndex, TimeSeries],
    attribute: str,
    one_hot: bool = False,
    cyclic: bool = False,
    until: Optional[Union[int, str, pd.Timestamp]] = None,
    add_length: int = 0,
    dtype=np.float64,
    with_columns: Optional[Union[list[str], str]] = None,
    tz: Optional[str] = None,
) -> TimeSeries:
    """
    Returns a new TimeSeries with index `time_index` and one or more dimensions containing
    (optionally one-hot encoded or cyclic encoded) pd.DatatimeIndex attribute information derived from the index.

    1-indexed attributes are shifted to enforce 0-indexing across all the encodings.

    Parameters
    ----------
    time_index
        Either a `pd.DatetimeIndex` attribute which will serve as the basis of the new column(s), or
        a `TimeSeries` whose time axis will serve this purpose.
    attribute
        An attribute of `pd.DatetimeIndex`, or `week` / `weekofyear` / `week_of_year` - e.g. "month", "weekday", "day",
        "hour", "minute", "second". See all available attributes in
        https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DatetimeIndex.html#pandas.DatetimeIndex.
    one_hot
        Boolean value indicating whether to add the specified attribute as a one hot encoding
        (results in more columns).
    cyclic
        Boolean value indicating whether to add the specified attribute as a cyclic encoding.
        Alternative to one_hot encoding, enable only one of the two.
        (adds 2 columns, corresponding to sin and cos transformation)
    until
        Extend the time_index up until timestamp for datetime indexed series
        and int for range indexed series, should match or exceed forecasting window.
    add_length
        Extend the time_index by add_length, should match or exceed forecasting window.
        Set only one of until and add_length.
    dtype
        The desired NumPy dtype (np.float32 or np.float64) for the resulting series
    with_columns
        Optionally, specify the output component names.
        * If `one_hot` and `cyclic` are ``False``, must be a string
        * If `cyclic` is ``True``, must be a list of two strings. The first string for the sine, the second for the
            cosine component name.
        * If `one_hot` is ``True``, must be a list of strings of the same length as the generated one hot encoded
            features.
    tz
        Optionally, a time zone to convert the time index to before computing the attributes.

    Returns
    -------
    TimeSeries
        New datetime attribute TimeSeries instance.
    """

    time_index_ts, time_index = _process_time_index(
        time_index=time_index,
        tz=tz,
        until=until,
        add_length=add_length,
    )

    raise_if_not(
        hasattr(pd.DatetimeIndex, attribute)
        or (attribute in ["week", "weekofyear", "week_of_year"]),
        f"attribute `{attribute}` needs to be an attribute of pd.DatetimeIndex. "
        "See all available attributes in "
        "https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DatetimeIndex.html#pandas.DatetimeIndex",
        logger,
    )

    raise_if(one_hot and cyclic, "set only one of one_hot or cyclic to true", logger)

    num_values_dict = {
        "month": 12,
        "day": 31,
        "weekday": 7,
        "dayofweek": 7,
        "day_of_week": 7,
        "hour": 24,
        "minute": 60,
        "second": 60,
        "microsecond": 1000000,
        "nanosecond": 1000,
        "quarter": 4,
        "dayofyear": 365,
        "day_of_year": 365,
        "week": 52,
        "weekofyear": 52,
        "week_of_year": 52,
    }

    if attribute not in ["week", "weekofyear", "week_of_year"]:
        values = getattr(time_index, attribute)
    else:
        values = (
            time_index.isocalendar()
            .set_index("week")
            .index.astype("int64")
            .rename("time")
        )

    # shift 1-indexed datetime attributes
    if attribute in ONE_INDEXED_FREQS:
        values -= 1

    # leap years insert an additional day on the 29th of February
    if attribute in {"dayofyear", "day_of_year"} and any(time_index.is_leap_year):
        num_values_dict[attribute] += 1

    # years contain an additional week if they are :
    # - a regular year starting on a thursday
    # - a leap year starting on a wednesday
    if attribute in {"week", "weekofyear", "week_of_year"}:
        years = time_index.year.unique()
        # check if year respect properties
        additional_week_year = any(
            ((not first_day.is_leap_year) and first_day.day_name() == "Thursday")
            or (first_day.is_leap_year and first_day.day_name() == "Wednesday")
            for first_day in [pd.Timestamp(f"{year}-01-01") for year in years]
        )
        # check if time index actually include the additional week
        additional_week_in_index = time_index[-1] - time_index[0] + pd.Timedelta(
            days=1
        ) >= pd.Timedelta(days=365)

        if additional_week_year and additional_week_in_index:
            num_values_dict[attribute] += 1

    if one_hot or cyclic:
        raise_if_not(
            attribute in num_values_dict,
            f"Given datetime attribute `{attribute}` not supported with one-hot or cyclical encoding. "
            f"Supported datetime attribute: {list(num_values_dict.keys())}",
            logger,
        )

    if one_hot:
        values_df = pd.get_dummies(values)
        # fill missing columns (in case not all values appear in time_index)
        attribute_range = np.arange(num_values_dict[attribute])
        is_missing = np.isin(attribute_range, values_df.columns.values, invert=True)
        # if there are attribute_range columns that are
        # not in values_df.columns.values
        if is_missing.any():
            dict_0 = {i: False for i in attribute_range[is_missing]}
            # Make a dataframe from the dictionary and concatenate it
            # to the values values_df  in which the existing columns
            values_df = pd.concat(
                [values_df, pd.DataFrame(dict_0, index=values_df.index)], axis=1
            ).sort_index(axis=1)
        else:
            values_df = values_df[attribute_range]

        if with_columns is None:
            with_columns = [
                attribute + "_" + str(column_name) for column_name in values_df.columns
            ]

        raise_if_not(
            len(with_columns) == len(values_df.columns),
            "For the given case with `one_hot=True`,`with_columns` must be a list of strings of length "
            f"{values_df.columns}.",
            logger=logger,
        )

        values_df.columns = with_columns
    else:
        if cyclic:
            if attribute == "day":
                periods = time_index.days_in_month.values
                freq = 2 * np.pi * np.reciprocal(periods.astype(dtype))
            else:
                period = num_values_dict[attribute]
                freq = 2 * np.pi / period

            if with_columns is None:
                with_columns = [attribute + "_sin", attribute + "_cos"]

            raise_if(
                len(with_columns) != 2,
                "`with_columns` must be a list of two strings when `cyclic=True`. "
                "The first string for the sine component name, the second for the cosine component name.",
                logger=logger,
            )
            values_df = pd.DataFrame({
                with_columns[0]: np.sin(freq * values),
                with_columns[1]: np.cos(freq * values),
            })
        else:
            if with_columns is None:
                with_columns = attribute
            raise_if_not(
                isinstance(with_columns, str),
                "`with_columns` must be a string specifying the output component name.",
                logger=logger,
            )
            values_df = pd.DataFrame({with_columns: values})

    values_df.index = time_index_ts
    return TimeSeries.from_dataframe(values_df).astype(dtype)

    Returns a new TimeSeries with index `time_index` and one or more dimensions containing
    (optionally one-hot encoded or cyclic encoded) pd.DatatimeIndex attribute information derived from the index.

    1-indexed attributes are shifted to enforce 0-indexing across all the encodings.

    Parameters
    ----------
    time_index
        Either a `pd.DatetimeIndex` attribute which will serve as the basis of the new column(s), or
        a `TimeSeries` whose time axis will serve this purpose.
    attribute
        An attribute of `pd.DatetimeIndex`, or `week` / `weekofyear` / `week_of_year` - e.g. "month", "weekday", "day",
        "hour", "minute", "second". See all available attributes in
        https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DatetimeIndex.html#pandas.DatetimeIndex.
    one_hot
        Boolean value indicating whether to add the specified attribute as a one hot encoding
        (results in more columns).
    cyclic
        Boolean value indicating whether to add the specified attribute as a cyclic encoding.
        Alternative to one_hot encoding, enable only one of the two.
        (adds 2 columns, corresponding to sin and cos transformation)
    until
        Extend the time_index up until timestamp for datetime indexed series
        and int for range indexed series, should match or exceed forecasting window.
    add_length
        Extend the time_index by add_length, should match or exceed forecasting window.
        Set only one of until and add_length.
    dtype
        The desired NumPy dtype (np.float32 or np.float64) for the resulting series
    with_columns
        Optionally, specify the output component names.
        * If `one_hot` and `cyclic` are ``False``, must be a string
        * If `cyclic` is ``True``, must be a list of two strings. The first string for the sine, the second for the
            cosine component name.
        * If `one_hot` is ``True``, must be a list of strings of the same length as the generated one hot encoded
            features.
    tz
        Optionally, a time zone to convert the time index to before computing the attributes.

    Returns
    -------
    TimeSeries
        New datetime attribute TimeSeries instance.
    
darts/utils/timeseries_generation.py