UNPKG

calendar-date

Version:

Immutable object to represent a calendar date with zero dependencies

154 lines (132 loc) 4.71 kB
import { CalendarDate } from './CalendarDate'; type DateRangeExcludeOptions = { excludeStart?: boolean; excludeEnd?: boolean; }; export class CalendarDateRange { readonly start: CalendarDate; readonly end: CalendarDate; constructor(start: CalendarDate, end: CalendarDate, autoArrange = false) { if (!autoArrange && end < start) { throw new Error( "CalendarDateRange Validation Error: end date can't be before the start date.", ); } this.start = start <= end ? start : end; this.end = end > start ? end : start; Object.freeze(this); } equals(calendarDateRange: CalendarDateRange): boolean { return this.start.equals(calendarDateRange.start) && this.end.equals(calendarDateRange.end); } toString(): string { return `${this.start.toString()}/${this.end.toString()}`; } toJSON(): string { return this.toString(); } /** * @param isoString pattern YYYY-MM-DD/YYYY-MM-DD */ static parse(isoString: string): CalendarDateRange { const split = isoString.split('/'); if (split.length !== 2) { throw new Error( `CalendarDateRange Validation Error: Input ${isoString.toString()} is not valid, it should follow the pattern YYYY-MM-DD/YYYY-MM-DD.`, ); } return new CalendarDateRange(new CalendarDate(split[0]), new CalendarDate(split[1])); } /** * Returns true if the given date ranges have gaps. * The date ranges will be sorted. */ static hasGaps(values: CalendarDateRange[], options?: DateRangeExcludeOptions): boolean { const sortedValues = values.sort((a, b) => a.start.valueOf() - b.start.valueOf()); for (let i = 1; i < sortedValues.length; i++) { let differenceInDays = sortedValues[i].start.getDifferenceInDays(sortedValues[i - 1].end); if (options?.excludeStart) { differenceInDays += 1; } if (options?.excludeEnd) { differenceInDays += 1; } if (differenceInDays > 1) { return true; } } return false; } /** * Returns true if the given date ranges overlap. * The date ranges will be sorted. */ static hasOverlap(values: CalendarDateRange[], options?: DateRangeExcludeOptions): boolean { const sortedValues = values.sort((a, b) => a.start.valueOf() - b.start.valueOf()); for (let i = 1; i < sortedValues.length; i++) { let differenceInDays = sortedValues[i].start.getDifferenceInDays(sortedValues[i - 1].end); if (options?.excludeStart) { differenceInDays += 1; } if (options?.excludeEnd) { differenceInDays += 1; } if (differenceInDays <= 0) { return true; } } return false; } /** * Returns the total amount of days in included in this date range * including start and end day. */ getTotalDays(): number { return this.end.getDifferenceInDays(this.start) + 1; } /** * Returns the difference in days between the start and end date. * See documentation of CalendarDate.getDifferenceInDays for more information. */ getDifferenceInDays(): number { return this.end.getDifferenceInDays(this.start); } /** * Returns the difference in months as an integer, ignoring the day values. */ getDifferenceInMonths(): number { return (this.end.year - this.start.year) * 12 + (this.end.month - this.start.month); } includes(calendarDate: CalendarDate, options?: DateRangeExcludeOptions): boolean; includes(calendarDateRange: CalendarDateRange, options?: DateRangeExcludeOptions): boolean; includes(input: CalendarDate | CalendarDateRange, options?: DateRangeExcludeOptions): boolean { if (input instanceof CalendarDateRange) { return this.includes(input.start, options) && this.includes(input.end, options); } return ( (options?.excludeStart ? input > this.start : input >= this.start) && (options?.excludeEnd ? input < this.end : input <= this.end) ); } /** * Creates an iterator that yields each CalendarDate in the range from start to end (inclusive). */ *iterateDatesInRange(options?: DateRangeExcludeOptions): IterableIterator<CalendarDate> { let current = this.start; // Skip start date if excludeStart is true if (options?.excludeStart) { current = current.addDays(1); } const endDate = options?.excludeEnd ? this.end.addDays(-1) : this.end; while (current <= endDate) { yield current; current = current.addDays(1); } } /** * Returns an array of all CalendarDate objects in the range. */ toDatesArrayInRange(options?: DateRangeExcludeOptions): CalendarDate[] { return Array.from(this.iterateDatesInRange(options)); } }