impl/typescript.ts
2,692 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 { addBusinessDays } from "./dates_add_business_days.ts"; ← from dates.add-business-days ^1.0.0 · built alongside by fune
import { type UkRegion, bankHolidays } from "./dates_bank_holidays.ts"; ← from dates.bank-holidays ^1.0.0 · built alongside by fune
import { dayOfWeek } from "./dates_day_of_week.ts"; ← from dates.day-of-week ^1.0.0 · built alongside by fune
import { type DeliveryEstimate } from "./logistics_delivery_date_types.ts";
const TIME = /^([01][0-9]|2[0-3]):[0-5][0-9]$/;
function estimate(
orderDate: string,
orderTime: string,
cutOff: string,
serviceDays: number,
holidays: readonly string[],
): DeliveryEstimate {
const working = dayOfWeek(orderDate) <= 5 && !holidays.includes(orderDate);
// HH:MM compares correctly as text. Strictly before: 15:00 misses a 15:00 cut-off.
const sameDayDispatch = working && orderTime < cutOff;
const dispatchDate = sameDayDispatch ? orderDate : addBusinessDays(orderDate, 1, holidays);
return { dispatchDate, deliveryDate: addBusinessDays(dispatchDate, serviceDays, holidays), sameDayDispatch };
}
/**
* When an order leaves the warehouse and when it should arrive: same working
* day if placed before the cut-off, then serviceDays working days in transit,
* skipping weekends, the region's bank holidays and the caller's closures.
*/
export function deliveryDate(
orderDate: string,
orderTime: string,
cutOff: string,
serviceDays: number,
region: UkRegion | null,
closures: readonly string[],
): DeliveryEstimate {
if (typeof orderTime !== "string" || !TIME.test(orderTime)) {
throw new RangeError(`orderTime must be a 24-hour time HH:MM, received "${orderTime}"`);
}
if (typeof cutOff !== "string" || !TIME.test(cutOff)) {
throw new RangeError(`cutOff must be a 24-hour time HH:MM, received "${cutOff}"`);
}
if (!Number.isInteger(serviceDays)) throw new TypeError(`serviceDays must be an integer, received ${serviceDays}`);
if (serviceDays < 0) throw new RangeError(`serviceDays must not be negative, received ${serviceDays}`);
dayOfWeek(orderDate); // validates the date before its year is used
const holidays: string[] = [...closures];
let lastYear = Number(orderDate.slice(0, 4));
if (region !== null) for (const h of bankHolidays(region, lastYear)) holidays.push(h.date);
// Load each later year the estimate reaches: an order on 31 December must
// skip next year's New Year's Day. More holidays only move the answer later,
// so this settles.
for (;;) {
const result = estimate(orderDate, orderTime, cutOff, serviceDays, holidays);
const reached = Number(result.deliveryDate.slice(0, 4));
if (region === null || reached <= lastYear) return result;
for (let year = lastYear + 1; year <= reached; year++) {
for (const h of bankHolidays(region, year)) holidays.push(h.date);
}
lastYear = reached;
}
}