Datetime Features
Expose time's hidden cycles
Datetimes compress seasonality, holidays, timezones and cyclical structure; breaking them into extractable components and periodic (sin/cos) features is a standard, high-yield engineering step.
What you will learn
- Extract hour, weekday, month, year and holiday components deliberately
- Encode cyclical time with sin/cos instead of raw integers to respect 23:00->00:00 adjacency
- Handle timezone and daylight-savings correctly across train and prod
What is this?
Datetime features convert a timestamp into semantically useful columns: calendar parts (hour, weekday, month, quarter, year), cyclical projections (sin(2π t/T), cos(2π t/T) so 23:00 and 00:00 are neighbors), and external events (holidays, promos, fiscal periods). This is where most of a forecast model's signal is unlocked.
Why do you need it?
- A raw epoch integer teaches a model nearly nothing about 'Friday evening' or 'December'.
- Integer hour encoding tells the model 23 and 00 are far apart — sin/cos restores the loop adjacency.
- Timezone/DST bugs silently shift predictions by an hour in production.
- Holiday calendar features are often the single strongest predictor in demand models.
Mental model
The 12-hour clock: 11 and 1 are neighbors
Cyclical encoding of hour-of-day
A linear model sees 11 then 1 as a distance 10 gap; sin/cos sees the clock face where they're adjacent.
How it works
- Parse once with pandas dt accessor: dt.year, dt.month, dt.day, dt.hour, dt.weekday, dt.dayofyear.
- Cyclical: time_h = 2*pi*hour/24; feature = [sin, cos]. Same for day-of-week (T=7) and month (T=12).
- External: holiday flags, fiscal week, promo windows merged from a calender table.
- Timezone: convert to a canonical zone before extraction; make DST explicit.
- Sanity-check train vs prod feature distributions for hour/weekday before modeling.
Comparing encodings of hour-of-day.
Examples
Cyclical + calendar datetime features
import pandas as pdimport numpy as np ts = pd.Series(pd.date_range("2026-06-01 23:50", "2026-06-02 00:10", freq="5min"))df = pd.DataFrame({"ts": ts}) hour = df["ts"].dt.hour + df["ts"].dt.minute / 60.0df["hour_sin"] = np.sin(2 * np.pi * hour / 24.0)df["hour_cos"] = np.cos(2 * np.pi * hour / 24.0) df["dow"] = df["ts"].dt.dayofweekdf["dow_sin"] = np.sin(2 * np.pi * df["dow"] / 7.0)df["dow_cos"] = np.cos(2 * np.pi * df["dow"] / 7.0)df["month"] = df["ts"].dt.month print(df.round(3))print("23:55 vs 00:05 sin/cos distance:", np.sqrt(((df.iloc[1][["hour_sin","hour_cos"]] - df.iloc[3][["hour_sin","hour_cos"]])) .pow(2).sum()).round(4))The sin/cos pair wraps midnight: 23:55 and 00:05 are near-neighbors in circular space even though their raw hours differ by 23, while the linear hour column still claims they're far apart.
In production
- Ride demand: hour+weekday cyclical features and rainfall aggregations anchor the model's time pattern.
- Retail forecasts: month/holiday/fiscal-week flags drive most MAPE reduction.
- Web traffic: DST-aware hour (before/after clock-change mapping) prevents phantom weekly spikes.
Best practices
- Always emit both sin AND cos (one alone can't disambiguate phase); re-derive prod features with the same timezone/calendar; treat dates as features at prediction time only as far as known-future calendars; store timestamps in UTC and convert at the boundary.
Common mistakes
- Feeding only sin; dividing by wrong period (hour by 12); ignoring DST so one hour doubles/trips a year's data; using datetime as a raw int; leaking future timestamps in validation windows.
Debugging tips
- Assert the parsed date range equals the raw file range (year 1900 = parse bug).
- Spot-wrap: plot hour decay — the sin/cos circle should be a clean loop.
- Check DST boundary days explicitly in train vs prod schedules.
Interview checkpoint
Quick quiz
Why must BOTH sin and cos of a cycle be emitted?
Which is a DST-safe practice?