Skip to main content

How to Test Dates, Timezones, and Scheduling

Short answer

Date and timezone bugs shift appointments an hour, bill on wrong days, and pass CI in UTC while failing for Sydney users. Use Playwright clock.install() for deterministic instants, test DST spring/fall boundaries explicitly, and probe stored UTC values—not localized display strings alone.

Part of Testing Guides by UI patterns.

Who this is for

Teams shipping scheduling, billing cycles, SLA timers, subscription renewals, or global dashboards where wall-clock time matters. Typical stacks: date-fns/Luxon/Day.js, browser Intl, backend UTC storage with locale display.

Why testing dates and timezones matters

Time bugs are silent until calendar edges:

  • DST transitions — 2 AM spring forward removes an hour; fall back creates duplicate local times; appointments double-book or vanish.
  • Billing errors — invoice generated a day early for APAC users; churn disputes spike.
  • Compliance — retention "delete after 30 days" fires at wrong instant; audit timestamps wrong timezone in exports.
  • CI false confidence — GitHub Actions runs UTC; prod users in America/Chicago hit untested paths.
  • Locale displayMM/DD vs DD/MM misread in forms; wrong date stored.

Freeze time with Playwright clock and assert probe UTC ISO strings plus selected locale display.

Complexity map

ScenarioEdge caseWhy tests breakApproach
DST spring forwardMissing hourSlot picker skipsclock at Mar 10 2024 US
DST fall backDuplicate 1:30 AMWrong slot storedTwo bookings probe
UTC storageLocal display onlyOff-by-one dayProbe 2024-03-15T00:00:00Z
User TZ preferenceAccount vs browserMismatchSeed user TZ; set context TZ
All-day eventsMidnight boundarySpans two UTC daysProbe date-only field
Relative labels"Tomorrow"Changes at midnightclock.tick(24h)
Billing anchorMonth-end Feb 29Skipped renewalclock at leap year
Scheduler cronJob at 00:00 UTCNever runs in testTrigger job route
Date picker widgetPortal in overlayClick missgetByRole gridcell
ISO inputtype vs pickerPartial datekeyboard fill + blur
Range pickersEnd before startValidation gapCross-field probe
Server/client skewNTP driftToken expiry flakeclock on both sides

Playwright clock setup

import { test, expect } from '@playwright/test';

test('books slot across DST spring forward', async ({ page, context }) => {
await context.clock.install({ time: new Date('2024-03-09T10:00:00-06:00') }); // America/Chicago
await page.emulateMedia({ reducedMotion: 'reduce' });
// Seed calendar with slots in America/Chicago
await page.goto('/schedule');
await page.getByRole('button', { name: 'Mar 10, 2:30 AM' }).click(); // may not exist — negative
await page.getByRole('button', { name: 'Mar 10, 3:30 AM' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();
const booking = await request.get(`/api/test/booking?runId=${runId}`).then(r => r.json());
expect(booking.startsAtUtc).toMatch(/2024-03-10T08:30:00/);
});

Advance time for relative UI:

await context.clock.runFor(86_400_000); // +24h
await expect(page.getByText('Due today')).toBeHidden();

See Playwright clock docs.

Timezone matrix (prioritize by TrueCoverage)

DimensionExample values
timezoneAmerica/New_York, Europe/London, Australia/Sydney
localeen-US, en-GB, de-DE

Compare prod user timezone distribution to test runs. If 25% of prod sessions are Australia/Sydney but tests only use UTC, run /testchimp evolve for Sydney DST scenarios—link SmartTests with // @Scenario: (requirement traceability).

Date picker interaction

await page.getByLabel('Start date').click();
await page.getByRole('gridcell', { name: '15' }).click();
await page.getByLabel('Start date').blur();
const probe = await request.post('/api/test/parse-date-field', {
data: { runId, field: 'startDate', display: '03/15/2024' },
});
expect((await probe.json()).utc).toBe('2024-03-15T00:00:00.000Z');

Prefer probe parsing over visible text when formats vary by locale.

CI checklist

  1. clock.install before navigation for time-sensitive specs
  2. At least one DST boundary spec per supported region
  3. Probe UTC fields on create/update
  4. Set timezoneId in Playwright config project for locale-critical apps
  5. Trigger cron via test route instead of waiting real hours
  6. Pair with calendar scheduling guide for slot availability

Anti-patterns

Anti-patternWhy it failsBetter approach
new Date() in assertsNondeterministicclock.install
Assert localized string onlyLocale driftProbe UTC
Skip DSTHour-shift prod bugsBoundary fixtures
Real setTimeout for cronSlow CITest job trigger
Single UTC projectHides TZ bugsTZ project matrix
Hard-code US holidays onlyGlobal gapsTrueCoverage TZ slices

Example scenario

Situation: Sydney user books telehealth slot on DST transition day.

Expected outcome: Stored UTC instant matches clinician availability; no duplicate local hour.

Why UI-only automation breaks: UI shows 2:30 AM slot; probe stores wrong UTC—calendar off one hour.

  1. Arrange: clock at 2024-10-05 Australia/Sydney; seed provider availability.
  2. Act: Select slot on Oct 6 DST change; confirm booking.
  3. Assert: Probe startsAtUtc matches expected offset; conflict probe shows single booking.

TestChimp workflow: Instrument booking_complete with timezone; expand DST specs when prod timezone slice lacks test coverage.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Connect scenarios to your QA workflow

Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).

External references

Frequently asked questions

How do I test DST transitions in E2E?

Install Playwright clock at instant before DST change in target timezone, select slots across boundary, probe stored UTC. Include spring forward (missing hour) and fall back (ambiguous hour) where applicable.

Should I assert displayed date or server UTC?

Both for display-critical UX, but probe UTC is authoritative for billing and scheduling. Display strings change with locale.

How do I set user timezone in Playwright?

Seed user profile timezone in Arrange, set browser context timezoneId in project config, and install clock for wall-time UI like relative labels.

How do I test cron or scheduled jobs?

Expose test-only trigger route for job runner, advance clock with clock.runFor or jump to next cron instant, probe side effects—never wait real hours in CI.

How do date pickers differ from text inputs?

Pickers use gridcell roles and portals—click open, select cell, blur. Text inputs need locale-specific typing and probe parse endpoint.

Which timezones should I prioritize?

Use TrueCoverage prod timezone distribution—cover top 3 zones plus one DST-heavy region your prod chart shows undertested.

How does TestChimp evolve help timezone coverage?

When TrueCoverage shows booking spikes in untested zones, evolve adds SmartTests with clock fixtures and // @Scenario: links in compliance plans.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo