Time unification
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# 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:00–1: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).
|
||||
Reference in New Issue
Block a user