Dates and times: pickers, zones and the lies of relative time

A date is the most dangerous simple thing in software. It looks like a string, behaves like a coordinate in four dimensions (calendar, clock, timezone, culture), and fails silently — the worst failure mode — because a date that's an hour or a day off still looks like a date. Your booking system happily confirms it, and the customer finds out at the airport.

We've designed date and time UX for travel booking, telehealth, restaurant reservations and payroll-adjacent SaaS. Every single project produced at least one DST or timezone incident after launch, usually about five months later when the clocks changed. This is what we now specify up front.

When the calendar grid wins — and when it's a tax

The calendar grid (the little month popover) is the default date input on the web, and it's the wrong default more often than it's right. The decision rule is spatial: a grid earns its place when the user thinks in the shape of a calendar. Booking a Friday-to-Sunday weekend away, picking a delivery day, scheduling around "not during school holidays" — these are spatial questions. The grid answers them because it shows the week structure, and because you can annotate it: prices per day, availability, blackout dates.

Free typing wins when the date is known, not chosen. A date of birth. An invoice date copied from a document. An expiry date on a card. Forcing someone to navigate a grid back to 1987 — discovering the year dropdown only after thirty clicks of the back chevron — is one of the web's most reliable humiliations. A masked text input with generous parsing ("3 feb 87" works) beats it every time.

The honest third option, which we now reach for first in forms: segmented fields with a grid available. Day, month, year as separate inputs with numeric keyboards on mobile, plus an affordance to open a grid when the user does think spatially. It matches the evidence in the user's hand (their passport, their memory) and never punishes them for knowing their own birthday. It also plays well with the autocomplete discipline from our forms piece — bday-day, bday-month, bday-year are free accuracy.

Two grid rules that survive every project:

  • Show two months on desktop. Choosing a range across a month boundary with one month visible is a memory test, not a date picker. (On mobile, one month, scroll continuous.)
  • The disabled date needs a reason. Grey out 14 October, fine — but why? "Fully booked" or "Closed Mondays" as hovers/labels turns a dead end into information. On the Wattle & Daub reservations work, annotated availability ("3 tables left" under a date) measurably reduced date-flapping — users cycling through days hoping. Illustrative, but the pattern repeated across projects.

Timezones: write the display rules down before you write the code

Every team says "we'll store UTC and display in local time" and believes the sentence is a design. It is not. "Local" has at least three meanings in a multi-user product, and they're frequently different:

  1. The viewer's zone — where the person reading the screen is right now.
  2. The event's zone — where the thing happens (the restaurant, the flight's departure airport, the physical office).
  3. The account's zone — the organisation's home zone, used for reporting and payroll-esque aggregation.

A booking platform needs the event's zone, always, with the viewer's zone only as an optional translation. A business dashboard needs the account's zone for every aggregate, because "Tuesday's revenue" split across the viewer's local Tuesdays is a number finance cannot use. A distributed team's meeting scheduler needs both sides of the comparison at once.

The display contract we ship with every time-bearing product:

  • A time shown with its zone, in words, at decision points. Confirmation screens, emails, calendar files — anywhere a human commits — say "6:30 pm Sydney time (AEST)". Persistent UI chrome can abbreviate once the pattern is established; moments of commitment never do.
  • Ambiguous moments get dual rendering. If a Sydney user books a 9 am slot in Auckland for early November — when the two zones are temporarily two hours apart instead of the usual... no, wait, when DST transitions shift the gap — show both: "9:00 am Auckland / 7:00 am Sydney". Yes, it's clutter. It's the kind of clutter that prevents missed meetings.
  • Round-trip integrity. If the user picked "next Friday at 3 pm" while travelling, and receives a calendar invite that says a different time than the confirmation screen did, trust in the product's clocks is gone for good. One canonical instant, rendered per context, never re-interpreted.

The engineering half of this — wall-clock-plus-zone storage, Intl as the only formatting path, DST edge tests — we cover in the internationalization architecture piece. The design half is simpler: decide whose clock the product speaks in, per surface, and write it in the spec.

Relative time is a decaying asset

"2 hours ago" is delightful in a feed and a lie generator in an audit trail. Relative timestamps optimise for glanceability and spend down precision — and the exchange rate changes with the age of the event and the stakes of the screen.

Our rule: relative time has a shelf life, after which it must decay to absolute. Within a day, relative is usually better ("12 minutes ago"). Beyond a few days, absolute wins on honesty: "3 weeks ago" makes people do calendar arithmetic nobody enjoys, and the arithmetic is exactly where mistakes live. The crossover for most products sits between 48 and 72 hours.

