impl/typescript.ts
2,112 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { parseIsoDate } from "./dates_add_days.ts"; ← from dates.add-days ^1.0.0 · built alongside by fune
import { dayName } from "./dates_day_of_week.ts"; ← from dates.day-of-week ^1.0.0 · built alongside by fune
import { type Recurrence } from "./todo_item.ts"; ← from todo.item ^1.0.0 · built alongside by fune
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const FREQUENCIES = ["daily", "weekdays", "weekly", "monthly", "yearly"];
/** 1st, 2nd, 3rd, 4th ... 11th, 12th, 13th ... 21st, 22nd, 23rd ... 31st. */
function ordinal(n: number): string {
if (n % 100 >= 11 && n % 100 <= 13) return `${n}th`;
const suffix = n % 10 === 1 ? "st" : n % 10 === 2 ? "nd" : n % 10 === 3 ? "rd" : "th";
return `${n}${suffix}`;
}
function every(interval: number, unit: string): string {
return interval === 1 ? `Every ${unit}` : `Every ${interval} ${unit}s`;
}
/**
* The English words for a repeat rule, for the chip next to a todo:
* "Every weekday", "Every 2 weeks on Monday", "Every month on the 31st",
* "Every year on 29 Feb". The weekday, day and date come from the anchor, the
* same anchor todo.next-occurrence counts from, and invalid rules fail with
* its messages.
*/
export function recurrenceLabel(recurrence: Recurrence): string {
const { frequency, interval, anchor } = recurrence;
if (!FREQUENCIES.includes(frequency)) {
throw new RangeError(`unknown repeat frequency "${frequency}": expected daily, weekdays, weekly, monthly or yearly`);
}
if (!Number.isInteger(interval) || interval < 1) {
throw new RangeError(`repeat interval must be 1 or more, received ${interval}`);
}
if (frequency === "weekdays" && interval !== 1) {
throw new RangeError(`weekday repeats cannot skip weeks: interval must be 1, received ${interval}`);
}
const date = parseIsoDate(anchor);
switch (frequency) {
case "daily":
return every(interval, "day");
case "weekdays":
return "Every weekday";
case "weekly":
return `${every(interval, "week")} on ${dayName(anchor)}`;
case "monthly":
return `${every(interval, "month")} on the ${ordinal(date.day)}`;
default:
return `${every(interval, "year")} on ${date.day} ${MONTHS[date.month - 1]}`;
}
}