# Timezone Architecture Fix Plan – America/Los_Angeles

**Business rule:** All booking dates represent **calendar days in America/Los_Angeles**.  
**Scope:** Hotel room booking grid, “today” / “before today” logic, storage, and any participant check-in/check-out dates used in the same context.

**Plan enhancements:** **Enhancement 1 – la_date_key** (§5a): persisted LA date key field and indexes; duplicate/daily logic and migration. **Enhancement 2 – API Contract Standardization** (§5b): frontend sends YYYY-MM-DD only; backend is source of truth; rollout order (backend → migration → frontend).

---

## 1. Current State Summary

### 1.1 Where the frontend constructs dates

| Location | What it does | Problem |
|----------|--------------|--------|
| **`src/components/ems/RoomsInformation.js`** | `startDate` = `new Date()` then `setHours(0,0,0,0)` | Uses **browser local timezone** for grid start. |
| **`RoomsInformation.js`** | `getDates(start, count)` builds columns with `setDate(getDate() + i)` | Grid columns are **local calendar days**, not LA. |
| **`RoomsInformation.js`** | `dateKey(date) = new Date(date).toISOString().slice(0,10)` | Converts any date to **UTC** YYYY-MM-DD for comparison. |
| **`RoomsInformation.js`** | `todayUtc = new Date().toISOString().slice(0,10)` | “Today” is **UTC date**, not LA. |
| **`RoomsInformation.js`** | Fetch params: `fromDate = dates[0].toISOString().slice(0,10)`, `toDate = dates[dates.length-1].toISOString().slice(0,10)` | Requested range is in **UTC**, so backend may return wrong bookings when storage becomes LA-midnight. |
| **`RoomsInformation.js`** | `formatHeaderDay` / `formatHeaderDate` use `getDay()`, `getDate()`, `getMonth()`, `getFullYear()` | Header labels follow **browser local** date parts. |
| **`src/components/ems/AllocationModal.js`** | `normalizedDate = new Date(d); normalizedDate.setUTCHours(0,0,0,0); date: normalizedDate.toISOString()` | Sends **UTC midnight** for the selected date → backend stores UTC midnight (wrong for “March 5 LA”). |
| **`src/components/ems/EditRoom.js`** | `from_date` / `to_date` from DatePicker, sent as part of `form` (Date objects) | Axios serializes to ISO string → **browser local** interpretation when parsed on server. |
| **`src/components/ems/AssignRooms.js`** | `isWithinRoomRange(date)`: `new Date(date)` vs `new Date(room.from_date)` | Compares instants; room dates may be UTC midnight, so range check is timezone-ambiguous. |
| **`src/components/ems/AssignRooms.js`** | `checkInDate` / `checkOutDate` sent as `.toISOString()` | Same as AllocationModal: **UTC** timestamps, not “LA calendar day”. |
| **`src/components/ems/AllocationModal.js`** | Participant `checkInDate`/`checkOutDate` from `person_data`, displayed/updated with `.toISOString()` | Stored and compared in **UTC**, not LA calendar. |
| **`src/utils/date.js`** | `formatDate(date)` uses `getDate()`, `getMonth()`, `getFullYear()` | Display uses **browser local** date parts. |

### 1.2 Where “today” is computed

| Location | Current logic |
|----------|----------------|
| **`RoomsInformation.js`** | `todayUtc = useMemo(() => new Date().toISOString().slice(0, 10), [])` → **UTC date string** (e.g. `2026-03-04` when it’s still March 3 in LA). |

No other “today” for booking eligibility was found in the codebase; the grid greying is the only place.

### 1.3 Where date comparisons occur

| Location | Comparison | Issue |
|----------|------------|--------|
| **`RoomsInformation.js`** | `isOutOfRange(date)`: `dk < todayUtc`, `dk < roomFromKey`, `dk > roomToKey` (all `dateKey()` = UTC YYYY-MM-DD) | “Before today” and room window use **UTC** keys. |
| **`RoomsInformation.js`** | `bookingsMap` key = `cellKey(unitId, dateKey(date))` | Booking lookup uses UTC date key; after storage change, keys must be LA-based. |
| **`AssignRooms.js`** | `isWithinRoomRange(date)`: `d >= room.from_date && d <= room.to_date` (Date vs Date) | Compares instants; depends how room dates are stored. |
| **`ParticipantEdit.js`** | Nights calculation: `room.from_date` / `room.to_date` diff in ms → days | Same: instant-based, timezone-ambiguous. |
| **`BookingRoutes.js`** (GET) | `bookingFilter.date.$gte / $lte` with `new Date(from_date)` / `new Date(to_date)` | Query uses client-supplied date strings; when client sends LA YYYY-MM-DD, backend must interpret as LA range (see below). |

