Building a stoppage-aware World Cup game clock

jun 17 2026 · 2 min read

Soccer time is deceptive. The clock never stops during play, but the official minute count does not advance during stoppages - instead, the referee adds them back at the end of each half as injury time. An API that reports 90'+4' is telling you the half ended regulation and four minutes of added time have elapsed. Naive parsing drops the stoppage portion entirely, so your clock jumps from 90 to nothing, or freezes.

The first step was moving the parse to the server side. The ESPN overlay returns a clock string in that M'+S' format during stoppage. I parse it into a structured object with distinct minute, stoppage, period, and display fields rather than passing the raw string to the client. That keeps the representation canonical regardless of what format the source decides to use in the next match, and it means the client never needs to know what the string looked like.

On the client, the hook receives a poll result that includes a fetched_at timestamp alongside the parsed clock. Rather than updating the displayed minute directly on each poll, the hook stores the anchor - the server-reported minute at the time the poll arrived - and ticks locally every second by adding elapsed milliseconds to that anchor. When a new poll arrives the anchor is replaced. This way the display moves smoothly between polls without accumulating drift, and a slow poll does not freeze the clock.

Freezing at half-time and full-time is handled by period. When period is HT or FT the tick is suppressed and the display is locked to the boundary value rather than continuing to count. This matches what a broadcast clock actually does - it holds at 45 or 90 when the referee blows, not after.

The timeline bar below the clock maps the current minute onto a percentage width. The half-time mark sits at the 45-minute position (roughly 42.5% of the bar's total width, which represents the full expected match span including stoppage zones), and the full-time mark at 90 minutes (85%). Beyond the full-time mark the bar enters an open-ended stoppage zone that extends to 100% but does not overflow - it just fills. The lesson from this feature is that the data model matters more than the rendering: once minute, stoppage, and period are first-class fields, every visual concern - the tick, the freeze, the bar, the display string - resolves naturally from them.