Skip to content

Instantly share code, notes, and snippets.

@LargatSeif
Last active June 18, 2024 17:52
Show Gist options
  • Save LargatSeif/1aacf76f1298b1319ab0327d0479a825 to your computer and use it in GitHub Desktop.
Save LargatSeif/1aacf76f1298b1319ab0327d0479a825 to your computer and use it in GitHub Desktop.
some time utilisites
import {
CalendarDate,
CalendarDateTime,
Time,
ZonedDateTime,
getLocalTimeZone,
now,
parseTime,
toCalendarDateTime,
toTime,
type DateTimeDuration,
type DateValue
} from '@internationalized/date';
/**
* Determine if a date is before the reference date.
* @param dateToCompare - The date to compare.
* @param referenceDate - The reference date to compare against.
* @returns `true` if the date is before the reference date, `false` otherwise.
*/
export function isBefore(dateToCompare: DateValue, referenceDate: DateValue): boolean {
return dateToCompare.compare(referenceDate) < 0;
}
/**
* Determine if a date is before or the same as the reference date.
*
* @param dateToCompare - the date to compare
* @param referenceDate - the reference date to make the comparison against
*
* @see {@link isBefore} for non-inclusive
*/
export function isBeforeOrSame(dateToCompare: DateValue, referenceDate: DateValue) {
return dateToCompare.compare(referenceDate) <= 0;
}
/**
* Determine if a date is after or the same as the reference date.
*
* @param dateToCompare - is this date after or the same as the `referenceDate`
* @param referenceDate - is the `dateToCompare` after or the same as this date
*
* @see {@link isAfter} for non-inclusive
*/
export function isAfterOrSame(dateToCompare: DateValue, referenceDate: DateValue) {
return dateToCompare.compare(referenceDate) >= 0;
}
/**
* Determine if a date is after the reference date.
* @param dateToCompare - The date to compare.
* @param referenceDate - The reference date to compare against.
* @returns `true` if the date is after the reference date, `false` otherwise.
*/
export function isAfter(dateToCompare: DateValue, referenceDate: DateValue): boolean {
return dateToCompare.compare(referenceDate) > 0;
}
/**
* Determine if a date is between a start and end reference date.
*
* @param date - is this date between the `start` and `end` dates
* @param start - the start reference date to make the comparison against
* @param end - the end reference date to make the comparison against
* @param type - The type of comparison to make. Default is exclusive. Possible values are: '[]', '[)', '(]', '()'
* @description The type of comparison to make. The default is exclusive. Possible values are:
* - '[]' - inclusive for both start and end
* - '[)' - inclusive for start, exclusive for end
* - '(]' - exclusive for start, inclusive for end
* - '()' - exclusive for both start and end
* @returns `true` if the date is between the start and end reference date, `false` otherwise
*/
export function isBetween(
date: DateValue,
start: DateValue,
end: DateValue,
type?: '[]' | '[)' | '(]' | '()'
): boolean {
if (type === '[]') return isAfterOrSame(date, start) && isBeforeOrSame(date, end);
if (type === '()') return isAfter(date, start) && isBefore(date, end);
if (type === '[)') return isAfterOrSame(date, start) && isBefore(date, end);
if (type === '(]') return isAfter(date, start) && isBeforeOrSame(date, end);
return isAfter(date, start) && isBefore(date, end);
}
/**
* Check if a given time slot has any conflicts with the list of events.
* @param slotStart - The start time of the time slot.
* @param slotEnd - The end time of the time slot.
* @param currentDate - The current date.
* @param events - The array of events that may conflict with the time slots.
* @returns `true` if there is a conflict, `false` otherwise.
*/
export function hasEventConflict(
slotStart: CalendarDateTime,
slotEnd: CalendarDateTime,
currentDate: CalendarDate,
events: BookedSlot[]
): boolean {
return events.some((event) => {
const eventStart = toCalendarDateTime(currentDate, parseTime(event.start.slice(0, 5)));
const eventEnd = toCalendarDateTime(currentDate, parseTime(event.end.slice(0, 5)));
return (
isBetween(eventStart, slotStart, slotEnd, '[)') || // inclusive check for start
isBetween(eventEnd, slotStart, slotEnd, '(]') || // inclusive check for end
(isBefore(eventStart, slotStart) && isAfter(eventEnd, slotEnd)) // event fully encompasses the slot
);
});
}
/**
* Represents a time slot.
* @property start - The start time of the time slot.
* @property end - The end time of the time slot.
* @property available - Whether the time slot is available or not.
*/
export type Slot = {
start: string;
end: string;
available: boolean;
};
/**
* Represents a booked time slot.
* @property start - The start time of the time slot.
* @property end - The end time of the time slot.
*/
export type BookedSlot = Omit<Slot, 'available'>;
/**
* Calculates the time slots based on the given parameters.
* @param start - The start time of the time slots.
* @param end - The end time of the time slots.
* @param currentDate - The current date.
* @param duration - The duration of each time slot in minutes.
* @param unit - The unit of the duration.
* @param events - The array of events that may conflict with the time slots.
* @returns The array of calculated time slots.
*/
export function generateTimeSlots(
start: Time,
end: Time,
currentDate: CalendarDate,
duration: number,
unit: keyof DateTimeDuration = 'minutes',
events: BookedSlot[]
): Slot[] {
if (start.compare(end) >= 0) {
throw new Error('The start time must be before the end time.');
}
if (duration <= 0) {
throw new Error('The duration must be greater than 0.');
}
const slots: Slot[] = [];
const currentTime: ZonedDateTime = now(getLocalTimeZone());
const parsedStart: CalendarDateTime = toCalendarDateTime(currentDate, start);
const parsedEnd: CalendarDateTime = toCalendarDateTime(currentDate, end);
let currentSlotStart: CalendarDateTime | ZonedDateTime = parsedStart.copy();
// Sort events by start time
events.sort((a, b) => parseTime(a.start).compare(parseTime(b.start)));
// Generate time slots until the end time is reached
while (isBefore(currentSlotStart, parsedEnd)) {
const slotEnd = currentSlotStart.add({ [unit]: duration });
let eventConflict = false;
if (events.length > 0) {
eventConflict = hasEventConflict(currentSlotStart, slotEnd, currentDate, events);
}
slots.push({
start: toTime(currentSlotStart).toString(),
end: toTime(slotEnd).toString(),
available: !eventConflict && isAfter(currentSlotStart, currentTime)
});
currentSlotStart = slotEnd;
}
return slots;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment