Last active
June 18, 2024 17:52
-
-
Save LargatSeif/1aacf76f1298b1319ab0327d0479a825 to your computer and use it in GitHub Desktop.
some time utilisites
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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