Skip to content

DaylightSavingFeatureTransformer

yohou.preprocessing.DaylightSavingFeatureTransformer

Bases: BaseActualTransformer

Deterministic daylight-saving-time features from the time column.

A series whose local time observes daylight saving crosses two kinds of boundary that a UTC-indexed frame cannot see directly: the summer/winter clock offset, which shifts the UTC hour of the local diurnal cycle by one hour, and the two transition days a year, which are 23- or 25-hour local days with a skipped or repeated hour. Both are deterministic and known for any future date, so they enter a model as unlagged, known-future features alongside the calendar terms. Output columns are prefixed with dst_.

Daylight saving is evaluated in time_zone: the "time" column (which must be timezone-aware) is converted into that zone, then the offset is read there. The parameter names the zone whose daylight-saving regime is evaluated, not an assertion about the input's zone, so a UTC-indexed frame and a Central-indexed frame produce the same features for the same instants.

For a timestamp \(t\) with local daylight-saving offset \(\delta(t)\) (in minutes, \(0\) in standard time), and \(\delta_d\) the offset at the local midnight of date \(d\):

\[\text{in\_effect}(t) = \mathbb{1}[\delta(t) \neq 0], \quad \text{transition\_day}(d) = \mathbb{1}[\delta_d \neq \delta_{d+1}], \quad \text{transition\_type}(d) = \operatorname{sign}(\delta_{d+1} - \delta_d)\]

Note that dst_transition_day is exactly dst_transition_type != 0; both are provided because a tree splits on them differently (the binary is a cleaner root split, the signed carries the spring/fall direction).

Parameters

Name Type Description Default
time_zone str

IANA time zone in which daylight saving time is evaluated.

"America/Chicago"
features list of str or None

Which daylight-saving features to emit. If None, emits every default feature applicable to the detected time interval (["in_effect", "transition_day"]). Valid options: "in_effect", "transition_day", "transition_type".

None

Attributes

Name Type Description
applicable_features_ list of str

The resolved feature list emitted during transform (after interval gating).

Raises

Type Description
ValueError

At fit time if the "time" column is not a timezone-aware pl.Datetime (a timezone-naive datetime or a pl.Date has no well-defined offset); if any requested feature name is unknown; if "in_effect" is explicitly requested on data whose detected interval is daily or coarser; or if a generated dst_* column name conflicts with an existing column in X.

See Also

Examples

>>> from datetime import datetime, timezone
>>> import polars as pl
>>> from yohou.preprocessing import DaylightSavingFeatureTransformer
>>> time = pl.datetime_range(
...     datetime(2026, 3, 8, 6, tzinfo=timezone.utc),
...     datetime(2026, 3, 8, 10, tzinfo=timezone.utc),
...     interval="1h",
...     eager=True,
... )
>>> X = pl.DataFrame({"time": time})
>>> DaylightSavingFeatureTransformer().fit_transform(X)["dst_in_effect"].to_list()
[0, 0, 1, 1, 1]

Source Code