### 1.4 Backend storage and normalization

| Location | Current behavior |
|----------|------------------|
| **`dashboard/Routes/BookingRoutes.js`** | POST: `normalizedDate = new Date(date); normalizedDate.setUTCHours(0,0,0,0)` → store that. So “March 5” from frontend (as UTC midnight) is stored as `2026-03-05T00:00:00.000Z` = **March 4 4:00 PM PST**. |
| **`dashboard/Routes/BookingRoutes.js`** | PATCH: same `setUTCHours(0,0,0,0)` for `updates.date`. |
| **`dashboard/Routes/BookingRoutes.js`** | GET: `from_date` / `to_date` from query → `new Date(from_date)` for `$gte`/`$lte`. No conversion; assumes client sends correct range. |
| **`dashboard/Routes/BookingRoutes.js`** | `sumGuests(roomUnitId, date, excludeId)`: exact match on `date: new Date(date)` (one day). After migration, “same day” must be LA day. |
| **`dashboard/Routes/RoomRoutes.js`** | Room create/update: `from_date` / `to_date` from `req.body` passed through; Mongoose stores as-is (ISO from client = browser/local or UTC depending on frontend). |
| **`dashboard/models/Booking.js`** | `date: { type: Date, required: true }` – no timezone logic. |
| **`dashboard/models/Room.js`** | `from_date` / `to_date: { type: Date }` – no timezone logic. |

**Conclusion:** The system currently uses **browser local** for grid and display, **UTC** for “today” and dateKey comparisons, and **UTC midnight** for stored booking/room dates. The hotel operates in **America/Los_Angeles**, so we have a three-way mismatch and the observed bug (March 3 greyed out while it’s still March 3 in LA).

---

## 2. Target Behavior (LA-only)

1. **Grid columns** = LA calendar days (e.g. first column = “today” in LA if grid starts at today).
2. **“Today”** = current calendar day in America/Los_Angeles.
3. **“Before today”** = any LA calendar day strictly before today (LA).
4. **Stored MongoDB dates** = midnight in LA, converted to the correct UTC instant (e.g. March 5 LA → `2026-03-05T08:00:00.000Z` in PST, or `2026-03-05T07:00:00.000Z` in PDT).
5. **DST** = handled by the timezone library (IANA `America/Los_Angeles`).
6. **Same-day booking / duplicate check** = same `la_date_key` string (no Date conversion needed; compare `la_date_key` equality directly).
7. **Existing stored bookings** = remain valid after migration (see migration below).

---

## 3. Exact Files and Modules to Modify

### 3.1 Frontend

| File | Changes |
|------|--------|
| **`src/components/ems/RoomsInformation.js`** | (1) Grid: build `startDate` and `dates` in LA (e.g. “start of today LA” as a Date, then add days in LA). (2) Replace `dateKey` with an LA calendar-day key (e.g. YYYY-MM-DD in LA). (3) Replace `todayUtc` with `todayLA` (YYYY-MM-DD in LA). (4) Fetch params: send LA date range (e.g. first/last LA date of grid). (5) `bookingsMap` key: use LA date key. (6) Header formatting: optional – can keep local display or show LA explicitly. (7) `shiftDates`: advance by 7 LA days. |
| **`src/components/ems/AllocationModal.js`** | When submitting: send **YYYY-MM-DD** only for each selected date (LA calendar day). Do not send ISO; backend converts to UTC and sets `la_date_key` (Enhancement 2). |
| **`src/components/ems/EditRoom.js`** | When submitting: send **YYYY-MM-DD** only for `from_date`/`to_date` (LA calendar day). Backend converts and stores UTC + `from_la_date_key`/`to_la_date_key` (Enhancement 2). |
| **`src/components/ems/AssignRooms.js`** | (1) `isWithinRoomRange`: compare LA calendar days using `from_la_date_key`/`to_la_date_key` from Room (or `utcToLADateKey` for display). (2) When sending `check_in_date`/`check_out_date`: send **YYYY-MM-DD** (LA calendar day) only — same contract as booking dates (Enhancement 2). |
| **`src/components/ems/BookingCellMenu.js`** | Display only: ensure `date` (from backend) is shown as LA calendar day; use shared “UTC instant → LA date” formatter if needed. |
| **`src/utils/date.js`** | Add (or use from shared module): `formatDateInLA(date)`, and optionally `toLADateKey(date)` for consistency. Display of booking/room dates in LA context should use LA. |
| **New shared module (recommended)** | **`src/utils/laDateUtils.js`** (or `src/utils/hotelTimezone.js`): `TZ = 'America/Los_Angeles'`; `todayLAKey()`, `toLAKey(date)`, `laMidnightToUTC(laYear, laMonth, laDay)` or `laDateKeyToUTC(laDateKey)`, `utcToLADateKey(utcDate)`. Use dayjs with timezone plugin or native `Intl` + small helpers. |

