Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | 10x 10x 5x 5x 1463x 1460x | /**
* IST date helpers — the one place the API turns an instant into an Indian
* calendar day.
*
* Workers run in UTC and the business runs in India. India has no DST and a
* fixed +05:30 offset, so IST is reachable by arithmetic: shift by
* `IST_OFFSET_MS` and read the *UTC* parts of the shifted instant. That is
* deliberately the whole mechanism — `Intl`/timezone databases are not used,
* and the Worker's own local time is never read, because both make the result
* depend on the runtime rather than on the input.
*
* The bug this file exists to prevent: between 00:00 and 05:29 IST the IST
* calendar date is one day *ahead* of the UTC date. Anything that counts "a
* day" from `new Date().setHours(0,0,0,0)` therefore credits every message
* sent in those five and a half hours to the previous day — which is five and
* a half hours out of every twenty-four in which a "today" figure on an ops
* screen is simply wrong.
*
* Provenance: these six exports are the generic half of the deleted
* `src/lib/rrm/time.ts` (F-21), restored verbatim. The ladder-scheduling half
* (quiet hours, jitter, `nextSendWindow`) and the financial-year half
* (`fyStart`/`fyLabel`, for TDS on partner payouts) stay deleted — nothing
* that survived F-21 needs them. The full original is at
* `.context/briefs/rescued-rrm-time.ts` if FY reporting ever returns; the
* 00:30 IST on 1 April boundary case documented there is the subtle part.
*
* Not in `packages/utils`: that package's bar is two or more consuming apps
* (`docs/rules/shared-utilities.md`) and only the API needs this today.
* Promote it if the marketplace ever asks.
*
* Everything public takes and returns `Date` (the codebase boundary type)
* except `istParts`/`istDayStartMs`, which work in epoch ms.
*/
/** IST is UTC+05:30, all year, every year. */
export const IST_OFFSET_MS = 19_800_000;
/** One day in ms. Exported so callers do not keep their own copy. */
export const DAY_MS = 86_400_000;
export interface IstParts {
/** 0–23, IST. */
hour: number;
/** 0–59. Non-zero on the half hour because the offset is :30, not :00. */
minute: number;
/** IST calendar date, `YYYY-MM-DD`. Compare calendar days against this. */
dateISO: string;
}
/** Shift into IST, read the UTC parts. */
export function istParts(ms: number): IstParts {
const d = new Date(ms + IST_OFFSET_MS);
return {
hour: d.getUTCHours(),
minute: d.getUTCMinutes(),
dateISO: d.toISOString().slice(0, 10),
};
}
/** Epoch ms of 00:00 IST on the IST day containing `ms`. */
export function istDayStartMs(ms: number): number {
// Flooring in shifted space is only exact because every IST day is exactly
// DAY_MS long — no DST means no 23- or 25-hour days to special-case.
return Math.floor((ms + IST_OFFSET_MS) / DAY_MS) * DAY_MS - IST_OFFSET_MS;
}
/**
* The instant 00:00 IST began for the IST day containing `date`.
*
* Returned as a `Date` rather than epoch ms because the callers are day
* counts — `gte(waMessages.dateCreated, istDayStart(now))` — and Drizzle's
* `mode:"timestamp"` columns compare against `Date`.
*
* Note this is usually 18:30 UTC on the *previous* calendar day. Counting a
* "day" from UTC midnight instead would credit the 00:00–05:29 IST activity to
* the wrong day, and understate today's by exactly that much.
*/
export function istDayStart(date: Date): Date {
return new Date(istDayStartMs(date.getTime()));
}
|