~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
114 lines · 3.6 KB Raw History
// 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'
	});