### 3.2 Backend

| File | Changes |
|------|--------|
| **`dashboard/models/Booking.js`** | Add `la_date_key: { type: String, required: true }`. Add index on `la_date_key`; add compound index e.g. `(room_unit_id, la_date_key)` for daily/duplicate lookups (see Enhancement 1). |
| **`dashboard/models/Room.js`** | Add `from_la_date_key` and `to_la_date_key` (String). Add indexes as needed (Enhancement 1). |
| **`dashboard/Routes/BookingRoutes.js`** | (1) **POST**: Accept `date` as **YYYY-MM-DD** (LA); convert to UTC midnight LA via `laDateKeyToUTC`; set `date` and `la_date_key`. Reject or normalize raw ISO (Enhancement 2). (2) **PATCH**: Same for `updates.date`. (3) **GET**: Accept `from_date`/`to_date` as LA YYYY-MM-DD; convert to UTC bounds and query; return `la_date_key` on documents. (4) **Duplicate check**: use `room_unit_id` + `participant_id` + `la_date_key`. (5) **sumGuests**: query by `room_unit_id` + `la_date_key` equality (not Date conversion). |
| **`dashboard/Routes/RoomRoutes.js`** | On create/update: accept `from_date`/`to_date` as LA YYYY-MM-DD; store UTC midnight LA and set `from_la_date_key`/`to_la_date_key`. Reject or normalize raw ISO (Enhancement 2). |
| **New shared module (backend)** | **`dashboard/utils/laDateUtils.js`**: `laDateKeyToUTC(laDateKey)`, `utcToLADateKey(utcDate)`, `todayLAKey()`, and range `laDateRangeToUTCBounds(fromKey, toKey)` for queries. Use a single timezone library (e.g. dayjs with timezone or a small Intl-based implementation). |

### 3.3 Documentation and tests

| File | Changes |
|------|--------|
| **`docs/ROOM_DATE_RANGE_GREYING_PLAN.md`** | Update to state that all dates are **America/Los_Angeles** (not UTC): today, from_date, to_date, and grid columns. |
| **Tests (if any)** | Add or update tests for: today in LA, before-today greying, booking create/read with LA dates, room window in LA, DST transition day. |

---

## 4. Unified Timezone Strategy

### 4.1 Single source of truth

- **Semantic meaning:** Every booking/room date is “calendar day in America/Los_Angeles”.
- **Storage:** Store the UTC instant that equals **midnight at the start of that LA day** (00:00:00 in LA). Also store **`la_date_key`** (Booking) and **`from_la_date_key`** / **`to_la_date_key`** (Room) as `YYYY-MM-DD` strings for equality and range queries without repeated Date conversion (Enhancement 1). DST is handled by the timezone library.
- **API (Enhancement 2):**  
  - **Input:** Frontend sends **YYYY-MM-DD** only for `date`, `from_date`, `to_date` (LA calendar days). Backend is the single source of truth for converting these to UTC midnight. Backend must reject raw ISO timestamps for these fields (or normalize them safely during transition).  
  - **Output:** API returns stored `date` (UTC) and `la_date_key` (and Room keys); frontend uses `la_date_key` for grid keys and display.

### 4.2 Helper semantics (to implement)

