Use Cases (Updated: 6/2/2026)

Date and Time Handling with Claude Code: A Practical Guide

Use Claude Code to implement timezone-safe date handling, Intl formatting, DST tests, and DB storage policy.

Date and Time Handling with Claude Code: A Practical Guide

Why Date and Time Handling Needs More Than a Prompt

Date and time bugs are easy to miss in review and expensive in production. A screen can look correct in Japan while failing during US daylight saving time, European month-end billing, Indian half-hour offsets, or a server/client timezone mismatch. If you ask Claude Code only to “implement date handling”, it may produce clean formatting while leaving storage policy, API contracts, and boundary tests unclear.

Use Claude Code as a pair engineer for the date/time specification, not only as a code generator. This guide focuses on web apps with reservations, billing, notifications, support SLAs, and dashboards that cross timezone boundaries. For the broader testing workflow, pair it with Claude Code testing strategies. For schema changes, read database migration with Claude Code.

Check official references before implementation. Use MDN Intl.DateTimeFormat for display behavior, TC39 Temporal and MDN Temporal for Temporal status and concepts, PostgreSQL Date/Time Types for database behavior, and Claude Code hooks when you want deterministic checks after edits.

Define the Vocabulary and Storage Policy First

Before asking Claude Code to write code, lock down the words your team uses. Otherwise, review comments drift into vague claims like “we store UTC, so it is safe” or “the product is Japan-only, so JST is fine”.

TermPlain meaningPractical storage rule
instantOne exact moment globally, such as 2026-06-02T00:00:00ZStore as a UTC-based timestamp
local dateA calendar date for a user or business, such as birthday or due dateKeep as YYYY-MM-DD, separate from time
wall clock timeThe time a person sees on the clock, such as a 09:00 meetingStore with an IANA timezone ID
IANA timezoneA region name such as Asia/Tokyo or America/New_YorkDo not replace it with a fixed offset
DSTDaylight saving time; some local days have 23 or 25 hoursTest transition days explicitly

In real projects, put these three rules in AGENTS.md or CLAUDE.md before asking for implementation.

  1. API timestamps for events that already happened must be ISO 8601 strings with Z or an explicit offset.
  2. Future scheduled work must keep localDate, localTime, and timeZone as separate fields.
  3. UI formatting must pass both locale and timeZone to Intl.DateTimeFormat; never rely on runtime defaults.

The boundary should look like this.

flowchart LR
  A["Client: local date/time input"] --> B["API contract: localDate + localTime + timeZone"]
  B --> C["Server: validate and convert when needed"]
  C --> D["Database: instant in timestamptz + original timeZone"]
  D --> E["UI: format with Intl.DateTimeFormat"]
  E --> A

Build a Safe Base with Intl.DateTimeFormat

For display, start with the standard Intl.DateTimeFormat API. MDN describes it as the built-in API for language-sensitive date and time formatting, and it accepts an explicit timeZone. Tell Claude Code not to use toLocaleString() without arguments and not to parse date-only strings with Date when the value means a local calendar day.

The following module is a dependency-free baseline. Put it in src/lib/date-policy.ts and reuse it from UI, API handlers, and tests.

// src/lib/date-policy.ts
export const TIME_POLICY = {
  defaultLocale: 'en-US',
  defaultTimeZone: 'America/New_York',
} as const;

type FormatOptions = {
  locale?: string;
  timeZone?: string;
  includeWeekday?: boolean;
};

function toDate(input: string | Date): Date {
  const date = input instanceof Date ? input : new Date(input);

  if (!Number.isFinite(date.getTime())) {
    throw new Error(`Invalid date value: ${String(input)}`);
  }

  return date;
}

export function toUtcIso(input: string | Date): string {
  if (typeof input === 'string' && !/(Z|[+-]\d{2}:?\d{2})$/i.test(input)) {
    throw new Error('Timestamp must include Z or an explicit UTC offset.');
  }

  return toDate(input).toISOString();
}

function dateParts(date: Date, timeZone: string): Record<string, string> {
  const parts = new Intl.DateTimeFormat('en-US', {
    timeZone,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  }).formatToParts(date);

  return Object.fromEntries(
    parts
      .filter((part) => part.type !== 'literal')
      .map((part) => [part.type, part.value]),
  );
}