But three places never get relative time, regardless of age:

  • Audit logs and activity feeds of record. "Deleted 3 hours ago" is useless at 2 am incident review when the question is "before or after the deploy at 14:32?" The activity feed grammar requires absolute timestamps; relative can ride along as a parenthetical, never as the primary.
  • Anything with a deadline. "Expires in 2 days" — which 2 days, counting from when, in whose timezone? Deadlines are absolute or they're wishful: "Free cancellation until 14 Oct, 3:00 pm (hotel time)".
  • Hover targets. Every relative timestamp carries its absolute form on hover/focus, and the absolute is in the DOM, not computed on demand — screen readers get the precise version by default. This is a one-line title-adjacent pattern and there is no excuse for skipping it.

Recurring events: the scope question is the whole feature

"Every Tuesday at 10" is a sentence a human says and a data structure a machine chokes on, because the first edit asks a question the naive model can't answer: which Tuesdays are you changing?

Any recurring-event editor must ask the scope question — this one, this and following, or all — and the asking is a UX design problem, not a copy afterthought. Three rules from scar tissue:

  • Ask at save, not at click. A dialog that interrupts "drag the event to Thursday" with "which events?" before the edit is made is asking about an intention the user hasn't confirmed yet. Let them make the edit; ask the scope question when they save, with the consequences visible.
  • The options must show the truth. "This and following events (23 events)" is honest. "This and following" is a guess the user makes blind. Count them; if counting is expensive, your recurrence model is wrong.
  • Exceptions are first-class. The instance you moved to Thursday for a public holiday is an exception to the series, and it must stay visible as one — on Sundial's travel-planning surface we rendered exceptions with a small mark and a "deviates from series" note, because the silent fork (where the exception quietly stops tracking series changes) caused a real almost-missed-flight in user research. Illustrative scenario, real design rule.

And the DST footnote that bites every recurring-event system eventually: a 9 am recurrence stored as "86400×n seconds after a UTC instant" shifts an hour across a clock change. A 9 am recurrence stored as "9 am wall clock, Australia/Sydney" does not. Store the human's intent — wall time plus zone — and compute instants per occurrence. The user said nine o'clock. Ship nine o'clock.

The input details that pay rent

Small things, cumulative trust:

  • Date math in plain language near constraints. "Must be at least 18: born on or before 7 Oct 2007" beats "Invalid date" by exactly the distance between a ticket and a shrug. (Same philosophy as errors that de-escalate, applied to the fourth dimension.)
  • Ranges that survive reversal. If the end date is set first and the user then picks a later start, swap silently. Do not clear their end date and make them redo it.
  • Duration alternatives. Next to range pickers in travel and booking flows: "3 nights" as a first-class input. People plan in durations; calendars store ranges; good UI translates, as it did across the Sundial booking flow.
  • Day names on near-future dates. "Tue 14 Oct" orients faster than "14/10/2025" and catches the classic off-by-one-week error. Under two weeks out, the weekday is the more load-bearing fact.

Key takeaways

  • Calendar grids for chosen dates (bookings, delivery), segmented typing for known dates (birthdays, document dates); never make anyone grid-navigate to their birth year.
  • "Local time" has three meanings — viewer, event, account. Decide per surface, write it in the spec, and dual-render zones at moments of commitment.
  • Relative timestamps decay to absolute after 48–72 hours and are banned outright in audit trails, deadlines, and anywhere a screen reader lands.
  • Recurring editing is a scope question — this one / this and following / all, asked at save with honest counts — and recurrence means wall time plus zone, never seconds-from-epoch.
  • Disabled dates need reasons; deadlines say whose clock they run on; day names beat numerals in the near future.

FAQ

A native date input or a custom picker? Native <input type="date"> on mobile is genuinely good — the OS wheels beat anything you'll build. On desktop it's weaker (browser grids vary, styling is hostile), so: native on mobile, a custom-but-accessible grid-plus-typing hybrid on desktop, both funnelled into the same validated value. Testing both paths in CI is the tax; pay it.

How do we handle users manually entering ambiguous formats like 06/07/2025? You can't disambiguate 06/07 against willpower. Use the locale's dominant order with an immediate echo-back — as they type, render "6 July 2025" beside the field. If the user meant June they'll see July and fix it in the moment that costs nothing, instead of in a booking email that costs plenty.

Should we show seconds? In consumer products, no — seconds are false precision that erodes trust in the hours. In operational and audit surfaces, yes, with monospaced tabular numerals so the column doesn't jitter. Precision is a register: match it to what the reader will do with the number.

What timezone should system-generated emails use? The recipient's, if you know it and the event is location-independent; the event's zone, with the zone named in words, if it isn't. Never the sender's unnamed zone — "Your appointment is at 2pm" from a Sydney server to a Perth customer is how no-shows are manufactured.

Is it ever OK to show times without any zone? Inside a single physical context everyone shares — the kitchen display in the restaurant, the departure board in the airport — the zone is ambient and naming it adds noise. The moment the information travels (email, calendar invite, API), the zone travels with it.


Keep reading