Skip to content

TimezoneTransformer

Converts the DataFrame index from UTC (what every source returns) to a local time zone, and moves the values of daily columns to local midnight.

Basic Usage

from epftoolbox2.data.transformers import TimezoneTransformer
transformer = TimezoneTransformer(target_tz="Europe/Warsaw")
df = transformer.transform(df)

As part of a DataPipeline, target_tz can be left unset: it then inherits DataPipeline.timezone, so the zone is written once instead of in both places.

pipeline = DataPipeline(timezone="Europe/Warsaw").add_transformer(TimezoneTransformer())

Parameters

ParameterTypeDefaultDescription
target_tzstrNoneTarget IANA time zone, e.g. "Europe/Warsaw". None inherits DataPipeline.timezone when part of a pipeline. Transforming without either raises
daily_columnslist[str] | NoneNoneColumn names holding one value per day, matched by exact name or a f"_{name}" suffix (so a prefixed neighbour’s column, e.g. fi_load_forecast_daily_min, is recognised too). None means ["load_forecast_daily_min", "load_forecast_daily_max"] (ENTSO-E week-ahead forecast). A name matching nothing in the frame is skipped
daily_anchorstr | NoneNoneTime zone whose calendar date a daily value belongs to: a UTC offset ("UTC+02:00", "+01:00", "UTC") or an IANA name. None uses the resolved target_tz itself

Daily columns

Each daily_columns entry matches a column of that exact name, or one ending in f"_{name}", so a prefixed neighbour’s column (EntsoeSource(..., prefix="fi") gives fi_load_forecast_daily_min) is recognised under the default list without adding every prefix by name.

ENTSO-E’s week-ahead load forecast (A31) has one value per day, but its periods start at 00:00 local time of the area that published it, whatever that area’s UTC offset is, in every zone. In CET (Warsaw) that instant is 00:00 UTC+2 in summer and 23:00 UTC the day before in winter. Without correction a later ResampleTransformer would give each winter value to the wrong hours. Further east it is a different offset again: 00:00 EEST (UTC+3) in Helsinki in summer, 00:00 UTC+3 all year in a zone with a fixed offset (e.g. Europe/Istanbul).

The rule TimezoneTransformer applies:

A value at timestamp t belongs to the calendar date of t in daily_anchor. It is moved to 00:00 local time (target_tz) on that date.

The default anchor (daily_anchor=None) is target_tz itself, which is right for A31 data in every zone, because that is where ENTSO-E actually anchors it:

ZoneA31 period start, in local timeMoved to
Europe/Warsaw, winter23:00 on D-1 (UTC) → already 00:00 CET on D00:00 on D (no-op)
Europe/Warsaw, summer22:00 on D-1 (UTC) → already 00:00 CEST on D00:00 on D (no-op)
Europe/Helsinki, summer (EEST, UTC+3)21:00 on D-1 (UTC) → already 00:00 EEST on D00:00 on D (no-op)
Europe/Istanbul (fixed UTC+3)21:00 on D-1 (UTC) → already 00:00 on D00:00 on D (no-op)

A fixed daily_anchor="UTC+02:00" gets this wrong for any zone whose actual offset isn’t UTC+2 at that moment: it silently misplaces every value in EET/EEST zones (FI, EE, LV, LT, BG, GR) every summer, and every value, year-round, in a zone with a fixed UTC+3 offset. Pass an explicit daily_anchor only when a daily column’s real anchor genuinely differs from target_tz, for example a source that anchors to a fixed offset regardless of the pipeline’s own zone:

TimezoneTransformer(target_tz="Europe/Warsaw", daily_columns=["my_daily_value"], daily_anchor="UTC+02:00")

After the move, ResampleTransformer spreads each daily value over exactly its own local day (23, 24 or 25 hours).

Checks:

  • A daily column must hold at most one value per day. If it holds more (typically because ResampleTransformer already ran and made it dense), TimezoneTransformer raises a ValueError instead of collapsing the column to a few values.
  • If the local midnight a value belongs to is not in the index, that row is added (other columns NaN). This is routine at the start of a winter frame: the A31 value for the first local day is stamped an hour before the other sources begin. If the missing midnight lies inside the data, the index has a hole there and a UserWarning is emitted.

Pipeline Order

pipeline = (
DataPipeline(timezone="Europe/Warsaw")
.add_source(EntsoeSource(...)) # Output: UTC
.add_transformer(TimezoneTransformer()) # target_tz inherits DataPipeline.timezone; creates DST gaps
.add_transformer(ResampleTransformer(freq="1h")) # Fills DST gaps
.add_transformer(LagTransformer(columns=["price"], lags=[1, 2, 7], freq="day")) # Lags by local calendar day
)

What happens on DST days

Converting to local time adds and removes nothing: the spring switch day has 23 hourly rows (no 02:00, because that local time doesn’t exist) and the autumn switch day has 25 (02:00 twice). ResampleTransformer keeps these 23 and 25 hours. It does not invent an 02:00 row.


Timezone List

For a complete list of valid timezone names, see: List of tz database time zones

Common Timezones

TimezoneDescription
"Europe/Warsaw"Poland (CET/CEST)
"Europe/Berlin"Germany (CET/CEST)
"Europe/Paris"France (CET/CEST)
"Europe/London"UK (GMT/BST)
"America/New_York"US Eastern
"UTC"Coordinated Universal Time

Behavior

  • Naive index: Localized to UTC, then converted
  • Timezone-aware index: Directly converted
# Input: 2024-01-01 00:00:00+00:00 (UTC)
# Output: 2024-01-01 01:00:00+01:00 (Warsaw = CET)