summaryrefslogtreecommitdiffhomepage
path: root/packages/throughput-store/src/period.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/throughput-store/src/period.ts')
-rw-r--r--packages/throughput-store/src/period.ts132
1 files changed, 66 insertions, 66 deletions
diff --git a/packages/throughput-store/src/period.ts b/packages/throughput-store/src/period.ts
index d8225f8..4b84528 100644
--- a/packages/throughput-store/src/period.ts
+++ b/packages/throughput-store/src/period.ts
@@ -15,55 +15,55 @@
export type Period = "day" | "week" | "month";
export interface ResolvedPeriod {
- readonly ok: true;
- /** Inclusive start, epoch-ms (local midnight). */
- readonly start: number;
- /** Exclusive end, epoch-ms (local midnight). */
- readonly end: number;
- /** Local `YYYY-MM-DD` day keys this period spans, in order. */
- readonly dayKeys: readonly string[];
- /** The normalized input date string. */
- readonly date: string;
+ readonly ok: true;
+ /** Inclusive start, epoch-ms (local midnight). */
+ readonly start: number;
+ /** Exclusive end, epoch-ms (local midnight). */
+ readonly end: number;
+ /** Local `YYYY-MM-DD` day keys this period spans, in order. */
+ readonly dayKeys: readonly string[];
+ /** The normalized input date string. */
+ readonly date: string;
}
export interface PeriodError {
- readonly ok: false;
- readonly error: string;
+ readonly ok: false;
+ readonly error: string;
}
function pad2(n: number): string {
- return n < 10 ? `0${n}` : String(n);
+ return n < 10 ? `0${n}` : String(n);
}
/** Local `YYYY-MM-DD` key for an epoch-ms timestamp. */
export function dayKeyOf(ts: number): string {
- const d = new Date(ts);
- return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
+ const d = new Date(ts);
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
}
/** Local `YYYY-MM-DD` key for a local calendar date. */
function dayKey(year: number, monthIndex: number, day: number): string {
- const d = new Date(year, monthIndex, day);
- return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
+ const d = new Date(year, monthIndex, day);
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
}
function parseYmd(date: string): { y: number; m: number; d: number } | null {
- if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return null;
- const [y, m, d] = date.split("-").map(Number) as [number, number, number];
- if (m < 1 || m > 12 || d < 1 || d > 31) return null;
- // Reject impossible dates (e.g. 2026-02-30) by round-tripping through Date.
- const probe = new Date(y, m - 1, d);
- if (probe.getFullYear() !== y || probe.getMonth() !== m - 1 || probe.getDate() !== d) {
- return null;
- }
- return { y, m, d };
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return null;
+ const [y, m, d] = date.split("-").map(Number) as [number, number, number];
+ if (m < 1 || m > 12 || d < 1 || d > 31) return null;
+ // Reject impossible dates (e.g. 2026-02-30) by round-tripping through Date.
+ const probe = new Date(y, m - 1, d);
+ if (probe.getFullYear() !== y || probe.getMonth() !== m - 1 || probe.getDate() !== d) {
+ return null;
+ }
+ return { y, m, d };
}
function parseYm(date: string): { y: number; m: number } | null {
- if (!/^\d{4}-\d{2}$/.test(date)) return null;
- const [y, m] = date.split("-").map(Number) as [number, number];
- if (m < 1 || m > 12) return null;
- return { y, m };
+ if (!/^\d{4}-\d{2}$/.test(date)) return null;
+ const [y, m] = date.split("-").map(Number) as [number, number];
+ if (m < 1 || m > 12) return null;
+ return { y, m };
}
/**
@@ -71,43 +71,43 @@ function parseYm(date: string): { y: number; m: number } | null {
* keys it covers. Returns a `PeriodError` for malformed input.
*/
export function resolvePeriod(period: Period, date: string): ResolvedPeriod | PeriodError {
- if (period === "day") {
- const p = parseYmd(date);
- if (!p) return { ok: false, error: `invalid day date "${date}" (expected YYYY-MM-DD)` };
- const start = new Date(p.y, p.m - 1, p.d).getTime();
- const end = new Date(p.y, p.m - 1, p.d + 1).getTime();
- return { ok: true, start, end, dayKeys: [dayKey(p.y, p.m - 1, p.d)], date };
- }
+ if (period === "day") {
+ const p = parseYmd(date);
+ if (!p) return { ok: false, error: `invalid day date "${date}" (expected YYYY-MM-DD)` };
+ const start = new Date(p.y, p.m - 1, p.d).getTime();
+ const end = new Date(p.y, p.m - 1, p.d + 1).getTime();
+ return { ok: true, start, end, dayKeys: [dayKey(p.y, p.m - 1, p.d)], date };
+ }
- if (period === "week") {
- const p = parseYmd(date);
- if (!p) return { ok: false, error: `invalid week date "${date}" (expected YYYY-MM-DD)` };
- // ISO week: Monday-based. JS getDay() is 0=Sun..6=Sat.
- const base = new Date(p.y, p.m - 1, p.d);
- const offset = (base.getDay() + 6) % 7; // days since Monday
- const monStart = new Date(p.y, p.m - 1, p.d - offset);
- const start = monStart.getTime();
- const end = new Date(
- monStart.getFullYear(),
- monStart.getMonth(),
- monStart.getDate() + 7,
- ).getTime();
- const dayKeys: string[] = [];
- for (let i = 0; i < 7; i++) {
- dayKeys.push(dayKey(monStart.getFullYear(), monStart.getMonth(), monStart.getDate() + i));
- }
- return { ok: true, start, end, dayKeys, date };
- }
+ if (period === "week") {
+ const p = parseYmd(date);
+ if (!p) return { ok: false, error: `invalid week date "${date}" (expected YYYY-MM-DD)` };
+ // ISO week: Monday-based. JS getDay() is 0=Sun..6=Sat.
+ const base = new Date(p.y, p.m - 1, p.d);
+ const offset = (base.getDay() + 6) % 7; // days since Monday
+ const monStart = new Date(p.y, p.m - 1, p.d - offset);
+ const start = monStart.getTime();
+ const end = new Date(
+ monStart.getFullYear(),
+ monStart.getMonth(),
+ monStart.getDate() + 7,
+ ).getTime();
+ const dayKeys: string[] = [];
+ for (let i = 0; i < 7; i++) {
+ dayKeys.push(dayKey(monStart.getFullYear(), monStart.getMonth(), monStart.getDate() + i));
+ }
+ return { ok: true, start, end, dayKeys, date };
+ }
- // month
- const p = parseYm(date);
- if (!p) return { ok: false, error: `invalid month date "${date}" (expected YYYY-MM)` };
- const start = new Date(p.y, p.m - 1, 1).getTime();
- const end = new Date(p.y, p.m, 1).getTime();
- const lastDay = new Date(p.y, p.m, 0).getDate();
- const dayKeys: string[] = [];
- for (let d = 1; d <= lastDay; d++) {
- dayKeys.push(dayKey(p.y, p.m - 1, d));
- }
- return { ok: true, start, end, dayKeys, date };
+ // month
+ const p = parseYm(date);
+ if (!p) return { ok: false, error: `invalid month date "${date}" (expected YYYY-MM)` };
+ const start = new Date(p.y, p.m - 1, 1).getTime();
+ const end = new Date(p.y, p.m, 1).getTime();
+ const lastDay = new Date(p.y, p.m, 0).getDate();
+ const dayKeys: string[] = [];
+ for (let d = 1; d <= lastDay; d++) {
+ dayKeys.push(dayKey(p.y, p.m - 1, d));
+ }
+ return { ok: true, start, end, dayKeys, date };
}