export function dayKeyInTimeZone(
  input: string | Date,
  timeZone = TIME_POLICY.defaultTimeZone,
): string {
  const parts = dateParts(toDate(input), timeZone);
  return `${parts.year}-${parts.month}-${parts.day}`;
}

export function formatInstant(
  input: string | Date,
  {
    locale = TIME_POLICY.defaultLocale,
    timeZone = TIME_POLICY.defaultTimeZone,
    includeWeekday = true,
  }: FormatOptions = {},
): string {
  return new Intl.DateTimeFormat(locale, {
    timeZone,
    weekday: includeWeekday ? 'short' : undefined,
    year: 'numeric',
    month: 'short',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    hourCycle: 'h23',
    timeZoneName: 'short',
  }).format(toDate(input));
}

The point is not to hide every date operation behind a utility. The point is to separate an instant, a local-day key, and a display label. Before Claude Code adds another helper, ask it to explain which category the new requirement belongs to.

Convert Future Local Times Explicitly

Intl.DateTimeFormat is good for output, but it is not the right tool for turning 2026-11-01 01:30 America/New_York into a safe UTC instant. For that job, choose one project-level library and verify the current docs. Luxon has explicit timezone APIs documented in its API docs. date-fns is strong for function-based date utilities, but timezone requirements should be checked against its current official docs. Temporal is Stage 4 at TC39, but you still need to check runtime support and any polyfill plan for your target browsers and Node version.

For a booking form, convert the user’s local date and time into the instant you store.

// src/lib/schedule-time.ts
import { DateTime } from 'luxon';

type LocalScheduleInput = {
  localDate: string; // YYYY-MM-DD
  localTime: string; // HH:mm
  timeZone: string; // IANA time zone, for example "America/New_York"
};

export function scheduleToUtcIso(input: LocalScheduleInput): string {
  const rawLocal = `${input.localDate}T${input.localTime}`;
  const local = DateTime.fromISO(rawLocal, { zone: input.timeZone });

  if (!local.isValid) {
    throw new Error(local.invalidExplanation ?? `Invalid local time: ${rawLocal}`);
  }

  const roundTrip = local.setZone(input.timeZone).toFormat("yyyy-MM-dd'T'HH:mm");
  if (roundTrip !== rawLocal) {
    throw new Error(`Nonexistent local time in ${input.timeZone}: ${rawLocal}`);
  }

  const iso = local.toUTC().toISO({ suppressMilliseconds: true });
  if (!iso) {
    throw new Error(`Could not convert local time to UTC: ${rawLocal}`);
  }

  return iso;
}

This helper does not magically solve the repeated 01:00 hour when daylight saving time ends. Your product still needs a rule: choose the earlier offset, choose the later offset, or ask the user. Tell Claude Code to add tests for nonexistent local times, ambiguous local times, month ends, and leap days.

Separate Server, Client, and Database Boundaries

The most common bug is passing a value to Date without deciding whether the server or the browser timezone owns the interpretation. Browser inputs, API payloads, database columns, emails, and CSV exports are different boundaries.

Use at least these three concrete use cases when reviewing the design.

  • Booking system: input uses the venue’s local time, storage uses a UTC instant, display uses either the viewer’s or venue’s timezone.
  • Billing cutoff: “last day of the month at 23:59” is a local-date rule in the customer’s contract timezone; do not create next month by adding 24-hour chunks.
  • Support SLA: business days, holidays, and office hours vary by region, so keep both the deadline instant and the human-readable local context.

With PostgreSQL, the official docs explain that timestamp with time zone input is converted to UTC and the original timezone is not retained. That means timestamptz alone cannot tell you that a user selected America/New_York. Store that separately when the product needs it.

create table scheduled_events (
  id uuid primary key,
  title text not null,
  starts_at timestamptz not null,
  original_time_zone text not null check (original_time_zone <> ''),
  local_date date not null,
  local_time time not null,
  created_at timestamptz not null default now()
);

create index scheduled_events_starts_at_idx
  on scheduled_events (starts_at);

Do not assume that inserting 2026-06-02T09:00:00+09:00 into a timestamp without time zone column keeps the offset meaningful. PostgreSQL determines the type first; for a value already determined to be without time zone, the timezone indication is ignored. Add “datetime column type matches the API contract” to Claude Code’s review checklist.

Write DST and Boundary Tests with Fixed Instants