- **`todayLAKey()`** → e.g. `"2026-03-03"` (current date in LA).
- **`toLAKey(utcDate)`** / **`utcToLADateKey(utcDate)`** → given a Date (or ISO string), return YYYY-MM-DD in LA.
- **`laDateKeyToUTC(laDateKey)`** → given `"2026-03-05"`, return Date = midnight start of that day in LA (as UTC instant).
- **`laDateRangeToUTCBounds(fromKey, toKey)`** → return `{ start: Date, end: Date }` for querying: start = midnight start of fromKey in LA, end = end of day toKey in LA (e.g. 23:59:59.999 LA or start of next day exclusive, depending on query style).
- **`validateLADateKey(value)`** → validates that a value is a well-formed, real LA calendar day: (1) matches `/^\d{4}-\d{2}-\d{2}$/`, (2) contains no time portion, (3) represents a valid calendar date when parsed in `America/Los_Angeles`. Returns `{ valid: true }` or `{ valid: false, message: string }`. Used by all booking/room routes before processing any date field (see Enhancement 2 strict validation).

### 4.3 Library recommendation

- **Option A (recommended):** Use **dayjs** (already in the project) with **dayjs/plugin/utc** and **dayjs/plugin/timezone**. Implement the helpers above in one place (frontend + backend). Backend must use the same TZ logic (e.g. Node with `dayjs` and `dayjs-timezone` or equivalent).
- **Option B:** Use **Intl** (no new deps): format and parse in LA, and compute “start of day LA” by formatting UTC to LA date and then constructing the UTC instant for that LA midnight (more verbose, DST still correct).
- **Option C:** Add **date-fns-tz** or **luxon** if you prefer a different API; keep a single module that hides the library.

**Recommendation:** dayjs + timezone plugin in a small **`laDateUtils`** module on frontend and backend so that “LA calendar day” and “LA midnight → UTC” are implemented once and reused everywhere. **Note:** The dashboard (backend) does not currently list `dayjs`; add `dayjs` and `dayjs` timezone plugin (or use Node’s `Intl` with a small LA-midnight helper) in `dashboard/package.json` for the backend LA helpers.

---

## 5. Migration Strategy for Existing Records

### 5.1 Verified storage patterns (from DB check script)

The `dashboard/scripts/check-date-storage.js` script confirmed **two distinct patterns** — not one:

| Collection | Field(s) | Observed pattern | Count |
|------------|----------|------------------|-------|
| **Booking** | `date` | `T00:00:00.000Z` — UTC midnight | 18 / 18 |
| **Room** | `from_date`, `to_date` | `T18:30:00.000Z` — **not** UTC midnight | 10 / 10 |

**Booking interpretation:** Stored as UTC midnight, intended to represent a calendar day, but is off by the LA UTC offset. Example: `2026-03-05T00:00:00.000Z` was meant to be "March 5" but equals March 4 4:00 PM PST.
**Migration rule:** Take the UTC date part (`toISOString().slice(0,10)`) as the intended LA calendar day, convert to LA midnight UTC, write back with `la_date_key`.

**Room interpretation:** Stored at 18:30 UTC — this is midnight in IST (UTC+5:30). Room dates were entered from a browser running in IST, so "December 24" in the date picker became `2025-12-23T18:30:00.000Z` (IST midnight serialized to UTC). The intended calendar day is the IST date, i.e. add 5h30m to the stored UTC instant and take the date part.
**Migration rule (committed):** For each room `from_date`/`to_date` at 18:30 UTC: add 5h30m to recover the IST calendar day, that string becomes `from_la_date_key`/`to_la_date_key`, then convert it to LA midnight UTC and write back. Example: `2025-12-23T18:30:00.000Z` + 5h30m = December 24 00:00:00 IST, intended day is `"2025-12-24"`, store as `2025-12-24T08:00:00.000Z` (PST midnight) + `from_la_date_key = "2025-12-24"`.

> **Verify before running:** Log the before/after mapping for all rooms and have a stakeholder confirm the intended calendar dates before committing the migration. If any rooms were entered from a different timezone, the 18:30 rule does not apply and must be handled separately.

**Guard against double-migration:** After migration, all dates will be at LA midnight UTC (offset 7h or 8h depending on DST). Before writing each document, check: if `utcToLADateKey(storedDate) === existingLaDateKey` already, skip (already migrated). This makes the script safe to re-run.

### 5.2 Options

