Functional Weave
Code in Rust

logistics.delivery-date@1.0.0

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;
  }
}