Tests should not depend on “today”, the current clock, or the developer machine’s timezone. Fix the input as a UTC instant, then assert the expected local day or label. If you use Vitest, start with this file. For broader patterns, see Claude Code Vitest advanced.

// src/lib/date-policy.test.ts
import { describe, expect, it } from 'vitest';
import { dayKeyInTimeZone, formatInstant, toUtcIso } from './date-policy';

describe('date/time policy', () => {
  it('requires an explicit offset for API timestamps', () => {
    expect(() => toUtcIso('2026-06-02T09:00:00')).toThrow(/offset/);
    expect(toUtcIso('2026-06-02T09:00:00+09:00')).toBe('2026-06-02T00:00:00.000Z');
  });

  it('calculates a local day key across the UTC date boundary', () => {
    expect(dayKeyInTimeZone('2026-03-31T15:01:00Z', 'Asia/Tokyo')).toBe('2026-04-01');
    expect(dayKeyInTimeZone('2026-04-01T00:30:00Z', 'America/Los_Angeles')).toBe('2026-03-31');
  });

  it('formats a DST transition instant in the requested time zone', () => {
    const label = formatInstant('2026-03-08T07:30:00Z', {
      locale: 'en-US',
      timeZone: 'America/New_York',
    });

    expect(label).toMatch(/03:30|3:30/);
    expect(label).toMatch(/EDT|GMT-4/);
  });
});

The test intentionally allows either EDT or GMT-4, because ICU data can differ between environments. Local-day keys and UTC conversion results are business logic, so those expectations should stay strict.

Prevent Regressions with Claude Code Prompts and Hooks

Give Claude Code the constraints before implementation. A longer prompt is fine when it names the dangerous boundaries.

Before implementing date/time changes, read date-policy.ts and the database schema.

Constraints:
- API timestamps for completed events must be ISO strings with Z or an explicit offset
- Future bookings must keep localDate, localTime, and timeZone separate
- Intl.DateTimeFormat must always receive locale and timeZone
- Do not use new Date('YYYY-MM-DD') for local calendar dates
- Add Vitest cases for DST start, DST end, month end, and leap day
- Explain the PostgreSQL timestamp/timestamptz difference in the review notes

Done when:
- npm test -- --run date-time passes
- Updated API response examples are added to README

For teams, use Claude Code hooks to run deterministic checks after edits. The official docs show that hooks can run shell commands at lifecycle events. A date/time test hook can live in .claude/settings.json. For the full workflow, see Claude Code hooks guide.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npm test -- --run date-time"
          }
        ]
      }
    ]
  }
}

Choose Libraries by the Type of Time You Handle

Do not choose a date library only by popularity. Choose it by the kind of time your product handles. Use Intl.DateTimeFormat for locale-aware display, date-fns for focused date utilities, Luxon for IANA timezone scheduling, and Temporal when you want future-facing types such as Instant, PlainDate, and ZonedDateTime. Even with Temporal at Stage 4, confirm target runtime support and polyfill policy before asking Claude Code to rewrite production code.

OptionBest fitCaveat
IntlLocale-aware display with explicit timezone formattingNot a local-time-to-instant conversion tool
date-fnsPure date math, function-level imports, small utilitiesCheck current official docs for timezone needs
LuxonIANA timezone scheduling, relative time, ISO conversionAmbiguous DST times still need product rules
TemporalClear separation of Instant, PlainDate, and ZonedDateTimeRuntime compatibility and polyfill plan matter

From a content and monetization angle, a generic library list is weak. Readers need to know what to store, where to convert, and what to test. If your team wants this policy embedded into CLAUDE.md, hooks, PR review, and migrations, the Claude Code training and consultation page is the natural next step. Solo developers can add the prompt above to the free cheat sheet and use it as a repeatable guardrail.

Hands-On Verification Note

Masa’s verification note: the most useful check was fixing a UTC instant and formatting it in Tokyo, New York, and Los Angeles, then asserting the local-day key across date boundaries. The easiest bug to reproduce was treating YYYY-MM-DD as a user’s local date, converting it to Date, and then seeing the previous day in another timezone. The policy in this article keeps local dates as strings and converts only stored instants and display labels, which makes Claude Code’s diff much easier to review.

#Claude Code #date #timezone #Intl #testing
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.