Files
inventory/inventory-server/docs/TIME.md
T
2026-06-17 15:06:38 -04:00

68 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TIME.md — the business-time convention
Adopted 2026-06-12 (see `TIME_UNIFICATION_PLAN.md` at the repo root for the
audit and rollout). **Never hand-roll an hour offset.**
## The convention
**A business day runs 1:00am Eastern → 1:00am Eastern the next day.**
Because 1am Eastern == midnight Central year-round (both observe US DST),
this is identical to:
> **Business date = the America/Chicago calendar date.**
- `BUSINESS_TZ = 'America/Chicago'` — canonical, used for all boundary math.
- `OFFICE_TZ = 'America/New_York'` — display only (the office reads ET).
The single source of truth is `shared/business-time/index.js`
(`@inventory/shared/business-time`). The frontend mirror for the few
client-side needs is `inventory/src/utils/businessTime.ts`.
## Approved idioms
| Context | Correct idiom |
| --- | --- |
| PG `timestamptz` → business date | `(ts AT TIME ZONE 'America/Chicago')::date`, or plain `ts::date` / `CURRENT_DATE` (the session TZ is pinned to America/Chicago in every pool config AND as the `inventory_db` database default) |
| MySQL DATETIME literal (stores **Central wall-clock**) → business date | `DATE(col)` / `DATE_FORMAT(col, '%Y-%m-%d')`**no hour shift** |
| MySQL WHERE bounds for business days | Central wall-clock strings via `toMySqlBound()`: `col >= ? AND col < ?` |
| MySQL hour-of-day for the office | `HOUR(ADDTIME(col, '01:00:00'))` — ET = CT + 1h year-round |
| Luxon | `dt.setZone('America/Chicago')` (real conversion). Never `keepLocalTime`, never a fixed `UTC-05:00` offset (wrong every winter) |
| Intervals | **Half-open** `[start, nextStart)`. No `-1 minute` / `-1 ms` endpoints (legacy exception: the `TimeManager` compat class keeps an inclusive 1ms end for its existing consumers) |
| "Today" in JS | `businessTodayStr()` / `businessToday()` from the shared module — never `new Date().toISOString().split('T')[0]` (UTC) |
| node-postgres DATE columns → string | `formatDateCol(value)` (local-time formatting round-trips correctly) or `::text` in SQL — never `.toISOString().split('T')[0]` (UTC shifts a day) |
| mysql2 driver config | `dateStrings: true` + parse with `parseMySql()` (zone Chicago). The import pipeline instead pins a **dynamic** Chicago offset (`currentChicagoOffset()` in `scripts/import-from-prod.js`) |
| Client → server | Named range presets, or **date-only strings** (`YYYY-MM-DD` = business dates). Never `toISOString()` of a browser-local midnight |
## Range vocabulary (both servers, one list)
`today, yesterday, twoDaysAgo, thisWeek, lastWeek, thisMonth, lastMonth,
last7days, last30days, last90days, previous7days, previous30days,
previous90days, custom`
`custom` takes `startDate`/`endDate` as date-only business dates (preferred)
or zoned ISO instants (used as-is, end exclusive). Calendar periods
("March 2026") mean the business month: Mar 1 1am ET → Apr 1 1am ET.
## Accepted deviations (documented, not fought)
- **GA4**: property TZ = America/New_York; `NdaysAgo` = calendar days,
midnight ET. Affects only 0:001:00am ET traffic attribution.
- **Meta insights**: `time_zone: 'America/New_York'` — same 1-hour deviation,
same note.
- **Klaviyo**: fetched with exact UTC bounds computed from our own range math
— fully on-convention. API returns UTC datetimes.
- **Pay periods** (payroll dashboard): 14-day periods aligned to **midnight
ET** Sundays — an HR display convention, intentionally not 1am-ET days.
## Infrastructure pins (belt-and-suspenders)
1. `ALTER DATABASE inventory_db SET timezone = 'America/Chicago'` (database default).
2. Every pg Pool passes `options: '-c TimeZone=America/Chicago'`
(`src/utils/db.js`, `shared/db/pg.js`, `scripts/metrics-new/utils/db.js`,
`scripts/calculate-metrics-new.js`, `scripts/import/utils.js`).
3. Node processes run with `TZ=America/Chicago`
(`/var/www/ecosystem.config.cjs` env blocks; `scripts/full-update.js` and
`scripts/forecast/run_forecast.js` set it themselves for cron contexts).
4. `forecast_engine.py` anchors on `business_today()` (zoneinfo Chicago).