**Option A – Reinterpret in place (no data migration)**
- Treat existing stored values as "legacy"; in code, when **reading** old records, reinterpret the stored UTC date part as the LA calendar day.
- When **writing** new/updated records, use LA midnight UTC.
- **Downside:** Existing data remains at wrong UTC instants (UTC midnight for bookings, 18:30 for rooms). The mixed patterns make this unreliable. Not recommended.

**Option B – One-time migration (recommended)**
- Add a migration script under `dashboard/scripts/`:
  - For each **Booking**: take UTC date part as intended LA calendar day, convert to LA midnight UTC, set `date` and `la_date_key`.
    Example: `2026-03-05T00:00:00.000Z` → `"2026-03-05"` → `2026-03-05T08:00:00.000Z` (PST) + `la_date_key = "2026-03-05"`.
  - For each **Room**: add 5h30m to recover IST calendar day, convert to LA midnight UTC, set `from_date`/`to_date` and `from_la_date_key`/`to_la_date_key`.
    Example: `2025-12-23T18:30:00.000Z` → IST day `"2025-12-24"` → `2025-12-24T08:00:00.000Z` (PST) + `from_la_date_key = "2025-12-24"`.
- Run **once**, with DB backup, in a maintenance window. Log all conversions for review before committing.

**Option C – Dual-read during transition**
- New writes use LA midnight UTC; reads support both old and new patterns temporarily. More complex; only warranted for zero-downtime requirements.

**Recommendation:** **Option B**. Run with backup, log all before/after values, and have a stakeholder confirm room dates before committing. **See Enhancement 1 (§5a)** for `la_date_key` backfill and index steps. **See Enhancement 2 (§5b)** for API contract and rollout order.

---

---

## 5a. Enhancement 1 – la_date_key

### Persisted field and schema

- **Booking model:** Add `la_date_key: { type: String, required: true }` (format `YYYY-MM-DD`, LA calendar day). Keep existing `date` (Date) as the UTC instant of midnight LA.
- **Room model:** Add `from_la_date_key` (String) and `to_la_date_key` (String), each `YYYY-MM-DD`, representing the LA calendar day of `from_date` and `to_date` respectively. Required when `from_date`/`to_date` are set. For **Booking**, one date → one `la_date_key`; for **Room**, a date range → `from_la_date_key` and `to_la_date_key`.

### Index strategy

- **Booking:**  
  - Index on `la_date_key` (for range and daily lookups).  
  - Compound unique index on `(room_unit_id, la_date_key)` only if business rule is “one booking per room unit per LA day” per participant; duplicate check is “same participant + same room_unit_id + same la_date_key”, so compound index for that query: `(room_unit_id, la_date_key)` (non-unique, for filter) and optionally unique on `(room_unit_id, la_date_key, participant_id)` if one booking per participant per unit per day.  
  - For GET by date range: index on `date` (UTC) is still useful for `$gte`/`$lte`; index on `la_date_key` supports equality and range (e.g. `la_date_key: { $gte, $lte }`).
- **Room:**  
  - Index on `from_la_date_key` and/or `to_la_date_key` if queries filter by room window; compound with other fields if needed.

### Create/update rules

- **Booking (create/update):**  
  - Accept LA calendar day (as YYYY-MM-DD from API; see Enhancement 2).  
  - Set `date = laDateKeyToUTC(la_date_key)` (UTC instant of midnight LA for that day).  
  - Set `la_date_key = la_date_key` (the received string, validated as YYYY-MM-DD).  
- **Room (create/update):**  
  - For `from_date`/`to_date`, derive `from_la_date_key` / `to_la_date_key` from the LA calendar day of the stored (or incoming) date, and persist both the `date` (UTC) and the key strings.

### Duplicate checks, daily occupancy, grid lookups

- **Duplicate check (same participant, same room unit, same day):** Use `la_date_key` equality: query by `room_unit_id`, `participant_id`, and `la_date_key` (no Date conversion).
- **Daily occupancy (sumGuests):** Query by `room_unit_id` and `la_date_key` equality instead of comparing `date` with a converted value.
- **Grid lookups / bookingsMap:** Key cells by `unitId` and `la_date_key` (from backend response); backend returns `la_date_key` on Booking documents so frontend can use it directly for map keys without converting `date` to LA.

### Migration (Enhancement 1)

