Kickoff times are a deceptively hard data problem. A fixture has one instant in time but many correct local representations, and the gap between them is where scheduling bugs live.
The rules that matter
- Store the instant in UTC. Always.
- Convert to local time only at display, using the viewer's IANA zone (
Europe/London, notGMT). - Never store a UTC offset as if it were a timezone — offsets change twice a year, zones do not.
- Transmit as ISO 8601 with an explicit offset:
2026-08-30T14:00:00+00:00.
Worked example
A 15:00 kickoff at Anfield in January is 15:00 UTC. The same 15:00 local kickoff in June is 14:00 UTC, because Britain is on BST. Store 15:00 both times and half your season is an hour wrong. Store the UTC instant and both render correctly everywhere.
The bugs this causes
Two failures dominate. First, storing local time without a zone: correct until the clocks change, then silently wrong for months. Second, applying a fixed offset — a match stored as "UTC+1" is right in summer and an hour out in winter. Both survive testing because they are correct on the day you write them. Notification systems and calendar exports are where they surface, usually to users rather than to you.
Doing this at scale
Stats API returns every kickoff as an ISO 8601 timestamp with an explicit offset, so the ambiguity never reaches your application. Convert at render time, against the viewer's zone.
Common questions
Why not just store local time and the country?
Countries can contain several zones and can change their DST rules by legislation. The zone database is updated several times a year; a stored instant is immune to that, a stored local time is not.
What happens to a fixture during a clock change?
Local times between 01:00 and 02:00 may not exist, or may occur twice. A UTC instant has neither problem, which is the argument for storing it.