Countdowns are annoying when they break
I built a Cinco De Mayo Countdown timer for a small marketing page a few years back, and the first time I deployed it, it showed a negative number two days before May 5th. Turns out I'd hardcoded the target date as a UTC timestamp but my server was reporting local time, so the difference shifted by several hours depending on where the user was reading from. That's the kind of detail nobody warns you about until it costs you an embarrassing Slack message to your team. The basic idea is simple: calculate the gap between now and May 5th of whatever year makes sense, then display days, hours, minutes, and seconds. The real work is in the edge cases. You need to decide whether Cinco de Mayo means 00:00:00 on May 5th UTC or 00:00:00 in the reader's local timezone. Most people just pick one and stick with it, but if your audience is global you'll get complaints either way. I settled on using the target date in the reader's browser timezone by reading the system offset with new Date().getTimezoneOffset() and then reconstructing the target as a proper Date object in local time rather than converting everything to UTC. This means someone in Mexico City sees a slightly different countdown than someone in New York on the same page load, which is technically correct and avoids the midday flip-flop problem where the timer resets at noon UTC and suddenly everyone three time zones ahead loses a whole day.
Here's the core logic without the boilerplate. You get the current timestamp, you subtract it from the target timestamp, and you break the difference down: days = total milliseconds / 86400000, floored
hours = remainder / 3600000, floored
minutes = remainder / 60000, floored
seconds = remainder / 1000, floored You update that every second with setInterval, and you clear the interval once the target passes so you don't keep showing negative time or looping awkwardly. I tend to swap in a "celebration mode" message once the countdown hits zero because letting it go negative looks broken even though mathematically it's fine.
For a static implementation you don't need a backend at all. A single HTML file with embedded JavaScript does the job, loads instantly, and works on any hosting setup including GitHub Pages or Netlify. If you need server-side precision, like if you're syncing multiple regional countdowns across different timezone targets, you'd want Node or Python handling the calculation on each request instead of relying on client time, which anyone can tweak with browser dev tools. There's a real tradeoff here. Client-side is faster to build and free to host but depends on the user's system clock being correct, which is rarely true on corporate machines that sync time weekly. Server-side is accurate but adds latency and infrastructure cost for what is essentially a trivial calculation. I use client-side for internal tools and personal projects, server-side when it's customer-facing and the stakes matter. If you want to download a ready-made version, I keep a minimal repo on GitHub called five-mayo-countdown. It's plain HTML and JS, no dependencies, about 60 lines total, and it handles the timezone reconstruction I described above. It also includes a basic fallback that detects if the page has loaded after May 5th and shows a static message instead of broken numbers. Grab it, fork it, or steal the logic. It's not polished but it won't show negative time.
Get the Full Details

One more thing people overlook: daylight saving time transitions. If your countdown spans a spring-forward or fall-back window, the hour count will jitter by an extra hour or disappear entirely for one cycle. The setInterval approach doesn't care about calendar semantics, it just divides milliseconds. To handle this cleanly you need to recalculate the target in local calendar terms on each tick rather than doing a single subtraction at page load. It adds maybe twenty lines but it's the difference between a timer that ticks smoothly and one that jumps around during the DST changeover. I don't recommend over-engineering this unless you're putting it on something public-facing. For most use cases a straightforward client-side countdown with a timezone-aware target and a post-event message is enough, and it'll save you a few hours of debugging that you'll never get back.