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.
High because timezone display bugs produce silent no-shows that cost direct revenue per missed booking and damage customer relationships.
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 })
ID: booking-calendar-availability.availability-logic.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 matching src/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 Intl API 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>
)
}