Skip to main content
Synced from the repo — do not edit here

Canonical source: docs/claude/date-time-handling.md. This page is generated by docs/scripts/sync-handbook.mjs. Edit the source file in the repo; changes appear here on the next build.

Date/Time Handling

CRITICAL BUG PATTERN: Dates entered show one day earlier than expected. This is a recurring issue across the platform.

The Root Cause

When a user enters "1990-01-15" in an HTML date input:

  1. new Date("1990-01-15") creates UTC midnight (1990-01-15T00:00:00Z)
  2. Saved as Firestore Timestamp (UTC)
  3. In PST (UTC-8), UTC midnight becomes January 14 at 4pm
  4. Display shows January 14 instead of January 15

Two Types of Date Fields

TypeExamplesStorageParsing
Date-OnlyBirthday, due date, start dateSTRING "1990-01-15"Never use new Date()
DateTimeAppointment time, created atTimestampUse Timestamp.fromDate()

Correct Patterns

Date-Only Fields (Birthday, Due Date, etc.)

// CORRECT - Store as string, display as string
await updateDoc(clientRef, { dateOfBirth: "1990-01-15" });

const dob = data.dateOfBirth; // Already a string

// If you MUST create a Date object for calculations:
const dobDate = new Date(dob + "T00:00:00"); // Append local midnight
// Flutter/Dart
await docRef.update({'dateOfBirth': '1990-01-15'});
final dob = DateTime.parse('${data['dateOfBirth']}T00:00:00');

DateTime Fields (Appointments, Timestamps)

import { Timestamp } from 'firebase/firestore';
await updateDoc(ref, {
appointmentTime: Timestamp.fromDate(new Date()),
createdAt: Timestamp.now(),
});
const time = data.appointmentTime?.toDate();

Anti-Patterns to AVOID

// WRONG - Parses as UTC midnight, causes day shift
const dob = new Date("1990-01-15");

// WRONG - Stores UTC midnight as Timestamp
await updateDoc(ref, { dateOfBirth: Timestamp.fromDate(new Date(dobString)) });

// WRONG - toISOString uses UTC, loses local date
const dateStr = someDate.toISOString().split('T')[0];

// WRONG - Mixing date-only input with Timestamp storage
const dueDate = new Date(inputValue);
await updateDoc(ref, { dueDate: Timestamp.fromDate(dueDate) });

Utility Functions

// lib/date-utils.ts

/** Format a Date to YYYY-MM-DD string using LOCAL timezone. */
export function formatDateOnly(date: Date): string {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}

/** Parse a YYYY-MM-DD string to a Date at LOCAL midnight. */
export function parseDateOnly(dateStr: string): Date {
return new Date(dateStr + 'T00:00:00');
}

/** Get today's date as YYYY-MM-DD string in local timezone. */
export function todayDateOnly(): string {
return formatDateOnly(new Date());
}

Audit Commands

# Find dangerous new Date("YYYY-MM-DD") patterns
grep -rn "new Date(['\"][0-9]" hybrid/basisflow-web hybrid/basisweb --include="*.ts" --include="*.tsx"

# Find Timestamp.fromDate(new Date(...)) with date strings
grep -rn "Timestamp.fromDate(new Date(" hybrid/basisflow-web --include="*.ts" --include="*.tsx"

# Find date-only fields being converted incorrectly
grep -rn "dateOfBirth.*new Date\|dueDate.*new Date\|birthday.*new Date" hybrid/ --include="*.ts" --include="*.tsx" --include="*.dart"

# Find toISOString().split('T')[0] pattern
grep -rn "toISOString().*split.*T" hybrid/basisflow-web --include="*.ts" --include="*.tsx"

Known Affected Areas

LocationIssueFix
ClientDetailPage.tsxdateOfBirth saved as TimestampStore as string
ClientDetailPage.tsxdueDate uses new Date(dueDate)Use parseDateOnly()
dashboard/page.tsxTask due datesStore as string
Various formsDate inputs -> TimestampStore as string

Developer Checklist

  • Is this a date-only field (no time component)?
    • YES -> Store as string "YYYY-MM-DD"
    • NO -> Store as Timestamp
  • When parsing date strings, am I using new Date(str + 'T00:00:00')?
  • When displaying, am I avoiding toLocaleDateString() on UTC dates?
  • Does this work for users in EEST, PST, and UAE timezones?