- **Booking:**  
  - For each document: interpret current `date` as legacy (UTC midnight or 18:30-style; see DB check script). Compute LA calendar day from that instant → `utcToLADateKey(date)` → set `la_date_key`. Convert legacy `date` to correct LA midnight UTC for that same calendar day → write back `date` and `la_date_key`.  
  - After all documents are updated: create index on `la_date_key` and compound index as above (e.g. `(room_unit_id, la_date_key)`).
- **Room:**  
  - For each document: apply the **confirmed IST rule** from §5.1 — add 5h30m to the stored UTC instant to recover the IST calendar day, use that as `from_la_date_key`/`to_la_date_key`, then convert those keys to LA midnight UTC and write back `from_date`/`to_date`.  
  - After update: create indexes on `from_la_date_key` / `to_la_date_key` as needed.
- **Order:** Run migration after backend code supports `la_date_key` (and Room keys) so that new writes already populate them; migration backfills only existing documents. Add indexes after migration is complete to avoid long lock on large collections.

---

## 5b. Enhancement 2 – API Contract Standardization

### Contract

- **Frontend sends booking/room dates as YYYY-MM-DD strings** representing LA calendar days. No raw ISO timestamps for “date” or “from_date”/“to_date” in hotel context.
- **Backend is the single source of truth** for converting LA date strings to UTC midnight (America/Los_Angeles) and for storing `date` and `la_date_key` (Enhancement 1).
- **Backend must reject or normalize raw ISO for booking dates:**  
  - **Reject:** Use **strict YYYY-MM-DD validation** (see “Validation for YYYY-MM-DD” below): format `/^\d{4}-\d{2}-\d{2}$/`, no time portion, valid LA calendar day. If the value does not pass, respond 400 (e.g. “date must be YYYY-MM-DD (LA calendar day)”).  
  - **Normalize (optional, transitional):** During a short transition, backend may accept ISO and derive LA date from it, then proceed as with YYYY-MM-DD. Prefer rejecting non-YYYY-MM-DD after frontend is updated.
- **GET endpoints:**  
  - Accept `from_date` and `to_date` as **LA YYYY-MM-DD** query params.  
  - Backend converts them to UTC bounds via `laDateRangeToUTCBounds(from_date, to_date)` and queries using `date` (UTC) with `$gte`/`$lte`, and/or uses `la_date_key` with `$gte`/`$lte` if indexed.  
  - Response continues to return `date` (ISO) and `la_date_key` so frontend can use `la_date_key` for grid keys and display.

### Validation for YYYY-MM-DD (strict)

Do not just “expect” YYYY-MM-DD. Enforce it so future changes cannot silently break the system.

- **Format (required):** The value must match the regex **`/^\d{4}-\d{2}-\d{2}$/`**. No spaces, no time portion, no trailing/leading characters. Reject anything else (e.g. `2026-03-05T00:00:00.000Z`, `2026-3-5`, `03/05/2026`) with **400** and a clear message (e.g. `"date must be YYYY-MM-DD (LA calendar day)"`).
- **No time portion:** Only the date string is allowed. If the client sends an ISO string or any value containing `T`, space, or time components, treat it as invalid and reject (do not try to “parse and take the date part” for these fields in the hotel API).
- **Valid calendar day:** After the format check, parse the string as a date in **America/Los_Angeles** and verify it represents a real calendar day (e.g. reject `2026-02-30`, `2026-13-01`). Use the same LA timezone logic as `laDateKeyToUTC`; if the result is invalid or out of range, respond **400** with a message like `"Invalid date for America/Los_Angeles"`.
- **Where to apply:** All booking/room date inputs: request body fields `date`, `from_date`, `to_date` (Booking and Room create/update) and query params `from_date`, `to_date` (GET room-bookings). Use a shared validator (e.g. in `laDateUtils` or a small validation middleware) so the rules are defined once and reused.

### Rollout (Enhancement 2)

1. **Backend first (support YYYY-MM-DD contract)**  
   - Implement `laDateUtils` and Booking/Room logic to accept **YYYY-MM-DD** for `date`, `from_date`, `to_date`.  
   - Store `date` as LA midnight UTC; add and persist `la_date_key` (and Room `from_la_date_key`/`to_la_date_key`).  
   - Reject (or normalize) raw ISO for these fields per contract above.  
   - GET: accept `from_date`/`to_date` as LA YYYY-MM-DD; convert to UTC bounds and query.  
   - Deploy backend; existing frontend may still send ISO—backend either rejects with 400 or normalizes during transition.