Source code in src/yohou/preprocessing/calendar.py
class DaylightSavingFeatureTransformer(BaseActualTransformer):
    r"""Deterministic daylight-saving-time features from the time column.

    A series whose local time observes daylight saving crosses two kinds of boundary that a
    UTC-indexed frame cannot see directly: the summer/winter clock offset, which shifts the
    UTC hour of the local diurnal cycle by one hour, and the two transition days a year,
    which are 23- or 25-hour local days with a skipped or repeated hour. Both are
    deterministic and known for any future date, so they enter a model as unlagged,
    known-future features alongside the calendar terms. Output columns are prefixed with
    ``dst_``.

    Daylight saving is evaluated in ``time_zone``: the ``"time"`` column (which must be
    timezone-aware) is converted into that zone, then the offset is read there. The
    parameter names the zone whose daylight-saving regime is evaluated, not an assertion
    about the input's zone, so a UTC-indexed frame and a Central-indexed frame produce the
    same features for the same instants.

    For a timestamp $t$ with local daylight-saving offset $\delta(t)$ (in minutes, $0$ in
    standard time), and $\delta_d$ the offset at the local midnight of date $d$:

    $$\text{in\_effect}(t) = \mathbb{1}[\delta(t) \neq 0], \quad
      \text{transition\_day}(d) = \mathbb{1}[\delta_d \neq \delta_{d+1}], \quad
      \text{transition\_type}(d) = \operatorname{sign}(\delta_{d+1} - \delta_d)$$

    Note that ``dst_transition_day`` is exactly ``dst_transition_type != 0``; both are
    provided because a tree splits on them differently (the binary is a cleaner root split,
    the signed carries the spring/fall direction).

    Parameters
    ----------
    time_zone : str, default="America/Chicago"
        IANA time zone in which daylight saving time is evaluated.
    features : list of str or None, default=None
        Which daylight-saving features to emit. If ``None``, emits every default feature
        applicable to the detected time interval (``["in_effect", "transition_day"]``).
        Valid options: ``"in_effect"``, ``"transition_day"``, ``"transition_type"``.

    Attributes
    ----------
    applicable_features_ : list of str
        The resolved feature list emitted during ``transform`` (after interval gating).

    Raises
    ------
    ValueError
        At fit time if the ``"time"`` column is not a timezone-aware ``pl.Datetime`` (a
        timezone-naive datetime or a ``pl.Date`` has no well-defined offset); if any
        requested feature name is unknown; if ``"in_effect"`` is explicitly requested on
        data whose detected interval is daily or coarser; or if a generated ``dst_*``
        column name conflicts with an existing column in ``X``.

    See Also
    --------
    - [`CalendarFeatureTransformer`][yohou.preprocessing.calendar.CalendarFeatureTransformer] : Calendar features (month, day of week, etc.).
    - [`HolidayFeatureTransformer`][yohou.preprocessing.calendar.HolidayFeatureTransformer] : Binary holiday indicator from user-provided dates.
    - [`FourierFeatureTransformer`][yohou.preprocessing.time_features.FourierFeatureTransformer] : Sin/cos harmonics for cyclical encoding.

    Examples
    --------
    >>> from datetime import datetime, timezone
    >>> import polars as pl
    >>> from yohou.preprocessing import DaylightSavingFeatureTransformer
    >>> time = pl.datetime_range(
    ...     datetime(2026, 3, 8, 6, tzinfo=timezone.utc),
    ...     datetime(2026, 3, 8, 10, tzinfo=timezone.utc),
    ...     interval="1h",
    ...     eager=True,
    ... )
    >>> X = pl.DataFrame({"time": time})
    >>> DaylightSavingFeatureTransformer().fit_transform(X)["dst_in_effect"].to_list()
    [0, 0, 1, 1, 1]

    """

    _parameter_constraints: dict = {
        "time_zone": [str],
        "features": [list, None],
    }

    def __init__(self, time_zone: str = "America/Chicago", features: list[str] | None = None):
        self.time_zone = time_zone
        self.features = features

    def _fit(self, X: pl.DataFrame, y: pl.DataFrame | None = None) -> None:
        """Validate the input contract, resolve features (with interval gating), and check conflicts."""
        dtype = X.schema["time"]
        time_zone = dtype.time_zone if isinstance(dtype, pl.Datetime) else None
        if time_zone is None:
            raise ValueError(
                "DaylightSavingFeatureTransformer requires a timezone-aware 'time' column "
                "(a daylight-saving offset is undefined without an instant and a zone); "
                f"got dtype {dtype}. Localize it first, e.g. dt.replace_time_zone('UTC')."
            )

        requested = list(self.features) if self.features is not None else list(DST_DEFAULT_FEATURES)
        invalid = set(requested) - set(DST_ALL_FEATURES)
        if invalid:
            raise ValueError(f"Unknown DST features: {sorted(invalid)}. Valid features: {list(DST_ALL_FEATURES)}")

        subdaily = _interval_is_subdaily(self.interval_)
        gated = [f for f in requested if f in DST_SUBDAILY_FEATURES and not subdaily]
        if self.features is not None and gated:
            raise ValueError(
                f"Features {gated} require sub-daily data, but the detected interval is "
                f"'{self.interval_}'. These features need an instant, not just a date."
            )
        self.applicable_features_ = [f for f in requested if f not in gated]

        generated = [f"dst_{f}" for f in self.applicable_features_]
        conflicts = set(generated) & (set(X.columns) - {"time"})
        if conflicts:
            raise ValueError(f"Generated column names {sorted(conflicts)} conflict with existing columns in X.")

    def _transform(self, X: pl.DataFrame) -> pl.DataFrame:
        """Emit ``time`` plus the selected ``dst_*`` feature columns."""
        exprs = [_extract_dst_feature(f, self.time_zone) for f in self.applicable_features_]
        return X.select(pl.col("time"), *exprs)

    def get_feature_names_out(self, input_features=None) -> list[str]:
        """Return the selected ``dst_*`` feature-column names.

        Parameters
        ----------
        input_features : array-like of str or None, default=None
            Input feature names (unused, for API compatibility).

        Returns
        -------
        list of str
            Generated daylight-saving feature column names.

        """
        check_is_fitted(self, ["applicable_features_"])
        return [f"dst_{f}" for f in self.applicable_features_]

Methods

get_feature_names_out(input_features=None)

Return the selected dst_* feature-column names.

Parameters
Name Type Description Default
input_features array-like of str or None

Input feature names (unused, for API compatibility).

None
Returns
Type Description
list of str

Generated daylight-saving feature column names.

Source Code
Source code in src/yohou/preprocessing/calendar.py
def get_feature_names_out(self, input_features=None) -> list[str]:
    """Return the selected ``dst_*`` feature-column names.

    Parameters
    ----------
    input_features : array-like of str or None, default=None
        Input feature names (unused, for API compatibility).

    Returns
    -------
    list of str
        Generated daylight-saving feature column names.

    """
    check_is_fitted(self, ["applicable_features_"])
    return [f"dst_{f}" for f in self.applicable_features_]