// How much was done when, a week at a time, from what // dev.knotless.getActorActivity says of each day in the viewer's zone. const DAY = 86_400_000; /** Weeks in a year's strip, and in a repository's sparkline. */ export const YEAR = 52; export const RECENT = 12; /** The date it is at `now` in the zone `tz` (UTC if none), as `2026-10-25`. */ export function today(now: number, tz: string | null): string { return new Date(now).toLocaleDateString('en-CA', { timeZone: tz ?? 'UTC' }); } /** The first of the days in the `weeks` whole weeks to `today`, the last of them, as a UTC midnight to count days from. */ const first = (today: string, weeks: number) => Date.parse(`${today}T00:00:00Z`) - (weeks * 7 - 1) * DAY; /** * A time to ask for the `weeks` weeks to `today` since: midnight in UTC of * the day before the first, which falls on the day before it or the one * before that in any zone, so none of them is left out. */ export function since(today: string, weeks: number): string { return new Date(first(today, weeks) - DAY).toISOString(); } /** What `count` says of each of `days`, a week at a time, for the `weeks` weeks to `today`, oldest first. */ export function weekly( days: T[], today: string, weeks: number, count: (d: T) => number ): number[] { const from = first(today, weeks); const out = new Array(weeks).fill(0); for (const d of days) { const week = Math.floor((Date.parse(`${d.day}T00:00:00Z`) - from) / (7 * DAY)); if (week >= 0 && week < weeks) out[week] += count(d); } return out; } /** The first day of the `i`th of the `weeks` weeks to `today`, as `Oct 19`. */ export function weekOf(today: string, weeks: number, i: number): string { const at = first(today, weeks) + i * 7 * DAY; return new Date(at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' }); } /** What was done in a month: how many of each kind, and the pushes to each repository, most first. */ export type Month = { /** As `2026-10`. */ month: string; pushes: number; pulls: number; issues: number; comments: number; repos: number; pushed: { name: string; pushes: number }[]; }; /** `days` and each of `repos`' days, a month at a time, newest first: only the months with something done. */ export function monthly( days: { day: string; pushes?: number; pulls?: number; issues?: number; comments?: number; repos?: number; }[], repos: { name: string; days: { day: string; pushes: number }[] }[] ): Month[] { const months = new Map(); const at = (day: string) => { const month = day.slice(0, 7); let m = months.get(month); if (!m) months.set( month, (m = { month, pushes: 0, pulls: 0, issues: 0, comments: 0, repos: 0, pushed: [] }) ); return m; }; for (const d of days) { const m = at(d.day); m.pushes += d.pushes ?? 0; m.pulls += d.pulls ?? 0; m.issues += d.issues ?? 0; m.comments += d.comments ?? 0; m.repos += d.repos ?? 0; } for (const r of repos) { const each = new Map(); for (const d of r.days) each.set(d.day.slice(0, 7), (each.get(d.day.slice(0, 7)) ?? 0) + d.pushes); for (const [month, pushes] of each) at(`${month}-01`).pushed.push({ name: r.name, pushes }); } for (const m of months.values()) m.pushed.sort((a, b) => b.pushes - a.pushes || a.name.localeCompare(b.name)); return [...months.values()].sort((a, b) => b.month.localeCompare(a.month)); } /** `2026-10` as `October 2026`. */ export const monthName = (month: string) => new Date(`${month}-01T00:00:00Z`).toLocaleDateString('en-US', { month: 'long', year: 'numeric', timeZone: 'UTC' });