113 lines
2.8 KiB
TypeScript
113 lines
2.8 KiB
TypeScript
import { getCalendarHolidayStrings, toIsoDate } from "./holiday-availability.js";
|
|
|
|
type VacationSpan = {
|
|
startDate: Date;
|
|
endDate: Date;
|
|
isHalfDay: boolean;
|
|
};
|
|
|
|
type HolidayContext = {
|
|
countryCode?: string | null | undefined;
|
|
federalState?: string | null | undefined;
|
|
metroCityName?: string | null | undefined;
|
|
calendarHolidayStrings?: string[] | undefined;
|
|
publicHolidayStrings?: string[] | undefined;
|
|
};
|
|
|
|
type CountVacationChargeableDaysInput = HolidayContext & {
|
|
vacation: VacationSpan;
|
|
periodStart?: Date | undefined;
|
|
periodEnd?: Date | undefined;
|
|
};
|
|
|
|
function clampToDay(value: Date): Date {
|
|
const date = new Date(value);
|
|
date.setUTCHours(0, 0, 0, 0);
|
|
return date;
|
|
}
|
|
|
|
function getOverlapRange(
|
|
startDate: Date,
|
|
endDate: Date,
|
|
periodStart?: Date,
|
|
periodEnd?: Date,
|
|
): { start: Date; end: Date } | null {
|
|
const startBoundary = clampToDay(periodStart ?? startDate);
|
|
const endBoundary = clampToDay(periodEnd ?? endDate);
|
|
const overlapStart = clampToDay(new Date(Math.max(startDate.getTime(), startBoundary.getTime())));
|
|
const overlapEnd = clampToDay(new Date(Math.min(endDate.getTime(), endBoundary.getTime())));
|
|
|
|
if (overlapStart > overlapEnd) {
|
|
return null;
|
|
}
|
|
|
|
return { start: overlapStart, end: overlapEnd };
|
|
}
|
|
|
|
export function countCalendarDaysInPeriod(
|
|
vacation: VacationSpan,
|
|
periodStart?: Date,
|
|
periodEnd?: Date,
|
|
): number {
|
|
const overlap = getOverlapRange(vacation.startDate, vacation.endDate, periodStart, periodEnd);
|
|
if (!overlap) {
|
|
return 0;
|
|
}
|
|
|
|
if (vacation.isHalfDay) {
|
|
return 0.5;
|
|
}
|
|
|
|
const ms = overlap.end.getTime() - overlap.start.getTime();
|
|
return Math.round(ms / 86_400_000) + 1;
|
|
}
|
|
|
|
export function countVacationChargeableDays(
|
|
input: CountVacationChargeableDaysInput,
|
|
): number {
|
|
const overlap = getOverlapRange(
|
|
input.vacation.startDate,
|
|
input.vacation.endDate,
|
|
input.periodStart,
|
|
input.periodEnd,
|
|
);
|
|
if (!overlap) {
|
|
return 0;
|
|
}
|
|
|
|
const holidaySet = new Set(
|
|
input.calendarHolidayStrings
|
|
? input.calendarHolidayStrings.filter((isoDate) => isoDate >= toIsoDate(overlap.start) && isoDate <= toIsoDate(overlap.end))
|
|
: getCalendarHolidayStrings(
|
|
overlap.start,
|
|
overlap.end,
|
|
input.countryCode,
|
|
input.federalState,
|
|
input.metroCityName,
|
|
),
|
|
);
|
|
|
|
for (const isoDate of input.publicHolidayStrings ?? []) {
|
|
if (isoDate >= toIsoDate(overlap.start) && isoDate <= toIsoDate(overlap.end)) {
|
|
holidaySet.add(isoDate);
|
|
}
|
|
}
|
|
|
|
if (input.vacation.isHalfDay) {
|
|
return holidaySet.has(toIsoDate(overlap.start)) ? 0 : 0.5;
|
|
}
|
|
|
|
let total = 0;
|
|
const cursor = new Date(overlap.start);
|
|
|
|
while (cursor <= overlap.end) {
|
|
if (!holidaySet.has(toIsoDate(cursor))) {
|
|
total += 1;
|
|
}
|
|
|
|
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
}
|
|
|
|
return total;
|
|
}
|