Event logs are usually stored in UTC: 2026-03-08T07:15:00Z (the Z means UTC). People think in local time, so convert:
ts = pd.to_datetime(events["ts_utc"]) # time-zone aware, in UTC
local = ts.dt.tz_convert("America/New_York")
tz_convert changes the zone of times that already have one. tz_localize attaches a zone to times that have none (naive times); use it when you know they were recorded in local time.
- Local dates differ from UTC dates: 11 pm in New York is already tomorrow in UTC. Group by
local.dt.date for "events per day" as a local manager sees them.
- Daylight saving: on 8 March 2026 New York moved from UTC-5 to UTC-4. Converting through the zone name handles that for you; adding a fixed number of hours would not.
- Comparing an aware time with a naive one raises Can't compare tz-naive and tz-aware. Give the other side a time zone too.