# Timezone conversion uses IANA library; no manual offset arithmetic

- **Pattern:** `ab-000529` (`booking-calendar-availability.availability-logic.iana-timezone-library`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000529
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000529)

## Why it matters

Manual offset arithmetic like `date.getTime() + 5 * 3600000` hard-codes a fixed UTC offset and silently breaks twice a year during DST transitions. CWE-682 (Incorrect Calculation) and ISO 25010 functional correctness both flag this: New York is UTC-5 in January and UTC-4 in July — a hard-coded `-5` produces a one-hour appointment time error for every booking during EDT. IANA libraries carry the full historical and future timezone rule database; manual arithmetic carries none of it. A single DST-related booking error can generate dozens of no-shows before anyone notices the pattern.

## Severity rationale

High because manual offset arithmetic silently produces one-hour appointment time errors on every DST transition, affecting all bookings during a roughly five-month EDT window without any visible error.

## Remediation

Replace all manual offset arithmetic with calls to an IANA-aware library. Install `date-fns-tz` (or `luxon`) and use its conversion functions exclusively throughout `src/lib/`.

```ts
// Bad — breaks during DST
const local = new Date(utc.getTime() - 5 * 3_600_000) // hardcoded EST offset

// Good — IANA library handles DST automatically
import { toZonedTime, fromZonedTime } from 'date-fns-tz'

const tz = 'America/New_York'
const localTime  = toZonedTime(utcDate, tz)   // display: UTC → local
const utcTime    = fromZonedTime(localDate, tz) // store:   local → UTC
```

Search `src/` for the pattern `* 3600000` or `* 3_600_000` near timezone variables and replace each instance. Confirm `date-fns-tz` or equivalent appears in `package.json` dependencies, not just `devDependencies`.

## Detection

- **ID:** `iana-timezone-library`
- **Severity:** `high`
- **What to look for:** Count all timezone conversion calls in the codebase. Search `package.json` dependencies for IANA-aware libraries: `date-fns-tz`, `luxon`, `moment-timezone`, `@date-fns/timezone`, `dayjs-plugin-utc`, `dayjs-plugin-timezone`. Also search all source files for manual offset arithmetic patterns: `getTime() + ... * 3600000`, `getHours() + offset`, `setHours(hours + tz)`, or any multiplication by `60 * 60 * 1000` near a timezone variable. Examine files matching `src/lib/*timezone*`, `src/lib/*date*`, `src/utils/*time*`, and all files importing `Date`.
- **Pass criteria:** Count all timezone conversion call sites. At least 1 IANA-aware library must be listed in `package.json` dependencies (e.g., `date-fns-tz`, `luxon`, `moment-timezone`). No more than 0 manual offset arithmetic patterns may exist (e.g., `getTime() + hours * 3600000`). 100% of timezone conversions must use the IANA library. Report: "IANA library: [name]. Manual arithmetic instances: [count]."
- **Fail criteria:** Timezone conversions are done manually with offset arithmetic (e.g., `date.getTime() + hours * 3600000`), or no IANA-aware timezone library is in `package.json`.
- **Do NOT pass when:** An IANA library is installed in `package.json` but manual offset arithmetic also exists elsewhere in the codebase. The presence of the library alone is insufficient — it must be used consistently everywhere.
- **Skip (N/A) when:** Platform does not handle multiple timezones and has no timezone conversion code.
- **Detail on fail:** Example: `"Code does: const estTime = new Date(utcTime.getTime() + 5 * 60 * 60 * 1000). This breaks when DST transitions — EST is UTC-5 in winter, EDT is UTC-4 in summer."`
- **Cross-reference:** Check `utc-storage` — times stored in UTC still require IANA library for display conversion.
- **Cross-reference:** Check `customer-timezone-display` — the IANA library is what powers correct per-viewer timezone conversion.
- **Cross-reference:** Check `dst-handling` — IANA libraries automatically handle DST; manual arithmetic does not.
- **Remediation:** Use an IANA library:

  ```ts
  // Bad: manual offset arithmetic
  // const localTime = new Date(utcTime.getTime() - 5 * 60 * 60 * 1000) // Wrong during DST

  // Good: use date-fns-tz
  import { toZonedTime, fromZonedTime } from 'date-fns-tz'

  const timeZone = 'America/New_York'
  const utcTime = new Date('2024-02-15T14:00:00Z')
  const localTime = toZonedTime(utcTime, timeZone) // Correct: 9:00 AM EST (UTC-5)

  // Or use Luxon
  import { DateTime } from 'luxon'

  const dt = DateTime.fromISO('2024-02-15T14:00:00Z')
    .setZone('America/New_York')
    .toLocaleString() // Correct: "2/15/2024, 9:00 AM"
  ```

## External references

- cwe CWE-682
- iso-25010 functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000529
