import { eq, gt, gte, lt, lte } from "./ZonedDateTime.js"; import * as Either from "effect/Either"; /** * an interval is the time frame between two Temporal.ZonedDateTimes, including the start and excluding the end. */ export type Interval = { readonly start: Temporal.ZonedDateTime; readonly end: Temporal.ZonedDateTime; }; /** * whether `date` is inside `interval`. "inside" means that the start can be * equal to date while the end has to be strictly after the `date`. This is an * opinionated approach because often the end of an interval coincides with the * start of the next one, e.g. the current day ends when the next one start, and * not one picosecond sooner. */ export const contains = (interval: Interval, date: Temporal.ZonedDateTime) => lte(interval.start, date) && lt(date, interval.end); /** * whether a completely covers b */ export const covers = (a: Interval, b: Interval) => gte(b.start, a.start) && lte(b.end, a.end); /** * calculates the intesection between two intervals. If the intervals have no * overlap undefined is returned. */ export const intersection = (a: Interval, b: Interval): Interval | undefined => lt(a.end, b.start) || lt(b.end, a.start) ? undefined : { start: lt(a.start, b.start) ? b.start : a.start, end: lt(a.end, b.end) ? a.end : b.end, }; /** * get the duration of an interval */ export const durationOf = ({ start, end }: Interval) => start.until(end); /** * whether a overlaps b */ export const overlaps = (a: Interval, b: Interval) => { const intersected = intersection(a, b); return intersected != null && durationOf(intersected).sign !== 0; }; export const fromDuration = ( duration: Temporal.DurationLike, start: Temporal.ZonedDateTime, ): Interval => ({ start, end: start.add(duration), }); export const stringify = (interval: Interval) => `${interval.start.toString()}/${interval.end.toString()}`; export const parse = (string: string): Either.Either => { const [start, end] = string.split(/(?<=\])\//) as [ string, string | undefined, ]; if (end == null) { return Either.left("invalid string"); } return Either.Do.pipe( Either.bind("start", () => Either.try(() => Temporal.ZonedDateTime.from(start)), ), Either.bind("end", () => Either.try(() => Temporal.ZonedDateTime.from(end)), ), ); }; export const equals = (a: Interval, b: Interval) => { return eq(a.start, b.start) && eq(a.end, b.end); };