ts-time-utils 4.0.1 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -30
- package/dist/{age.js → age.cjs} +14 -6
- package/dist/{calculate.js → calculate.cjs} +30 -18
- package/dist/{calendar.js → calendar.cjs} +80 -39
- package/dist/{calendars.js → calendars.cjs} +48 -23
- package/dist/{chain.js → chain.cjs} +41 -40
- package/dist/{compare.js → compare.cjs} +58 -28
- package/dist/constants.cjs +19 -0
- package/dist/{countdown.js → countdown.cjs} +16 -7
- package/dist/{cron.js → cron.cjs} +20 -9
- package/dist/{dateRange.js → dateRange.cjs} +42 -26
- package/dist/{duration.js → duration.cjs} +56 -44
- package/dist/esm/chain.js +0 -5
- package/dist/esm/finance.d.ts +236 -0
- package/dist/esm/finance.d.ts.map +1 -0
- package/dist/esm/finance.js +495 -0
- package/dist/esm/healthcare.d.ts +260 -0
- package/dist/esm/healthcare.d.ts.map +1 -0
- package/dist/esm/healthcare.js +447 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/naturalLanguage.d.ts +1 -3
- package/dist/esm/naturalLanguage.d.ts.map +1 -1
- package/dist/esm/naturalLanguage.js +9 -2
- package/dist/esm/plugins.d.ts +0 -6
- package/dist/esm/plugins.d.ts.map +1 -1
- package/dist/esm/plugins.js +36 -42
- package/dist/esm/recurrence.d.ts.map +1 -1
- package/dist/esm/recurrence.js +3 -5
- package/dist/esm/scheduling.d.ts +206 -0
- package/dist/esm/scheduling.d.ts.map +1 -0
- package/dist/esm/scheduling.js +329 -0
- package/dist/esm/timezone.d.ts +6 -1
- package/dist/esm/timezone.d.ts.map +1 -1
- package/dist/esm/timezone.js +106 -66
- package/dist/esm/types.d.ts +0 -4
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/finance.cjs +512 -0
- package/dist/finance.d.ts +236 -0
- package/dist/finance.d.ts.map +1 -0
- package/dist/{fiscal.js → fiscal.cjs} +36 -17
- package/dist/{format.js → format.cjs} +83 -70
- package/dist/healthcare.cjs +462 -0
- package/dist/healthcare.d.ts +260 -0
- package/dist/healthcare.d.ts.map +1 -0
- package/dist/{holidays.js → holidays.cjs} +52 -25
- package/dist/index.cjs +595 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/{interval.js → interval.cjs} +24 -11
- package/dist/{iterate.js → iterate.cjs} +84 -41
- package/dist/{locale.js → locale.cjs} +54 -26
- package/dist/{naturalLanguage.js → naturalLanguage.cjs} +36 -23
- package/dist/naturalLanguage.d.ts +1 -3
- package/dist/naturalLanguage.d.ts.map +1 -1
- package/dist/{parse.js → parse.cjs} +24 -11
- package/dist/{performance.js → performance.cjs} +23 -10
- package/dist/{plugins.js → plugins.cjs} +48 -47
- package/dist/plugins.d.ts +0 -6
- package/dist/plugins.d.ts.map +1 -1
- package/dist/{precision.js → precision.cjs} +74 -37
- package/dist/{rangePresets.js → rangePresets.cjs} +40 -19
- package/dist/{recurrence.js → recurrence.cjs} +27 -21
- package/dist/recurrence.d.ts.map +1 -1
- package/dist/scheduling.cjs +344 -0
- package/dist/scheduling.d.ts +206 -0
- package/dist/scheduling.d.ts.map +1 -0
- package/dist/{serialize.js → serialize.cjs} +36 -17
- package/dist/{temporal.js → temporal.cjs} +28 -13
- package/dist/{timezone.js → timezone.cjs} +140 -82
- package/dist/timezone.d.ts +6 -1
- package/dist/timezone.d.ts.map +1 -1
- package/dist/{types.js → types.cjs} +9 -3
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/{validate.js → validate.cjs} +54 -26
- package/dist/{workingHours.js → workingHours.cjs} +36 -17
- package/package.json +52 -34
- package/dist/constants.js +0 -16
- package/dist/index.js +0 -66
package/dist/esm/plugins.js
CHANGED
|
@@ -19,38 +19,20 @@
|
|
|
19
19
|
* chain(new Date()).nextMonday().format('YYYY-MM-DD');
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
// We need the actual class (not just the type) to modify its prototype
|
|
24
|
-
let ChainedDateConstructor = null;
|
|
22
|
+
import { ChainedDate } from './chain.js';
|
|
25
23
|
/**
|
|
26
|
-
* Get ChainedDate class
|
|
24
|
+
* Get ChainedDate class for prototype mutation.
|
|
25
|
+
* Importing `plugins` now brings in `chain` directly, so the class is available
|
|
26
|
+
* without a hidden global handshake.
|
|
27
27
|
*/
|
|
28
28
|
function getChainedDate() {
|
|
29
|
-
if (!
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// Try ESM import path first
|
|
33
|
-
ChainedDateConstructor = globalThis.__chainedDateClass;
|
|
34
|
-
if (!ChainedDateConstructor) {
|
|
35
|
-
// Fallback: The class will be set by chain.js when it loads
|
|
36
|
-
throw new Error('ChainedDate not yet loaded. Import chain.js before using plugins.');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
catch (e) {
|
|
40
|
-
throw new Error('ChainedDate class not available. Ensure chain.js is imported before registering plugins.');
|
|
41
|
-
}
|
|
29
|
+
if (!ChainedDate) {
|
|
30
|
+
throw new Error('ChainedDate export is not available yet. Import chain.js before registering plugins.');
|
|
42
31
|
}
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Initialize the plugin system with ChainedDate class
|
|
47
|
-
* This is called automatically when chain.js is imported
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
50
|
-
export function __initPluginSystem(ChainedDateClass) {
|
|
51
|
-
ChainedDateConstructor = ChainedDateClass;
|
|
32
|
+
return ChainedDate;
|
|
52
33
|
}
|
|
53
34
|
const registry = {};
|
|
35
|
+
const methodStates = {};
|
|
54
36
|
/**
|
|
55
37
|
* Extend ChainedDate with custom methods
|
|
56
38
|
*
|
|
@@ -80,23 +62,23 @@ export function extend(pluginName, methods) {
|
|
|
80
62
|
if (registry[pluginName]) {
|
|
81
63
|
throw new Error(`Plugin "${pluginName}" is already registered. Use a different name or uninstall first.`);
|
|
82
64
|
}
|
|
83
|
-
// Get ChainedDate class and save original methods before overwriting
|
|
84
65
|
const ChainedDateClass = getChainedDate();
|
|
85
|
-
const originalMethods = new Map();
|
|
86
66
|
Object.entries(methods).forEach(([methodName, fn]) => {
|
|
87
|
-
|
|
67
|
+
const current = ChainedDateClass.prototype[methodName];
|
|
68
|
+
if (!methodStates[methodName]) {
|
|
69
|
+
methodStates[methodName] = {
|
|
70
|
+
original: typeof current === 'function' ? current : undefined,
|
|
71
|
+
owners: []
|
|
72
|
+
};
|
|
73
|
+
}
|
|
88
74
|
if (methodName in ChainedDateClass.prototype) {
|
|
89
|
-
const original = ChainedDateClass.prototype[methodName];
|
|
90
|
-
if (typeof original === 'function') {
|
|
91
|
-
originalMethods.set(methodName, original);
|
|
92
|
-
}
|
|
93
75
|
console.warn(`Method "${methodName}" already exists on ChainedDate and will be overwritten`);
|
|
94
76
|
}
|
|
77
|
+
methodStates[methodName].owners.push(pluginName);
|
|
95
78
|
// Add the plugin method
|
|
96
79
|
ChainedDateClass.prototype[methodName] = fn;
|
|
97
80
|
});
|
|
98
|
-
|
|
99
|
-
registry[pluginName] = { plugin: methods, originalMethods };
|
|
81
|
+
registry[pluginName] = { plugin: methods };
|
|
100
82
|
}
|
|
101
83
|
/**
|
|
102
84
|
* Remove a plugin and its methods from ChainedDate
|
|
@@ -113,20 +95,32 @@ export function uninstall(pluginName) {
|
|
|
113
95
|
if (!entry) {
|
|
114
96
|
throw new Error(`Plugin "${pluginName}" is not registered`);
|
|
115
97
|
}
|
|
116
|
-
// Get ChainedDate class and restore/remove methods
|
|
117
98
|
const ChainedDateClass = getChainedDate();
|
|
118
99
|
Object.keys(entry.plugin).forEach((methodName) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
100
|
+
const state = methodStates[methodName];
|
|
101
|
+
if (!state) {
|
|
102
|
+
delete ChainedDateClass.prototype[methodName];
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
state.owners = state.owners.filter((owner) => owner !== pluginName);
|
|
106
|
+
const nextOwner = state.owners[state.owners.length - 1];
|
|
107
|
+
if (nextOwner) {
|
|
108
|
+
const nextPlugin = registry[nextOwner];
|
|
109
|
+
if (nextPlugin && methodName in nextPlugin.plugin) {
|
|
110
|
+
ChainedDateClass.prototype[methodName] = nextPlugin.plugin[methodName];
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
delete ChainedDateClass.prototype[methodName];
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (state.original) {
|
|
117
|
+
ChainedDateClass.prototype[methodName] = state.original;
|
|
123
118
|
}
|
|
124
119
|
else {
|
|
125
|
-
// Otherwise, delete the method entirely
|
|
126
120
|
delete ChainedDateClass.prototype[methodName];
|
|
127
121
|
}
|
|
122
|
+
delete methodStates[methodName];
|
|
128
123
|
});
|
|
129
|
-
// Remove from registry
|
|
130
124
|
delete registry[pluginName];
|
|
131
125
|
}
|
|
132
126
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recurrence.d.ts","sourceRoot":"","sources":["../../src/recurrence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc;;oCAKjB,SAAS;mCACV,SAAS,OAAO,SAAS,UAAU,MAAM;6BAE/C,SAAS;;
|
|
1
|
+
{"version":3,"file":"recurrence.d.ts","sourceRoot":"","sources":["../../src/recurrence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc;;oCAKjB,SAAS;mCACV,SAAS,OAAO,SAAS,UAAU,MAAM;6BAE/C,SAAS;;EAmBrC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAyC1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,SAAS,EACd,KAAK,SAAO,GACX,IAAI,EAAE,CA6BR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAW/E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAgC5E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CA6C/D"}
|
package/dist/esm/recurrence.js
CHANGED
|
@@ -43,19 +43,17 @@ export function createRecurrence(rule) {
|
|
|
43
43
|
isRecurrenceDate: (date) => isRecurrenceDate(date, rule),
|
|
44
44
|
getAllOccurrences: (limit = 100) => {
|
|
45
45
|
const occurrences = [];
|
|
46
|
-
let current = new Date(startDate);
|
|
47
|
-
|
|
48
|
-
while (count < limit) {
|
|
46
|
+
let current = new Date(startDate.getTime() - 1);
|
|
47
|
+
while (occurrences.length < limit) {
|
|
49
48
|
if (rule.until && current > new Date(rule.until))
|
|
50
49
|
break;
|
|
51
|
-
if (rule.count &&
|
|
50
|
+
if (rule.count && occurrences.length >= rule.count)
|
|
52
51
|
break;
|
|
53
52
|
const next = getNextOccurrence(rule, current);
|
|
54
53
|
if (!next)
|
|
55
54
|
break;
|
|
56
55
|
occurrences.push(next);
|
|
57
56
|
current = new Date(next.getTime() + 1);
|
|
58
|
-
count++;
|
|
59
57
|
}
|
|
60
58
|
return occurrences;
|
|
61
59
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Scheduling and booking utilities
|
|
3
|
+
* Provides slot generation, availability checking, and conflict detection
|
|
4
|
+
*/
|
|
5
|
+
import type { DateRange, DateInput, WorkingHoursConfig, RecurrenceRule } from './types.js';
|
|
6
|
+
/** Configuration for scheduling operations */
|
|
7
|
+
export interface SchedulingConfig {
|
|
8
|
+
/** Working hours configuration */
|
|
9
|
+
workingHours?: WorkingHoursConfig;
|
|
10
|
+
/** Buffer time between appointments in minutes */
|
|
11
|
+
bufferMinutes?: number;
|
|
12
|
+
/** Default slot duration in minutes */
|
|
13
|
+
slotDuration?: number;
|
|
14
|
+
/** Holidays to exclude */
|
|
15
|
+
holidays?: Date[];
|
|
16
|
+
}
|
|
17
|
+
/** A time slot with availability status */
|
|
18
|
+
export interface Slot {
|
|
19
|
+
start: Date;
|
|
20
|
+
end: Date;
|
|
21
|
+
available: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** A booking with optional metadata */
|
|
24
|
+
export interface Booking extends DateRange {
|
|
25
|
+
id?: string;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/** Default scheduling configuration */
|
|
29
|
+
export declare const DEFAULT_SCHEDULING_CONFIG: SchedulingConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Generates time slots for a single day
|
|
32
|
+
* @param date - The date to generate slots for
|
|
33
|
+
* @param config - Scheduling configuration
|
|
34
|
+
* @returns Array of slots for the day
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const slots = generateSlots(new Date('2024-01-15'), { slotDuration: 30 });
|
|
39
|
+
* // Returns 30-minute slots during working hours
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateSlots(date: DateInput, config?: SchedulingConfig): Slot[];
|
|
43
|
+
/**
|
|
44
|
+
* Generates time slots for a date range
|
|
45
|
+
* @param range - The date range to generate slots for
|
|
46
|
+
* @param config - Scheduling configuration
|
|
47
|
+
* @returns Array of slots for all days in range
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const range = { start: new Date('2024-01-15'), end: new Date('2024-01-17') };
|
|
52
|
+
* const slots = generateSlotsForRange(range, { slotDuration: 60 });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function generateSlotsForRange(range: DateRange, config?: SchedulingConfig): Slot[];
|
|
56
|
+
/**
|
|
57
|
+
* Gets available slots for a day, excluding existing bookings
|
|
58
|
+
* @param date - The date to check
|
|
59
|
+
* @param bookings - Existing bookings
|
|
60
|
+
* @param config - Scheduling configuration
|
|
61
|
+
* @returns Array of available slots
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const bookings = [{ start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') }];
|
|
66
|
+
* const available = getAvailableSlots(new Date('2024-01-15'), bookings);
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function getAvailableSlots(date: DateInput, bookings: Booking[], config?: SchedulingConfig): Slot[];
|
|
70
|
+
/**
|
|
71
|
+
* Finds the next available slot of specified duration
|
|
72
|
+
* @param after - Start searching after this date
|
|
73
|
+
* @param bookings - Existing bookings
|
|
74
|
+
* @param duration - Required slot duration in minutes
|
|
75
|
+
* @param config - Scheduling configuration
|
|
76
|
+
* @returns Next available slot or null if none found within 30 days
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const nextSlot = findNextAvailable(new Date(), bookings, 60);
|
|
81
|
+
* if (nextSlot) console.log(`Next 1-hour slot at ${nextSlot.start}`);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function findNextAvailable(after: DateInput, bookings: Booking[], duration: number, config?: SchedulingConfig): Slot | null;
|
|
85
|
+
/**
|
|
86
|
+
* Checks if a slot is available (no conflicts with existing bookings)
|
|
87
|
+
* @param slot - The slot to check
|
|
88
|
+
* @param bookings - Existing bookings
|
|
89
|
+
* @returns True if slot is available
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* const slot = { start: new Date('2024-01-15T14:00'), end: new Date('2024-01-15T15:00') };
|
|
94
|
+
* if (isSlotAvailable(slot, existingBookings)) {
|
|
95
|
+
* // Book the slot
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function isSlotAvailable(slot: DateRange, bookings: Booking[]): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Finds bookings that conflict with a proposed time range
|
|
102
|
+
* @param bookings - Existing bookings
|
|
103
|
+
* @param proposed - Proposed time range
|
|
104
|
+
* @returns Array of conflicting bookings
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* const conflicts = findConflicts(existingBookings, { start: propStart, end: propEnd });
|
|
109
|
+
* if (conflicts.length > 0) {
|
|
110
|
+
* console.log('Conflicts with:', conflicts);
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare function findConflicts(bookings: Booking[], proposed: DateRange): Booking[];
|
|
115
|
+
/**
|
|
116
|
+
* Checks if a proposed time range has any conflicts
|
|
117
|
+
* @param bookings - Existing bookings
|
|
118
|
+
* @param proposed - Proposed time range
|
|
119
|
+
* @returns True if there are conflicts
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* if (hasConflict(existingBookings, proposedMeeting)) {
|
|
124
|
+
* console.log('Time slot not available');
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export declare function hasConflict(bookings: Booking[], proposed: DateRange): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Adds buffer time around a slot
|
|
131
|
+
* @param slot - The original slot
|
|
132
|
+
* @param bufferMinutes - Buffer time in minutes
|
|
133
|
+
* @returns New slot with buffer added
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* const slot = { start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') };
|
|
138
|
+
* const buffered = addBuffer(slot, 15);
|
|
139
|
+
* // buffered.start = 09:45, buffered.end = 11:15
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export declare function addBuffer(slot: DateRange, bufferMinutes: number): DateRange;
|
|
143
|
+
/**
|
|
144
|
+
* Removes buffer time from a slot
|
|
145
|
+
* @param slot - The buffered slot
|
|
146
|
+
* @param bufferMinutes - Buffer time in minutes to remove
|
|
147
|
+
* @returns New slot with buffer removed
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const bufferedSlot = { start: new Date('2024-01-15T09:45'), end: new Date('2024-01-15T11:15') };
|
|
152
|
+
* const original = removeBuffer(bufferedSlot, 15);
|
|
153
|
+
* // original.start = 10:00, original.end = 11:00
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare function removeBuffer(slot: DateRange, bufferMinutes: number): DateRange;
|
|
157
|
+
/**
|
|
158
|
+
* Expands recurring availability pattern into concrete slots
|
|
159
|
+
* @param pattern - Recurrence pattern
|
|
160
|
+
* @param range - Date range to expand within
|
|
161
|
+
* @param config - Scheduling configuration
|
|
162
|
+
* @returns Array of slots from the recurring pattern
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* const pattern = {
|
|
167
|
+
* frequency: 'weekly',
|
|
168
|
+
* startDate: new Date('2024-01-01'),
|
|
169
|
+
* byWeekday: [1, 3, 5], // Mon, Wed, Fri
|
|
170
|
+
* until: new Date('2024-12-31')
|
|
171
|
+
* };
|
|
172
|
+
* const slots = expandRecurringAvailability(pattern, range);
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export declare function expandRecurringAvailability(pattern: RecurrenceRule, range: DateRange, config?: SchedulingConfig): Slot[];
|
|
176
|
+
/**
|
|
177
|
+
* Merges adjacent or overlapping bookings
|
|
178
|
+
* @param bookings - Array of bookings to merge
|
|
179
|
+
* @returns Array of merged bookings
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const bookings = [
|
|
184
|
+
* { start: new Date('2024-01-15T09:00'), end: new Date('2024-01-15T10:00') },
|
|
185
|
+
* { start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') }
|
|
186
|
+
* ];
|
|
187
|
+
* const merged = mergeBookings(bookings);
|
|
188
|
+
* // [{ start: 09:00, end: 11:00 }]
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export declare function mergeBookings(bookings: Booking[]): Booking[];
|
|
192
|
+
/**
|
|
193
|
+
* Splits a slot at a specific time
|
|
194
|
+
* @param slot - The slot to split
|
|
195
|
+
* @param at - The time to split at
|
|
196
|
+
* @returns Tuple of two slots, or null if split point is outside slot
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* const slot = { start: new Date('2024-01-15T09:00'), end: new Date('2024-01-15T11:00'), available: true };
|
|
201
|
+
* const [before, after] = splitSlot(slot, new Date('2024-01-15T10:00'));
|
|
202
|
+
* // before: 09:00-10:00, after: 10:00-11:00
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export declare function splitSlot(slot: Slot, at: DateInput): [Slot, Slot] | null;
|
|
206
|
+
//# sourceMappingURL=scheduling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduling.d.ts","sourceRoot":"","sources":["../../src/scheduling.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK3F,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;IACV,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,uCAAuC;AACvC,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,uCAAuC;AACvC,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAkBF;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,GAAE,gBAAqB,GAAG,IAAI,EAAE,CAqCpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,GAAE,gBAAqB,GAAG,IAAI,EAAE,CAe7F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,GAAE,gBAAqB,GAC5B,IAAI,EAAE,CAmBR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,gBAAqB,GAC5B,IAAI,GAAG,IAAI,CAmBb;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAE7E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,GAAG,OAAO,EAAE,CAEjF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,GAAG,OAAO,CAE7E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,CAM3E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,CAM9E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,SAAS,EAChB,MAAM,GAAE,gBAAqB,GAC5B,IAAI,EAAE,CAUR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAS5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,CAmBxE"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Scheduling and booking utilities
|
|
3
|
+
* Provides slot generation, availability checking, and conflict detection
|
|
4
|
+
*/
|
|
5
|
+
import { dateRangeOverlap, mergeDateRanges } from './dateRange.js';
|
|
6
|
+
import { isWorkingDay, isWorkingTime, DEFAULT_WORKING_HOURS, getWorkDayStart, getWorkDayEnd } from './workingHours.js';
|
|
7
|
+
import { getOccurrencesBetween } from './recurrence.js';
|
|
8
|
+
/** Default scheduling configuration */
|
|
9
|
+
export const DEFAULT_SCHEDULING_CONFIG = {
|
|
10
|
+
workingHours: DEFAULT_WORKING_HOURS,
|
|
11
|
+
bufferMinutes: 0,
|
|
12
|
+
slotDuration: 30,
|
|
13
|
+
holidays: []
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Helper to convert DateInput to Date
|
|
17
|
+
*/
|
|
18
|
+
function toDate(input) {
|
|
19
|
+
if (input instanceof Date)
|
|
20
|
+
return new Date(input);
|
|
21
|
+
return new Date(input);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a date is a holiday
|
|
25
|
+
*/
|
|
26
|
+
function isHoliday(date, holidays) {
|
|
27
|
+
const dateStr = date.toISOString().split('T')[0];
|
|
28
|
+
return holidays.some(h => h.toISOString().split('T')[0] === dateStr);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generates time slots for a single day
|
|
32
|
+
* @param date - The date to generate slots for
|
|
33
|
+
* @param config - Scheduling configuration
|
|
34
|
+
* @returns Array of slots for the day
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const slots = generateSlots(new Date('2024-01-15'), { slotDuration: 30 });
|
|
39
|
+
* // Returns 30-minute slots during working hours
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function generateSlots(date, config = {}) {
|
|
43
|
+
const d = toDate(date);
|
|
44
|
+
const cfg = { ...DEFAULT_SCHEDULING_CONFIG, ...config };
|
|
45
|
+
const workingHours = cfg.workingHours ?? DEFAULT_WORKING_HOURS;
|
|
46
|
+
// Check if it's a working day and not a holiday
|
|
47
|
+
if (!isWorkingDay(d, workingHours))
|
|
48
|
+
return [];
|
|
49
|
+
if (cfg.holidays && isHoliday(d, cfg.holidays))
|
|
50
|
+
return [];
|
|
51
|
+
const slots = [];
|
|
52
|
+
const slotDuration = cfg.slotDuration ?? 30;
|
|
53
|
+
const dayStart = getWorkDayStart(d, workingHours);
|
|
54
|
+
const dayEnd = getWorkDayEnd(d, workingHours);
|
|
55
|
+
let current = new Date(dayStart);
|
|
56
|
+
while (current < dayEnd) {
|
|
57
|
+
const slotEnd = new Date(current.getTime() + slotDuration * 60 * 1000);
|
|
58
|
+
// Don't create slots that extend past working hours
|
|
59
|
+
if (slotEnd <= dayEnd) {
|
|
60
|
+
// Check if slot is during working time (not during breaks)
|
|
61
|
+
const midpoint = new Date(current.getTime() + (slotDuration * 60 * 1000) / 2);
|
|
62
|
+
const available = isWorkingTime(midpoint, workingHours);
|
|
63
|
+
slots.push({
|
|
64
|
+
start: new Date(current),
|
|
65
|
+
end: new Date(slotEnd),
|
|
66
|
+
available
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
current = slotEnd;
|
|
70
|
+
}
|
|
71
|
+
return slots;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generates time slots for a date range
|
|
75
|
+
* @param range - The date range to generate slots for
|
|
76
|
+
* @param config - Scheduling configuration
|
|
77
|
+
* @returns Array of slots for all days in range
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const range = { start: new Date('2024-01-15'), end: new Date('2024-01-17') };
|
|
82
|
+
* const slots = generateSlotsForRange(range, { slotDuration: 60 });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function generateSlotsForRange(range, config = {}) {
|
|
86
|
+
const slots = [];
|
|
87
|
+
const current = new Date(range.start);
|
|
88
|
+
current.setHours(0, 0, 0, 0);
|
|
89
|
+
const endDate = new Date(range.end);
|
|
90
|
+
endDate.setHours(23, 59, 59, 999);
|
|
91
|
+
while (current <= endDate) {
|
|
92
|
+
const daySlots = generateSlots(current, config);
|
|
93
|
+
slots.push(...daySlots);
|
|
94
|
+
current.setDate(current.getDate() + 1);
|
|
95
|
+
}
|
|
96
|
+
return slots;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Gets available slots for a day, excluding existing bookings
|
|
100
|
+
* @param date - The date to check
|
|
101
|
+
* @param bookings - Existing bookings
|
|
102
|
+
* @param config - Scheduling configuration
|
|
103
|
+
* @returns Array of available slots
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const bookings = [{ start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') }];
|
|
108
|
+
* const available = getAvailableSlots(new Date('2024-01-15'), bookings);
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function getAvailableSlots(date, bookings, config = {}) {
|
|
112
|
+
const cfg = { ...DEFAULT_SCHEDULING_CONFIG, ...config };
|
|
113
|
+
const slots = generateSlots(date, cfg);
|
|
114
|
+
const bufferMs = (cfg.bufferMinutes ?? 0) * 60 * 1000;
|
|
115
|
+
return slots.map(slot => {
|
|
116
|
+
// Expand slot by buffer for conflict checking
|
|
117
|
+
const checkRange = {
|
|
118
|
+
start: new Date(slot.start.getTime() - bufferMs),
|
|
119
|
+
end: new Date(slot.end.getTime() + bufferMs)
|
|
120
|
+
};
|
|
121
|
+
const hasConflict = bookings.some(booking => dateRangeOverlap(checkRange, booking));
|
|
122
|
+
return {
|
|
123
|
+
...slot,
|
|
124
|
+
available: slot.available && !hasConflict
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Finds the next available slot of specified duration
|
|
130
|
+
* @param after - Start searching after this date
|
|
131
|
+
* @param bookings - Existing bookings
|
|
132
|
+
* @param duration - Required slot duration in minutes
|
|
133
|
+
* @param config - Scheduling configuration
|
|
134
|
+
* @returns Next available slot or null if none found within 30 days
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const nextSlot = findNextAvailable(new Date(), bookings, 60);
|
|
139
|
+
* if (nextSlot) console.log(`Next 1-hour slot at ${nextSlot.start}`);
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export function findNextAvailable(after, bookings, duration, config = {}) {
|
|
143
|
+
const startDate = toDate(after);
|
|
144
|
+
const cfg = { ...DEFAULT_SCHEDULING_CONFIG, ...config, slotDuration: duration };
|
|
145
|
+
// Search up to 30 days ahead
|
|
146
|
+
for (let dayOffset = 0; dayOffset < 30; dayOffset++) {
|
|
147
|
+
const checkDate = new Date(startDate);
|
|
148
|
+
checkDate.setDate(checkDate.getDate() + dayOffset);
|
|
149
|
+
const availableSlots = getAvailableSlots(checkDate, bookings, cfg);
|
|
150
|
+
for (const slot of availableSlots) {
|
|
151
|
+
if (slot.available && slot.start >= startDate) {
|
|
152
|
+
return slot;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Checks if a slot is available (no conflicts with existing bookings)
|
|
160
|
+
* @param slot - The slot to check
|
|
161
|
+
* @param bookings - Existing bookings
|
|
162
|
+
* @returns True if slot is available
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* const slot = { start: new Date('2024-01-15T14:00'), end: new Date('2024-01-15T15:00') };
|
|
167
|
+
* if (isSlotAvailable(slot, existingBookings)) {
|
|
168
|
+
* // Book the slot
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export function isSlotAvailable(slot, bookings) {
|
|
173
|
+
return !bookings.some(booking => dateRangeOverlap(slot, booking));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Finds bookings that conflict with a proposed time range
|
|
177
|
+
* @param bookings - Existing bookings
|
|
178
|
+
* @param proposed - Proposed time range
|
|
179
|
+
* @returns Array of conflicting bookings
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const conflicts = findConflicts(existingBookings, { start: propStart, end: propEnd });
|
|
184
|
+
* if (conflicts.length > 0) {
|
|
185
|
+
* console.log('Conflicts with:', conflicts);
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export function findConflicts(bookings, proposed) {
|
|
190
|
+
return bookings.filter(booking => dateRangeOverlap(booking, proposed));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Checks if a proposed time range has any conflicts
|
|
194
|
+
* @param bookings - Existing bookings
|
|
195
|
+
* @param proposed - Proposed time range
|
|
196
|
+
* @returns True if there are conflicts
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* if (hasConflict(existingBookings, proposedMeeting)) {
|
|
201
|
+
* console.log('Time slot not available');
|
|
202
|
+
* }
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export function hasConflict(bookings, proposed) {
|
|
206
|
+
return bookings.some(booking => dateRangeOverlap(booking, proposed));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Adds buffer time around a slot
|
|
210
|
+
* @param slot - The original slot
|
|
211
|
+
* @param bufferMinutes - Buffer time in minutes
|
|
212
|
+
* @returns New slot with buffer added
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* const slot = { start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') };
|
|
217
|
+
* const buffered = addBuffer(slot, 15);
|
|
218
|
+
* // buffered.start = 09:45, buffered.end = 11:15
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export function addBuffer(slot, bufferMinutes) {
|
|
222
|
+
const bufferMs = bufferMinutes * 60 * 1000;
|
|
223
|
+
return {
|
|
224
|
+
start: new Date(slot.start.getTime() - bufferMs),
|
|
225
|
+
end: new Date(slot.end.getTime() + bufferMs)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Removes buffer time from a slot
|
|
230
|
+
* @param slot - The buffered slot
|
|
231
|
+
* @param bufferMinutes - Buffer time in minutes to remove
|
|
232
|
+
* @returns New slot with buffer removed
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* const bufferedSlot = { start: new Date('2024-01-15T09:45'), end: new Date('2024-01-15T11:15') };
|
|
237
|
+
* const original = removeBuffer(bufferedSlot, 15);
|
|
238
|
+
* // original.start = 10:00, original.end = 11:00
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
export function removeBuffer(slot, bufferMinutes) {
|
|
242
|
+
const bufferMs = bufferMinutes * 60 * 1000;
|
|
243
|
+
return {
|
|
244
|
+
start: new Date(slot.start.getTime() + bufferMs),
|
|
245
|
+
end: new Date(slot.end.getTime() - bufferMs)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Expands recurring availability pattern into concrete slots
|
|
250
|
+
* @param pattern - Recurrence pattern
|
|
251
|
+
* @param range - Date range to expand within
|
|
252
|
+
* @param config - Scheduling configuration
|
|
253
|
+
* @returns Array of slots from the recurring pattern
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* const pattern = {
|
|
258
|
+
* frequency: 'weekly',
|
|
259
|
+
* startDate: new Date('2024-01-01'),
|
|
260
|
+
* byWeekday: [1, 3, 5], // Mon, Wed, Fri
|
|
261
|
+
* until: new Date('2024-12-31')
|
|
262
|
+
* };
|
|
263
|
+
* const slots = expandRecurringAvailability(pattern, range);
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function expandRecurringAvailability(pattern, range, config = {}) {
|
|
267
|
+
const occurrences = getOccurrencesBetween(pattern, range.start, range.end);
|
|
268
|
+
const slots = [];
|
|
269
|
+
for (const occurrence of occurrences) {
|
|
270
|
+
const daySlots = generateSlots(occurrence, config);
|
|
271
|
+
slots.push(...daySlots);
|
|
272
|
+
}
|
|
273
|
+
return slots;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Merges adjacent or overlapping bookings
|
|
277
|
+
* @param bookings - Array of bookings to merge
|
|
278
|
+
* @returns Array of merged bookings
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* const bookings = [
|
|
283
|
+
* { start: new Date('2024-01-15T09:00'), end: new Date('2024-01-15T10:00') },
|
|
284
|
+
* { start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') }
|
|
285
|
+
* ];
|
|
286
|
+
* const merged = mergeBookings(bookings);
|
|
287
|
+
* // [{ start: 09:00, end: 11:00 }]
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export function mergeBookings(bookings) {
|
|
291
|
+
if (bookings.length === 0)
|
|
292
|
+
return [];
|
|
293
|
+
const ranges = mergeDateRanges(bookings);
|
|
294
|
+
return ranges.map(range => ({
|
|
295
|
+
start: range.start,
|
|
296
|
+
end: range.end
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Splits a slot at a specific time
|
|
301
|
+
* @param slot - The slot to split
|
|
302
|
+
* @param at - The time to split at
|
|
303
|
+
* @returns Tuple of two slots, or null if split point is outside slot
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* const slot = { start: new Date('2024-01-15T09:00'), end: new Date('2024-01-15T11:00'), available: true };
|
|
308
|
+
* const [before, after] = splitSlot(slot, new Date('2024-01-15T10:00'));
|
|
309
|
+
* // before: 09:00-10:00, after: 10:00-11:00
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
export function splitSlot(slot, at) {
|
|
313
|
+
const splitTime = toDate(at);
|
|
314
|
+
if (splitTime <= slot.start || splitTime >= slot.end) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
return [
|
|
318
|
+
{
|
|
319
|
+
start: new Date(slot.start),
|
|
320
|
+
end: new Date(splitTime),
|
|
321
|
+
available: slot.available
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
start: new Date(splitTime),
|
|
325
|
+
end: new Date(slot.end),
|
|
326
|
+
available: slot.available
|
|
327
|
+
}
|
|
328
|
+
];
|
|
329
|
+
}
|