Calendar displays appointment times in customer's local timezone
Why it matters
Displaying all appointment times in a single server timezone (typically UTC or the business owner's zone) forces customers to do mental arithmetic and guarantees no-shows. A customer in EST booking a slot labeled '14:00 UTC' will arrive at 2 PM local time, six hours late. This directly destroys revenue per missed appointment, generates support tickets, and violates baseline WCAG 3.1.4 abbreviation expectations when timezone labels are absent. High-volume booking platforms lose thousands of dollars weekly to this single defect.
Severity rationale
High because timezone display bugs produce silent no-shows that cost direct revenue per missed booking and damage customer relationships.
Remediation
Detect the viewer's timezone with Intl.DateTimeFormat().resolvedOptions().timeZone, then convert every stored UTC timestamp through date-fns-tz before render, and always display the zone abbreviation alongside the time. Update every appointment display component (calendar, confirmation page, email template). See src/components/AppointmentDisplay.tsx:
const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone
const zoned = toZonedTime(appointment.start_time, userTz)
format(zoned, 'MMM d h:mm a zzz', { timeZone: userTz })
Detection
-
ID:
customer-timezone-display -
Severity:
high -
What to look for: Search for timezone detection code:
Intl.DateTimeFormat().resolvedOptions().timeZone, timezone selector dropdowns, or explicit timezone parameters in API calls. Count all locations where appointment times are displayed to the user (calendar slots, booking confirmation, appointment details). For each, verify the display converts UTC stored times to the viewer's local timezone using an IANA library. Examine files matchingsrc/components/*Appointment*,src/components/*Calendar*,src/components/*Booking*,src/app/*booking*. -
Pass criteria: Count all appointment time display locations. 100% of display locations must convert stored UTC times to the viewer's local timezone. At least 1 timezone detection mechanism must exist (browser
IntlAPI or user-selected timezone). The timezone label (e.g., "EST", "PST", "zzz" format) must be visible alongside displayed times. Report: "X of Y time display locations convert to local timezone. Detection method: [Intl/selector/none]." -
Fail criteria: All viewers see times in a single timezone (e.g., all UTC), regardless of their local timezone, or timezone labels are absent from displayed times.
-
Skip (N/A) when: Platform operates in a single timezone or explicitly does not support customers in multiple timezones (e.g., local-only business).
-
Detail on fail: Example:
"All times displayed in UTC. A customer in EST books a 9 AM slot and sees 'Confirmed: 2 PM UTC' in their confirmation email, causing confusion." -
Cross-reference: Check
utc-storage— UTC storage is the prerequisite for correct timezone display conversion. -
Cross-reference: Check
iana-timezone-library— display conversion must use an IANA-aware library, not manual arithmetic. -
Cross-reference: Check
confirmation-calendar-parity— the confirmation must use the same timezone as the calendar display. -
Remediation: Detect user timezone and convert for display:
import { toZonedTime, format } from 'date-fns-tz' export function AppointmentDisplay({ appointment }) { const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone const zonedTime = toZonedTime(appointment.start_time, userTz) return ( <div> <p> Appointment: {format(zonedTime, 'MMM d, yyyy h:mm a zzz', { timeZone: userTz, })} </p> </div> ) }
External references
- wcag:2.2 · 3.1.4 — Abbreviations
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-calendar-availability·automated