2. **Migration script**  
   - Run migration for existing data: convert legacy dates to LA midnight UTC, backfill `la_date_key` (and Room keys), then add indexes (Enhancement 1).

3. **Frontend update**  
   - Change all hotel date payloads and query params to **YYYY-MM-DD** only (LA calendar day). Remove any `toISOString()` or ISO for `date`/`from_date`/`to_date` in booking and room flows.  
   - Deploy frontend; backend no longer needs to accept raw ISO.

4. **Validation and documentation**  
   - Verify GET with `from_date`/`to_date` as YYYY-MM-DD returns correct range; verify POST/PATCH with YYYY-MM-DD stores correct `date` and `la_date_key`.  
   - Document the API contract (LA YYYY-MM-DD only) in `ROOM_DATE_RANGE_GREYING_PLAN.md` or API docs.

---

## 6. Edge Cases

| Case | Handling |
|------|----------|
| **DST transition (spring forward / fall back)** | Use IANA `America/Los_Angeles` in the library; “midnight” is always 00:00 in LA. Spring forward: that day has 23 hours; fall back: 25 hours. No special code if you use “start of day” and “end of day” in LA. |
| **End of month** | LA date keys (YYYY-MM-DD) and adding days in LA via the library avoid off-by-one; no manual month arithmetic. |
| **Same-day booking** | Duplicate check and bed-count query by `la_date_key` equality (indexed string compare, no Date conversion). POST and PATCH routes reject duplicate `(room_unit_id, participant_id, la_date_key)`. |
| **Grid “today” at DST change** | `todayLAKey()` returns the correct LA date; grid and greying stay correct. |
| **Room window from_date = to_date** | Single day; LA midnight for that day and “end of that day” in LA for range query; no change in logic. |
| **Existing bookings displayed after migration** | Stored value is LA midnight UTC; frontend converts to LA date key for display and map key → correct. |
| **Backend in a different timezone** | Backend must not use server local time; all logic uses the shared LA helpers (dayjs or Intl with `America/Los_Angeles`). |

---

## 7. Risks

| Risk | Mitigation |
|------|------------|
| **Inconsistent adoption** | Use a single `laDateUtils` (or equivalent) on frontend and backend; all “booking date” and “room window” logic goes through it. |
| **Migration script bugs** | Test on a copy of prod data; verify a sample of bookings/rooms before and after (UTC instant → LA date label). |
| **Third-party or future code** | Document in `ROOM_DATE_RANGE_GREYING_PLAN.md` and in code comments that hotel dates are **LA calendar days** and storage is **LA midnight in UTC**. |
| **Performance** | Persisted `la_date_key` (Enhancement 1) avoids repeated Date→LA conversion; daily and duplicate checks use indexed `la_date_key` equality. Range queries may use `date` (UTC) or `la_date_key` with indexes. |
| **Participant check-in/check-out** | If these are used only for display or for “assign room” and not for hard business rules in other timezones, aligning them to “LA calendar day” (store LA midnight UTC) keeps consistency with room bookings. If they have different semantics (e.g. “user’s local date”), document and potentially keep them separate. |

---

## 8. Rollout Strategy

Follow **Enhancement 2** for contract and order: backend first (YYYY-MM-DD contract), then migration, then frontend.

1. **Implement shared LA helpers**  
   - Add `src/utils/laDateUtils.js` (frontend) and `dashboard/utils/laDateUtils.js` (backend) with the same contract (todayLAKey, toLAKey, laDateKeyToUTC, range). Use dayjs+timezone (or chosen library) and add dependency on backend if needed.

2. **Backend first (YYYY-MM-DD contract)**  
   - Add `la_date_key` to Booking model and `from_la_date_key`/`to_la_date_key` to Room model (Enhancement 1).  
   - **BookingRoutes**: Accept `date` as **YYYY-MM-DD** (LA); validate via `validateLADateKey`; convert via `laDateKeyToUTC`; store `date` (UTC) and `la_date_key`. **Reject** raw ISO with 400. GET: accept `from_date`/`to_date` as LA YYYY-MM-DD; validate both; convert to UTC bounds; return documents with `la_date_key`. Duplicate check and sumGuests use `la_date_key`.  
   - **RoomRoutes**: Accept `from_date`/`to_date` as LA YYYY-MM-DD; validate each via `validateLADateKey`; store UTC midnight LA and LA key fields. **Reject** raw ISO with 400.  
   - Deploy backend with **strict YYYY-MM-DD validation** active from day one (see Enhancement 2). Do not silently normalize legacy ISO — return 400 immediately. The frontend is updated in step 4; if the team needs a brief parallel deploy window, coordinate a synchronized cutover rather than running a long silent-normalization period that could mask bugs.

