A calendar day is a timezone interval
“Today's fixtures” depends on where the reader is. A UTC date filter alone can omit a late-evening local kickoff or include a fixture that belongs to tomorrow locally. Convert the reader's local midnight and next local midnight into UTC, fetch a broad enough documented date range, then filter starts_at against that half-open interval in your application.
Use a named timezone such as Europe/London rather than a fixed offset when daylight-saving rules matter. The local day can contain twenty-three or twenty-five hours around a clock change. Adding twenty-four hours to a UTC start does not necessarily reach the next local midnight.
Keep the request contract and final filter separate
The match collection accepts date_from and date_to as dates, not arbitrary timestamp parameters. Do not invent starts_after or a timezone query parameter. Request the UTC calendar dates covering your local interval, paginate the response, and apply start <= kickoff < end locally. The upper-exclusive comparison means a fixture exactly at next midnight belongs to the next day.
This example demonstrates Europe/London on 25 October 2026, when the local interval is twenty-five hours. The test uses synthetic kickoff timestamps near its boundaries. It is a calendar calculation using the timezone database, not a claim that a real match will occur at either timestamp.
Run the reproducible example
This Python 3 example uses deliberately synthetic inputs, not a downloaded match or a forecast for a real fixture. Save it as example.py and run python3 example.py. It uses only the standard library and its assertions check the behavior discussed here. Replace the fixture inputs with validated API records when connecting your application; demo identifiers are not production lookup values.
from datetime import date, datetime, time, timedelta, timezone
from zoneinfo import ZoneInfo
def utc_window(day, zone):
tz = ZoneInfo(zone)
start = datetime.combine(day,time.min,tzinfo=tz)
end = datetime.combine(day+timedelta(days=1),time.min,tzinfo=tz)
return start.astimezone(timezone.utc), end.astimezone(timezone.utc)
start,end = utc_window(date(2026,10,25),"Europe/London")
assert (end-start).total_seconds() == 25*3600
assert start.isoformat() == "2026-10-24T23:00:00+00:00"
assert end.isoformat() == "2026-10-26T00:00:00+00:00"
def included(value):
stamp = datetime.fromisoformat(value.replace("Z","+00:00"))
return start <= stamp < end
assert included("2026-10-24T23:00:00Z")
assert not included("2026-10-26T00:00:00Z")
print(start.isoformat(),end.isoformat(),"25 hours")
Read the result
The program reports a UTC window from 24 October at 23:00 to 26 October at 00:00, a duration of twenty-five hours. It includes the starting boundary and excludes the next day boundary, as a local-date view requires.
Retain UTC as the stored identity
Store the original starts_at value with the match ID and format it only for display. Do not save a localized string as the authoritative kickoff; strings such as 01:30 can occur twice during a clock change and lack enough information to recover the instant. Include the timezone label in exports or calendar views so users understand the display context.
A rescheduled kickoff must replace the previous value for the same match identity. Do not infer live or finished status because the displayed kickoff is in the past. Match status is a separate returned field, and delayed or postponed games make clock-based assumptions unreliable.
Test more than your own timezone
Add tests for a normal day, a spring clock change, an autumn clock change, and a timezone with a half-hour offset. Check matches exactly at both bounds and one second outside. Validate incoming timestamps as offset-aware; comparing a naive local datetime to an aware UTC datetime should fail during development rather than drift silently in production.
Pin or maintain the runtime's timezone data as part of your deployment. Calendar rules can change, so a copied offset table is less dependable than the maintained zone database. For a cached fixtures page, the selected timezone and local date belong in the cache key as well as the competition and season filters.
Connect this to your project
Compare the interval with the kickoff time converter, then use the match endpoint's documented date parameters for your broad fetch. Keep the final timestamp filter in one tested utility so list views, notifications and exports agree about which local day contains a match.