Dates and Time Series
Time data has at least four independent concerns: instant, time zone, calendar frequency, and interval semantics. Make each one explicit.
Core types
Timestamp: one point in time.DatetimeIndex: an index of timestamps.Timedelta: an elapsed duration.Period: a calendar span such as a month or quarter.DateOffset: a calendar-aware movement such as month end or business day.
Parse at the boundary
import pandas as pd
events = pd.DataFrame({
"occurred_at": ["2026-01-01T12:00:00+0000", "2026-01-03T12:00:00+0000"],
"value": [10, 30],
})
events["occurred_at"] = pd.to_datetime(
events["occurred_at"],
format="%Y-%m-%dT%H:%M:%S%z",
errors="coerce",
utc=True,
)
An explicit format is faster to reason about and prevents locale ambiguity. Treat coerced parse failures as data-quality events and inspect them.
Time zones
tz_localizeattaches a zone to naive local clock readings.tz_convertconverts already-aware instants to another zone.
Store instants in UTC when possible and convert for presentation. Daylight-saving transitions create ambiguous or nonexistent local times; choose a policy rather than silently discarding the issue.
Index and resample
assert events["occurred_at"].notna().all()
series = events.set_index("occurred_at")["value"].sort_index()
daily = series.resample("D").sum(min_count=1)
rolling_7d = series.rolling("7D").mean()
resample groups observations into calendar bins. rolling computes windows
around observations. Their parameters encode different questions; do not use one
as shorthand for the other.
For interval labels, specify boundary and labeling behavior (closed, label,
and origin where applicable) when defaults affect interpretation.
Calendar ranges and periods
month_ends = pd.date_range("2026-01-01", periods=6, freq="ME", tz="UTC")
quarters = month_ends.tz_localize(None).to_period("Q")
Use periods when the entity is a calendar span rather than a precise instant.
Calendar bins versus elapsed windows
The example’s daily totals are 10, missing, 30 for January 1–3: min_count=1 preserves the empty day. The rolling result has only the two observation timestamps, with means 10 and 20. A time window "7D" includes observations in the preceding seven days, excluding the left boundary by default; rolling(7) instead counts seven observations and requires seven valid values by default. Time-based rolling requires a monotonic datetime index without NaT.
For forecasting, Time Series and Walk-Forward Evaluation checks lag availability and compares seasonal baselines on future periods.
For datetime values in a Series, use .dt.tz_localize(...) and .dt.tz_convert(...); the Series methods without .dt act on its index. Passing naive strings to to_datetime(..., utc=True) interprets them as UTC, not as local time. A 24-hour Timedelta is elapsed time; a one-day DateOffset preserves local clock time across a daylight-saving transition and may span 23 or 25 hours.
The time-series guide distinguishes calendar frequencies from periods. "ME" is the month-end offset spelling in pandas 2.2 and newer. Periods do not retain time zones: the example deliberately removes the UTC zone while preserving UTC clock readings before converting to quarters. Convert to the business time zone first if it determines the calendar quarter.
The pandas air-quality time-series tutorial carries one dataset from timestamp parsing through plotting and resampling. Reproduce its daily means and monthly maxima, then explain why changing both the frequency and the aggregation changes the question being answered.