3. **Migration script**  
   - Run migration (Enhancement 1): convert legacy Booking/Room dates to LA midnight UTC; backfill `la_date_key` (and Room keys). Add indexes **after** migration completes. Run with backup.

4. **Frontend**  
   - Send only **YYYY-MM-DD** for booking and room dates (no ISO). Grid and fetch params use LA date keys; `bookingsMap` keys use `la_date_key` from API response.  
   - Deploy frontend after backend and migration.

5. **Validation**  
   - Confirm GET with `from_date`/`to_date` as YYYY-MM-DD returns correct range; confirm POST/PATCH with YYYY-MM-DD stores correct `date` and `la_date_key`.  
   - Set system/browser to different timezones; confirm “today” and greying follow LA. Test DST transition date if possible.

6. **Documentation**  
   - Update `ROOM_DATE_RANGE_GREYING_PLAN.md` to LA and document API contract (LA YYYY-MM-DD only); add “Hotel dates are America/Los_Angeles” note where applicable.

---

## 9. Summary Checklist

- [ ] Add `laDateUtils` (or equivalent) on frontend and backend with dayjs+timezone (or agreed library). Export: `todayLAKey`, `toLAKey`, `laDateKeyToUTC`, `utcToLADateKey`, `laDateRangeToUTCBounds`, `validateLADateKey`.
- [ ] **Enhancement 1 – la_date_key:** Add `la_date_key` to Booking; add `from_la_date_key`/`to_la_date_key` to Room. Add indexes (e.g. `la_date_key`, compound `(room_unit_id, la_date_key)`). Duplicate checks and daily occupancy use `la_date_key`; grid lookups use `la_date_key` from API.
- [ ] **Enhancement 2 – API contract:** Frontend sends YYYY-MM-DD only; backend converts to UTC and stores `date` + `la_date_key`. Backend rejects (or normalizes) raw ISO for booking/room dates. GET accepts `from_date`/`to_date` as LA YYYY-MM-DD.
- [ ] **Strict YYYY-MM-DD validation:** Backend validates format `/^\d{4}-\d{2}-\d{2}$/`, no time portion allowed, and valid LA calendar day; 400 with clear message on failure. Apply to `date`, `from_date`, `to_date` in body and query.
- [ ] **BookingRoutes.js**: accept `date` as YYYY-MM-DD; store LA midnight UTC and `la_date_key`; GET range via UTC bounds; duplicate/sumGuests by `la_date_key`.
- [ ] **RoomRoutes.js**: accept `from_date`/`to_date` as YYYY-MM-DD; store LA midnight UTC and `from_la_date_key`/`to_la_date_key`.
- [ ] **RoomsInformation.js**: grid and “today” in LA; fetch params and bookingsMap key use LA date key (and `la_date_key` from response).
- [ ] **AllocationModal.js**: submit dates as YYYY-MM-DD (LA) only.
- [ ] **EditRoom.js**: submit from_date/to_date as YYYY-MM-DD (LA) only.
- [ ] **AssignRooms.js** (and participant check-in/out if aligned): range and payload in LA (YYYY-MM-DD).
- [ ] **Migration**: Bookings — take UTC date part as LA calendar day, convert to LA midnight UTC, set `la_date_key`. Rooms — add 5h30m (IST rule per §5.1) to recover intended calendar day, convert to LA midnight UTC, set `from_la_date_key`/`to_la_date_key`. Add indexes after migration. Run with backup; log all before/after for stakeholder review.
- [ ] **Rollout**: backend first (YYYY-MM-DD contract) → migration → frontend (send YYYY-MM-DD only). Update docs and validate.

This plan gives a single, consistent interpretation of “March 5” as March 5 in Los Angeles everywhere: grid, validation, storage, and queries; with persisted `la_date_key` for efficient daily comparisons and a standardized YYYY-MM-DD API contract.
