johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
// 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<T extends { day: string }>(
days: T[],
today: string,
weeks: number,
count: (d: T) => number
): number[] {
const from = first(today, weeks);
const out = new Array<number>(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<string, Month>();
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<string, number>();
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'
});