skip to content

How do you decide whether Go services embed time/tzdata or rely on the image's zoneinfo?

level: principalimportance: nice to knowfreq 22%

answer

  1. the same data, from inside or outside the artefact
  2. who ships the fix when a country changes its rules
  3. an undeclared dependency on the base image
  4. the embedded copy is pinned to the toolchain
  5. fail at startup rather than degrade to UTC

basics

~20 s

Decide by who must fix a wrong zone rule, and how fast. Embedding time/tzdata makes a binary self-contained but pins zone data to the toolchain that built it; relying on the base image lets the platform team patch it without a rebuild.

solid answer

~50 s

There are three viable postures and the choice is about ownership, not taste. Blank-importing `time/tzdata` in `main` costs roughly 450 KB and makes the binary work anywhere, with data pinned to the Go release that built it — so refreshing zone rules means a toolchain bump and a rebuild that the service team owns. Relying on the base image's zoneinfo keeps binaries small and lets the platform team ship a zone-data patch to every service without recompiling, but it is an invisible dependency: an image change can silently turn every local time into UTC. Embedding a pinned database and calling `time.LoadLocationFromTZData` gives the tightest control at the price of owning updates yourself. Whichever you pick, make it a written default, verify it in the build, and fail at startup rather than falling back to UTC.

go deeper

for a junior

Know that zone data can either be compiled into the binary with a blank import of time/tzdata or come from files in the environment the binary runs in.

for a middle

Be able to compare the two: binary size and a toolchain-pinned copy on one side, a smaller binary and an unstated dependency on the runtime image on the other.

for a senior

Show how you would de-risk either choice — startup validation of every configurable zone, no silent UTC fallback, and a test that runs the real artefact in the real image with no zone files present.

for a principal

Own the call across teams: state a default and its exception, agree with whoever owns the base image which posture it supports, and be explicit about who ships a fix when a region changes its DST rules.

## What is actually being decided A Go binary that renders local times needs the IANA time zone database at run time. It can come from inside the binary or from outside it, and that is a decision with an owner, a cost and a failure mode — not a style preference. Two teams have a claim on it: the service team that ships the binary and gets paged when appointments are an hour out, and the platform team that owns the base image and would like to patch zone data centrally. Spell out the three postures before arguing about them. ### Posture 1 — embed the database `import _ "time/tzdata"` in the `main` package embeds a full copy, roughly 450 KB, used whenever system zone files are absent. - **Pro:** the binary is self-contained. It behaves identically on a laptop, in CI, and in a minimal image with nothing else in it. Zone behaviour becomes a property of the artefact you tested, which is the strongest argument in its favour. - **Con:** the embedded copy is fixed at build time and travels with the Go release you built with. A country moving its DST rules means updating the toolchain and rebuilding — the service team's work, on the service team's release cadence. - **Note:** the guidance is explicit that `main` imports it and libraries do not, so this is a per-binary decision by design. ### Posture 2 — rely on the image The base image ships an OS zone package; the binary just calls `time.LoadLocation`. - **Pro:** binaries stay small, and zone data can be refreshed by rebuilding images — no application release, no toolchain bump, and one team can do it for the whole fleet. - **Con:** it is an undeclared dependency. Nothing in the repository states that the image must contain zone files, so a slimming change to the base image removes them and every service quietly renders UTC. The blast radius is the whole fleet and the signal is zero. ### Posture 3 — pin your own data `//go:embed` a database or the specific zones you need and build locations with `time.LoadLocationFromTZData`. - **Pro:** the exact bytes are under version control and reviewable; the binary stays small if you ship only the zones you support. - **Con:** you now own sourcing, verifying and refreshing that data, and any zone outside the set fails. Worth it when the set of zones is genuinely small and fixed, or when auditability of the data matters. ## The questions that decide it **How wrong can a stale or missing zone be for this product?** For a scheduling service the answer is "an hour, silently, for a subset of users" — bad enough that reproducibility beats central patching, which argues for embedding. For a service that only formats logs, UTC everywhere is fine and the whole discussion is moot. **Who can ship a fix, and how fast?** Governments change DST rules with weeks of notice. If your release cadence is daily, embedding is fine; if a rebuild takes a change-approval window, the image route puts the fix in the hands of a team that can move. **Can the dependency be made visible?** Posture 2 is defensible only with an explicit contract — the image documents that it ships zone data, and something in the build or a startup check proves it. Without that, the platform team can break every service without knowing the dependency existed. **How much does 450 KB cost here?** Usually nothing. Argue about it only where image size or memory per instance is a measured constraint, not as a reflex. ## The parts that are not a trade-off A few things hold whichever posture wins, and they are what actually stops the outage: - **Fail at startup.** Load every zone the service can be configured with at boot and refuse to start if one fails. There is no useful degraded mode for a service that renders local times. - **Never substitute UTC on error.** Silent fallback converts a loud failure into wrong data that looks right. - **Test the failure.** A test with `ZONEINFO` pointed at a nonexistent path exercises the error branch; running the release artefact in its own image catches the missing-database case where it actually happens. - **Keep libraries out of it.** Shared packages report zone errors upward; only the binary's `main` decides where data comes from. ## Writing it down The deliverable is a one-line default with a stated exception, not a document: *services that render user-local time blank-import `time/tzdata` in `main` and validate their zones at startup; services that only emit UTC do neither.* Put the check in the shared build so drift is caught mechanically, and agree with the platform team which posture the base image is expected to support — because the failure of an unwritten assumption here is discovered by a user, the morning after a transition, and never by a test.

  • What actually refreshes the zone data embedded by time/tzdata?
    Building with a newer Go release that carries a newer copy, then redeploying. Nothing is fetched at run time and no configuration changes it, so zone-rule updates arrive on your toolchain-upgrade cadence — which is the main argument against embedding when rules change on short notice.
  • How would you make a dependency on the base image's zone files visible rather than implicit?
    Assert it: a startup check that loads every supported zone and refuses to run on failure, plus a build- or release-time test that runs the real artefact in the real image. That turns an unwritten assumption into a failure the platform team sees before the fleet does.
  • A platform team wants one fleet-wide answer; the scheduling team wants control. How do you settle it?
    Split by consequence. Services rendering user-local time embed the database, because reproducibility of the tested artefact outweighs central patching; everything else takes the image default. Both sides get the same non-negotiables: validate zones at startup, never fall back to UTC, keep the decision in `main`.

It is the difference between a ship carrying its own charts and trusting the harbour to hand them over: one is heavier and slower to update, the other depends on a promise nobody wrote down.

saying these in an interview costs you the question

  • Treats it as a style choice with no owner
  • Blank-imports time/tzdata from a shared library to settle it fleet-wide
  • Assumes embedded zone data updates itself at run time
  • Argues the 450 KB matters without measuring anything
  • Leaves the dependency on the image's zone files unwritten
  • Accepts a UTC fallback as a reasonable degraded mode