Every answer a gambling platform gives has a time inside it. A wager was accepted at one, a session began at another, a round settled after it, a bonus window closed, a market suspended, a jackpot accrued, a report’s day ended, a log line was written. None of those times is decoration: each one is an assertion the operator may eventually have to defend — to a player with a dispute, to an auditor with a question, to an incident review reconstructing what happened, or to a reconciliation that does not balance.
Time is also the one dependency a platform never stops using. It is read on every request rather than called occasionally, it has no health endpoint by default, and when it is wrong the platform does not stop — it keeps serving, quietly, with timestamps that no longer mean what the surrounding records assume. That is why time deserves the same treatment as a ledger: one source of truth, an explicit representation, stated boundaries and tests that reproduce the failure.

The four parts of a time-based claim
“At 14:03:07” is not yet a claim anyone can check. A defensible time assertion has four parts, and a platform that leaves any of them implicit will eventually disagree with its own reports.
| Part | The question it answers | Where it goes wrong when unwritten |
|---|---|---|
| The clock | Which machine, synchronised to what, within what tolerance | Two services order the same events differently |
| The representation | Instant with an offset, or local wall time with no zone | A stored time cannot be placed on a timeline after the fact |
| The rule | Which calendar, which boundary, inclusive or exclusive | A cut-off enforced one way in the API and another in the report |
| The evidence | What was recorded, by whom, kept for how long | A dispute is settled by a log that cannot show the offset |
The rest of this article takes those four in order, because the mistakes compound: an unwritten clock source makes the representation ambiguous, an ambiguous representation makes the boundary unprovable, and an unprovable boundary turns a routine reconciliation into an argument.
Name one clock the platform trusts
The Network Time Protocol is the ordinary answer, and RFC 5905 describes what it is for: NTP “is widely used to synchronize computer clocks in the Internet”, and version 4 “includes fundamental improvements in the mitigation and discipline algorithms that extend the potential accuracy to the tens of microseconds with modern workstations and fast LANs”. Read that sentence carefully before quoting it internally. It is a statement about the accuracy the protocol can reach under favourable conditions, not a promise about a virtual machine on a busy hypervisor, behind a saturated network link, with a daemon that has been polling the same server for a month.
Four practical rules follow from that.
- More than one source, and a named fallback. A single upstream server is a single point of failure for something every request depends on. A platform configuration should look like a small pool of sources, and the configuration should be reviewable in the repository rather than typed once into a running host.
- Authenticate the source where the exposure justifies it. RFC 8915 specifies Network Time Security, which uses “Transport Layer Security (TLS) and Authenticated Encryption with Associated Data (AEAD) to provide cryptographic security for the client-server mode of the Network Time Protocol (NTP)”. Chrony exposes it as the
ntsoption on aserverdirective, and its documentation warns that when authentication is enabled it is important to disable unauthenticated sources that could otherwise be used to influence the clock. - Step at boot, slew afterwards. Chrony’s
makestepdirective “forces chronyd to step the system clock if the adjustment is larger than a threshold value, but only if there were no more clock updates since chronyd was started than the specified limit”. Its own guidance is that “on most systems it is desirable to step the system clock only on boot, before starting programs that rely on time advancing monotonically forwards”. A platform that steps its clock in normal operation has decided that a second may be repeated or skipped while it is running. - Monitor the time source as a dependency. Offset and drift per host, alerted at a threshold tighter than the tightest boundary the platform asserts, and treated as a reason to remove a host from time-sensitive writes rather than as a graph someone reads later. A host whose clock is outside tolerance is not “fine, just a bit fast”; it is a host that will write a record that disagrees with the record beside it.
Store the instant, not the impression
A timestamp is an instant with a stated relationship to Coordinated Universal Time. RFC 3339, the Internet date and time format, is explicit that its values express an offset from UTC, and it defines a convention worth knowing: if the UTC time is known but the local offset is not, the offset -00:00 means exactly that, and “differs semantically from an offset of Z or +00:00, which imply that UTC is the preferred reference point for the specified time”. The same document notes the property that makes this representation worth the discipline: if values share an offset and the same precision, they sort as text into time order.
The failure mode is the opposite habit — storing what the screen showed. A wall time with no zone is unrecoverable once the offset is lost. PostgreSQL’s documentation states the mechanics plainly: if no time zone is given in the input it “is assumed to be in the time zone indicated by the system’s TimeZone parameter”, and in either case “the value is stored internally as UTC, and the originally stated or assumed time zone is not retained”. The database does the right thing with the instant and discards the human context, which means a rule that mentioned a local zone has to be reconstructed from something else. It also means a server configuration can silently change how an unqualified timestamp is read.
RFC 9557, published in 2024, exists for that gap: it extends RFC 3339 “for representing additional information, including a time zone”, precisely because applications “handle each such instant in time with an associated time zone name in order to take into account events such as daylight saving time transitions”. Whether or not a platform adopts that format, the rule it encodes is the one to follow: store the instant, and keep the zone name beside it when a rule referred to a local time, rather than a fixed offset computed at the time of writing.
Measure with a clock that cannot be reset
Two clocks live in most hosts, and the Go documentation states the distinction better than most specifications: “Operating systems provide both a ‘wall clock,’ which is subject to changes for clock synchronization, and a ‘monotonic clock,’ which is not. The general rule is that the wall clock is for telling time and the monotonic clock is for measuring time.”
That split has an operational consequence. Anything that measures an interval — a timeout, a retry backoff, a rate-limit window, a latency sample, a session countdown, a lock lease — must be driven by a monotonic source, or a clock correction becomes a bug: a step forward expires work that had time left, a step backward makes an elapsed duration negative. Anything that records a fact — acceptance, settlement, accrual — must use the wall clock, because an instant has to be comparable with other machines.
The two also diverge at serialisation, which is where the mistake usually ships. The monotonic reading “has no meaning outside the current process”, so Go’s encoders omit it, and every parse constructor creates a time without it. A duration that was kept as a timestamp difference therefore survives a process restart as wall-clock arithmetic, with whatever error the clock accumulated. If the interval matters across a restart, it should be stored as a deadline instant rather than recomputed from a start time.
Browsers draw the same line: the W3C High Resolution Time specification defines a time source in sub-millisecond resolution “such that it is not subject to system clock skew or adjustments”. Client-side telemetry that measures durations with a wall clock is measuring the player’s device clock, not the platform.
There is one trap that no amount of discipline on the platform side fixes, and it is worth writing into the acceptance notes rather than discovering in production: on some systems a monotonic clock stops while the machine is suspended, so an elapsed duration measured across a suspend can under-report the real time that passed.
Decide what a leap second does to your ledger
Coordinated Universal Time is not a pure count of seconds. NIST explains the reason: leap seconds are inserted to keep UTC “within ±0.9 s of the UT1 astronomical time scale”, the current difference between UTC and International Atomic Time is 37 seconds, and the sequence of events at a positive leap second runs 23h 59m 59s, then 23h 59m 60s, then 00h 00m 00s. RFC 3339’s appendix records that the decision belongs to the IERS, that “a leap second occurs simultaneously in all time zones”, and that the insertion is represented as YYYY-MM-DDT23:59:60Z.
Very little of a typical stack models that. Go’s documentation says its “calendrical calculations always assume a Gregorian calendar, with no leap seconds”; most databases and date libraries take the same position, which is reasonable — a 60th second that exists twice a decade is not something a bet ledger wants to represent. The consequence is that the fleet must choose a policy, and there are two live ones:
- Step. The clock is set back or held for the extra second at the leap. Ordering across that second becomes non-monotonic: two different processes may stamp the same second twice, or skip one. Some subsystems handle this by repeating the last second of the day.
- Smear. The extra second is spread over a window, so that no single jump occurs. Google’s public NTP service describes doing this “since 2008, instead of applying leap seconds to our servers using clock steps”, recommends a “24-hour linear smear from noon to noon UTC”, and states the cost honestly: “Smeared time briefly deviates from UTC during the smear period but realigns after 24 hours.”
The cost matters for ordering. During a smear window a smeared clock and an unsmeared one differ by up to about a second, so two services with different policies can order the same pair of events differently. The practical rules are short: write down which policy the fleet follows, keep every timestamp in the same format either way, make sure anything comparing times from two sources either shares a policy or tolerates a second of disagreement, and make the time daemon’s leap information current — chrony’s leapsectz directive reads the system timezone database to learn when the next leap second occurs, and its documentation notes that when one is announced “the timezone needs to be updated at least 12 hours before the leap second”, with no daemon restart required.
Cut-offs are a stated rule, not a rounding
Every regulated product has cut-offs: the instant a market closes, a bonus period ends, a session expires, a withdrawal window shuts, a reporting day is declared finished. A cut-off that exists only as a comparison somewhere in the code is not a rule; it is a coincidence that holds until someone writes the next service.
A stated cut-off has four properties, and they should be written where the product team, the platform team and the report writer can all read them.
- Which clock. The platform’s own synchronised time, never a client-supplied timestamp. A rule that accepts the device’s clock is a rule the device can move.
- The boundary, exactly. To the second, the millisecond, or the scheduled job’s tick — and inclusive or exclusive at the edge. A half-open interval such as
[open, close)is a decision the API, the UI and the report can each implement identically; “closes at midnight” is not. - What happens to work in flight. A request that arrives before the cut-off and completes after it needs a defined outcome. For wagers, the commitment point is the platform’s accepted instant, and the wager acceptance architecture guide covers why that point has to be the authoritative one rather than the moment the player pressed a button.
- What the player is told. A cut-off that the interface presents differently from the way the platform enforces it produces support contacts, not disputes about milliseconds.
Session expiry deserves its own line, because it is a cut-off that mixes both clocks. A countdown that only has to survive inside one process — an idle warning, a rate-limit window — should run on a monotonic deadline so a clock correction cannot end a session early or extend it. An expiry that must survive a restart, a deploy or a load balancer moving the player to another node has to be stored as an instant with an offset, and its rule has to say what happens when the clock is corrected while sessions are open.
The calendar is data, and the data changes
A business rule expressed in local time is not stable, because local time is defined by data the platform does not control. Two transitions a year make a stated local clock time ambiguous or impossible: an hour that occurs twice, and an hour that does not occur at all. A rule that says “the promotion ends at 02:00 local” needs an answer for the night when 02:00 never arrives.
The timezone database itself is routinely updated as governments change their rules, which has a consequence for anything future-dated: the same rule text renders a different instant after an update. So a future-dated cut-off should be stored as a rule — zone name plus local time — and evaluated with the data version in force, and any scheduled run should record the data version it used. A computed instant frozen a year in advance is a promise about legislation.
Databases add one more silent dependency. PostgreSQL converts between timestamp without time zone and timestamp with time zone by assuming the value “should be taken or given as timezone local time”, and warns that the conversion normally assumes the session’s TimeZone. A query that looks correct therefore changes meaning with a connection setting. The remedy is unglamorous and effective: name the zone explicitly in the query, and keep stored instants unambiguous so the session setting has nothing to reinterpret.
Where the reporting day ends is a decision
Daily and monthly reports have a boundary that is a product decision wearing a technical costume. A UTC day, a regulator’s local day and an operator’s business day are three different spans with three different lengths on a daylight-saving transition — 23 or 25 hours instead of 24 — and the boundary chosen changes which wagers, deposits or bonuses fall into which period.
Pick one, write it down, and publish it beside the numbers. That single line resolves most mismatches before they become tickets: the wallet reconciliation guide already sets reconciliation against the provider’s clock and ledger boundaries, and a report whose day boundary is documented can be compared with a provider’s statement instead of argued with it.
Test the time failures you can reproduce
Time defects are invisible in a test suite that reads the system clock, because the system clock is the thing under suspicion. The first requirement is therefore structural: time enters the code as an injected dependency, and tests supply it. Then the following failures are all reproducible, and each one has a defined expected behaviour rather than a hope.
| Failure | What it stresses | What the platform should do |
|---|---|---|
| Clock stepped forward during an open session | Expiry, rate-limit windows, retry backoff | Work in flight is not silently cancelled or double-counted |
| Clock stepped backward | Log ordering, “elapsed” durations, dedupe windows | No negative durations, no repeated window, ordering still explainable |
| Skew between two services | Event ordering reconstructed by a job | Ordering comes from a record, not from comparing two hosts’ clocks |
| Time source unreachable | Boot and steady state | Behaviour is defined, logged and alerted rather than drifting silently |
| Daylight-saving transition day | Scheduled jobs, local-time rules | A rule that names a zone resolves consistently, not twice or never |
| Leap second, under either policy | Whole-fleet ordering | The fleet agrees on its policy and tolerates the disagreement it implies |
| Suspend or migration | Monotonic intervals | Expired work is not resurrected; a lease does not double-hold |
Two of those tests need something other than a unit test. The behaviour of the platform when its time source is unreachable belongs in the non-production environment that can be made wrong on purpose, and any test that measures duration across a long run has to state which clock it measured with — the load and soak testing guide covers the same discipline applied to capacity evidence, where a wall-clock measurement that drifts invalidates the run it describes. When an incident review reconstructs a timeline, the incident response plan depends on ordering that was recorded correctly before the incident, and the audit evidence guide is where the offset and source expectations belong.
Turn it into acceptance evidence
A buyer, an auditor or a new platform team should be able to answer time questions from documents rather than from interviews. The pack is small enough to assemble in an afternoon and specific enough to be tested:
- The clock source list, the authentication method, the tolerance, and how offset and drift are monitored and alerted.
- The instant format, the offset rule, and where a zone name is retained and why.
- The cut-off register: each boundary, its precision, its inclusive or exclusive edge, the clock that decides it, and the service that enforces it.
- The calendar rule for every scheduled behaviour, with the timezone data version recorded per run.
- The leap-second policy, and the reasoning that accepted its cost.
- The failure tests above, with results, an owner and a date.
- The reporting day boundary, published with the reports it governs.
The reason to spend the afternoon is that time is the only dependency that never gets to be down. It cannot be feature-flagged off, it has no maintenance window, and a platform that has not decided what its timestamps mean will discover the decision during the dispute that needed them. The engineering work of a gambling platform — the platform development discipline of specifying behaviour before building it — applies here as much as anywhere: the rules are cheap to write while the system is quiet.
Questions a platform buyer asks
Does the operating system not take care of the clock?
It keeps the clock broadly right, and that is a narrower job than it sounds. The operating system does not choose the timestamp format, does not decide what a cut-off means at its edge, does not retain the zone a rule referred to, and does not record which policy the fleet followed through a leap second. Those are product and platform decisions, and they are the parts a review will ask about.
Is storing timestamps in UTC enough?
UTC storage is the right default and it is only half of the rule. An instant with an offset is unambiguous; the surrounding context is not automatic. If a rule mentioned a local time, the zone name has to be retained somewhere for the rule to be evaluated after a timezone data update, and the boundary — which clock, which precision, inclusive or exclusive — still has to be stated.
How accurate does a platform’s clock actually need to be?
Tighter than the finest boundary the platform asserts, and no tighter. If a market close is stated to the second, the fleet’s uncertainty has to sit comfortably inside a second; a tolerance looser than the boundary means the boundary is enforced differently on different hosts, which is how two players in the same second get different answers. The right way to set the number is to derive it from the tightest rule, then monitor against it.
Do leap seconds really matter to a casino platform?
Not to the game maths, and yes to the evidence. Most date libraries and databases do not model a 60th second at all, which is a defensible engineering choice. What matters is that the choice is written down and consistent, so that a leap-second advance — or a fleet that follows one policy in one region and another elsewhere — does not leave two records of the same event disagreeing by a second with no document explaining why.








































