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.
Files changed (81) hide show
  1. package/README.md +175 -30
  2. package/dist/{age.js → age.cjs} +14 -6
  3. package/dist/{calculate.js → calculate.cjs} +30 -18
  4. package/dist/{calendar.js → calendar.cjs} +80 -39
  5. package/dist/{calendars.js → calendars.cjs} +48 -23
  6. package/dist/{chain.js → chain.cjs} +41 -40
  7. package/dist/{compare.js → compare.cjs} +58 -28
  8. package/dist/constants.cjs +19 -0
  9. package/dist/{countdown.js → countdown.cjs} +16 -7
  10. package/dist/{cron.js → cron.cjs} +20 -9
  11. package/dist/{dateRange.js → dateRange.cjs} +42 -26
  12. package/dist/{duration.js → duration.cjs} +56 -44
  13. package/dist/esm/chain.js +0 -5
  14. package/dist/esm/finance.d.ts +236 -0
  15. package/dist/esm/finance.d.ts.map +1 -0
  16. package/dist/esm/finance.js +495 -0
  17. package/dist/esm/healthcare.d.ts +260 -0
  18. package/dist/esm/healthcare.d.ts.map +1 -0
  19. package/dist/esm/healthcare.js +447 -0
  20. package/dist/esm/index.d.ts +6 -0
  21. package/dist/esm/index.d.ts.map +1 -1
  22. package/dist/esm/index.js +6 -0
  23. package/dist/esm/naturalLanguage.d.ts +1 -3
  24. package/dist/esm/naturalLanguage.d.ts.map +1 -1
  25. package/dist/esm/naturalLanguage.js +9 -2
  26. package/dist/esm/plugins.d.ts +0 -6
  27. package/dist/esm/plugins.d.ts.map +1 -1
  28. package/dist/esm/plugins.js +36 -42
  29. package/dist/esm/recurrence.d.ts.map +1 -1
  30. package/dist/esm/recurrence.js +3 -5
  31. package/dist/esm/scheduling.d.ts +206 -0
  32. package/dist/esm/scheduling.d.ts.map +1 -0
  33. package/dist/esm/scheduling.js +329 -0
  34. package/dist/esm/timezone.d.ts +6 -1
  35. package/dist/esm/timezone.d.ts.map +1 -1
  36. package/dist/esm/timezone.js +106 -66
  37. package/dist/esm/types.d.ts +0 -4
  38. package/dist/esm/types.d.ts.map +1 -1
  39. package/dist/finance.cjs +512 -0
  40. package/dist/finance.d.ts +236 -0
  41. package/dist/finance.d.ts.map +1 -0
  42. package/dist/{fiscal.js → fiscal.cjs} +36 -17
  43. package/dist/{format.js → format.cjs} +83 -70
  44. package/dist/healthcare.cjs +462 -0
  45. package/dist/healthcare.d.ts +260 -0
  46. package/dist/healthcare.d.ts.map +1 -0
  47. package/dist/{holidays.js → holidays.cjs} +52 -25
  48. package/dist/index.cjs +595 -0
  49. package/dist/index.d.ts +6 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/{interval.js → interval.cjs} +24 -11
  52. package/dist/{iterate.js → iterate.cjs} +84 -41
  53. package/dist/{locale.js → locale.cjs} +54 -26
  54. package/dist/{naturalLanguage.js → naturalLanguage.cjs} +36 -23
  55. package/dist/naturalLanguage.d.ts +1 -3
  56. package/dist/naturalLanguage.d.ts.map +1 -1
  57. package/dist/{parse.js → parse.cjs} +24 -11
  58. package/dist/{performance.js → performance.cjs} +23 -10
  59. package/dist/{plugins.js → plugins.cjs} +48 -47
  60. package/dist/plugins.d.ts +0 -6
  61. package/dist/plugins.d.ts.map +1 -1
  62. package/dist/{precision.js → precision.cjs} +74 -37
  63. package/dist/{rangePresets.js → rangePresets.cjs} +40 -19
  64. package/dist/{recurrence.js → recurrence.cjs} +27 -21
  65. package/dist/recurrence.d.ts.map +1 -1
  66. package/dist/scheduling.cjs +344 -0
  67. package/dist/scheduling.d.ts +206 -0
  68. package/dist/scheduling.d.ts.map +1 -0
  69. package/dist/{serialize.js → serialize.cjs} +36 -17
  70. package/dist/{temporal.js → temporal.cjs} +28 -13
  71. package/dist/{timezone.js → timezone.cjs} +140 -82
  72. package/dist/timezone.d.ts +6 -1
  73. package/dist/timezone.d.ts.map +1 -1
  74. package/dist/{types.js → types.cjs} +9 -3
  75. package/dist/types.d.ts +0 -4
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/{validate.js → validate.cjs} +54 -26
  78. package/dist/{workingHours.js → workingHours.cjs} +36 -17
  79. package/package.json +52 -34
  80. package/dist/constants.js +0 -16
  81. package/dist/index.js +0 -66
@@ -1,8 +1,16 @@
1
+ "use strict";
1
2
  /**
2
3
  * @fileoverview Recurring events and pattern-based date generation utilities
3
4
  * Provides RRULE-inspired recurrence patterns for creating repeating events
4
5
  */
5
- import { addTime } from './calculate.js';
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createRecurrence = createRecurrence;
8
+ exports.getNextOccurrence = getNextOccurrence;
9
+ exports.getOccurrencesBetween = getOccurrencesBetween;
10
+ exports.isRecurrenceDate = isRecurrenceDate;
11
+ exports.isValidRecurrenceRule = isValidRecurrenceRule;
12
+ exports.recurrenceToString = recurrenceToString;
13
+ const calculate_js_1 = require("./calculate.cjs");
6
14
  /**
7
15
  * Creates a recurrence pattern generator
8
16
  * @param rule - The recurrence rule defining the pattern
@@ -34,7 +42,7 @@ import { addTime } from './calculate.js';
34
42
  * });
35
43
  * ```
36
44
  */
37
- export function createRecurrence(rule) {
45
+ function createRecurrence(rule) {
38
46
  const startDate = new Date(rule.startDate);
39
47
  return {
40
48
  rule,
@@ -43,19 +51,17 @@ export function createRecurrence(rule) {
43
51
  isRecurrenceDate: (date) => isRecurrenceDate(date, rule),
44
52
  getAllOccurrences: (limit = 100) => {
45
53
  const occurrences = [];
46
- let current = new Date(startDate);
47
- let count = 0;
48
- while (count < limit) {
54
+ let current = new Date(startDate.getTime() - 1);
55
+ while (occurrences.length < limit) {
49
56
  if (rule.until && current > new Date(rule.until))
50
57
  break;
51
- if (rule.count && count >= rule.count)
58
+ if (rule.count && occurrences.length >= rule.count)
52
59
  break;
53
60
  const next = getNextOccurrence(rule, current);
54
61
  if (!next)
55
62
  break;
56
63
  occurrences.push(next);
57
64
  current = new Date(next.getTime() + 1);
58
- count++;
59
65
  }
60
66
  return occurrences;
61
67
  }
@@ -79,7 +85,7 @@ export function createRecurrence(rule) {
79
85
  * // Returns Date('2024-01-07') - every other day
80
86
  * ```
81
87
  */
82
- export function getNextOccurrence(rule, afterDate) {
88
+ function getNextOccurrence(rule, afterDate) {
83
89
  const after = afterDate ? new Date(afterDate) : new Date();
84
90
  const start = new Date(rule.startDate);
85
91
  // If afterDate is before start, check if start matches
@@ -94,7 +100,7 @@ export function getNextOccurrence(rule, afterDate) {
94
100
  }
95
101
  // Start searching from the day after 'after'
96
102
  // We want strictly greater than after, so start from the next potential occurrence
97
- let candidate = addTime(after, 1, 'day');
103
+ let candidate = (0, calculate_js_1.addTime)(after, 1, 'day');
98
104
  // Preserve time from start date
99
105
  candidate.setHours(start.getHours(), start.getMinutes(), start.getSeconds(), start.getMilliseconds());
100
106
  const maxIterations = 1000; // Prevent infinite loops
@@ -137,7 +143,7 @@ export function getNextOccurrence(rule, afterDate) {
137
143
  * // Returns all Mondays and Fridays in January 2024
138
144
  * ```
139
145
  */
140
- export function getOccurrencesBetween(rule, start, end, limit = 1000) {
146
+ function getOccurrencesBetween(rule, start, end, limit = 1000) {
141
147
  const startDate = new Date(start);
142
148
  const endDate = new Date(end);
143
149
  const occurrences = [];
@@ -179,7 +185,7 @@ export function getOccurrencesBetween(rule, start, end, limit = 1000) {
179
185
  * isRecurrenceDate(new Date('2024-01-09'), rule); // false (Tuesday)
180
186
  * ```
181
187
  */
182
- export function isRecurrenceDate(date, rule) {
188
+ function isRecurrenceDate(date, rule) {
183
189
  const checkDate = new Date(date);
184
190
  const start = new Date(rule.startDate);
185
191
  // Must be on or after start date
@@ -210,7 +216,7 @@ export function isRecurrenceDate(date, rule) {
210
216
  * }); // false
211
217
  * ```
212
218
  */
213
- export function isValidRecurrenceRule(rule) {
219
+ function isValidRecurrenceRule(rule) {
214
220
  if (!rule.frequency || !rule.startDate)
215
221
  return false;
216
222
  const validFrequencies = ['daily', 'weekly', 'monthly', 'yearly'];
@@ -264,7 +270,7 @@ export function isValidRecurrenceRule(rule) {
264
270
  * // "Every 2 weeks on Monday, Wednesday, Friday"
265
271
  * ```
266
272
  */
267
- export function recurrenceToString(rule) {
273
+ function recurrenceToString(rule) {
268
274
  const interval = rule.interval || 1;
269
275
  let result = interval === 1 ? 'Every' : `Every ${interval}`;
270
276
  switch (rule.frequency) {
@@ -378,27 +384,27 @@ function getNextCandidate(date, rule) {
378
384
  case 'daily':
379
385
  // For daily recurrence, always increment by 1 day to check each day
380
386
  // matchesRecurrenceRule will filter based on interval
381
- return addTime(date, 1, 'day');
387
+ return (0, calculate_js_1.addTime)(date, 1, 'day');
382
388
  case 'weekly':
383
389
  // If we have specific weekdays, try next day, otherwise skip full interval
384
390
  if (rule.byWeekday && rule.byWeekday.length > 0) {
385
- return addTime(date, 1, 'day');
391
+ return (0, calculate_js_1.addTime)(date, 1, 'day');
386
392
  }
387
- return addTime(date, interval * 7, 'day');
393
+ return (0, calculate_js_1.addTime)(date, interval * 7, 'day');
388
394
  case 'monthly':
389
395
  // If we have specific days of month, try next day, otherwise skip full interval
390
396
  if (rule.byMonthDay && rule.byMonthDay.length > 0) {
391
- return addTime(date, 1, 'day');
397
+ return (0, calculate_js_1.addTime)(date, 1, 'day');
392
398
  }
393
- return addTime(date, interval, 'month');
399
+ return (0, calculate_js_1.addTime)(date, interval, 'month');
394
400
  case 'yearly':
395
401
  // If we have specific months or days, try next day, otherwise skip full interval
396
402
  if ((rule.byMonth && rule.byMonth.length > 0) ||
397
403
  (rule.byMonthDay && rule.byMonthDay.length > 0)) {
398
- return addTime(date, 1, 'day');
404
+ return (0, calculate_js_1.addTime)(date, 1, 'day');
399
405
  }
400
- return addTime(date, interval, 'year');
406
+ return (0, calculate_js_1.addTime)(date, interval, 'year');
401
407
  default:
402
- return addTime(date, 1, 'day');
408
+ return (0, calculate_js_1.addTime)(date, 1, 'day');
403
409
  }
404
410
  }
@@ -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;;EAqBrC;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"}
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"}
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Scheduling and booking utilities
4
+ * Provides slot generation, availability checking, and conflict detection
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.DEFAULT_SCHEDULING_CONFIG = void 0;
8
+ exports.generateSlots = generateSlots;
9
+ exports.generateSlotsForRange = generateSlotsForRange;
10
+ exports.getAvailableSlots = getAvailableSlots;
11
+ exports.findNextAvailable = findNextAvailable;
12
+ exports.isSlotAvailable = isSlotAvailable;
13
+ exports.findConflicts = findConflicts;
14
+ exports.hasConflict = hasConflict;
15
+ exports.addBuffer = addBuffer;
16
+ exports.removeBuffer = removeBuffer;
17
+ exports.expandRecurringAvailability = expandRecurringAvailability;
18
+ exports.mergeBookings = mergeBookings;
19
+ exports.splitSlot = splitSlot;
20
+ const dateRange_js_1 = require("./dateRange.cjs");
21
+ const workingHours_js_1 = require("./workingHours.cjs");
22
+ const recurrence_js_1 = require("./recurrence.cjs");
23
+ /** Default scheduling configuration */
24
+ exports.DEFAULT_SCHEDULING_CONFIG = {
25
+ workingHours: workingHours_js_1.DEFAULT_WORKING_HOURS,
26
+ bufferMinutes: 0,
27
+ slotDuration: 30,
28
+ holidays: []
29
+ };
30
+ /**
31
+ * Helper to convert DateInput to Date
32
+ */
33
+ function toDate(input) {
34
+ if (input instanceof Date)
35
+ return new Date(input);
36
+ return new Date(input);
37
+ }
38
+ /**
39
+ * Check if a date is a holiday
40
+ */
41
+ function isHoliday(date, holidays) {
42
+ const dateStr = date.toISOString().split('T')[0];
43
+ return holidays.some(h => h.toISOString().split('T')[0] === dateStr);
44
+ }
45
+ /**
46
+ * Generates time slots for a single day
47
+ * @param date - The date to generate slots for
48
+ * @param config - Scheduling configuration
49
+ * @returns Array of slots for the day
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const slots = generateSlots(new Date('2024-01-15'), { slotDuration: 30 });
54
+ * // Returns 30-minute slots during working hours
55
+ * ```
56
+ */
57
+ function generateSlots(date, config = {}) {
58
+ const d = toDate(date);
59
+ const cfg = { ...exports.DEFAULT_SCHEDULING_CONFIG, ...config };
60
+ const workingHours = cfg.workingHours ?? workingHours_js_1.DEFAULT_WORKING_HOURS;
61
+ // Check if it's a working day and not a holiday
62
+ if (!(0, workingHours_js_1.isWorkingDay)(d, workingHours))
63
+ return [];
64
+ if (cfg.holidays && isHoliday(d, cfg.holidays))
65
+ return [];
66
+ const slots = [];
67
+ const slotDuration = cfg.slotDuration ?? 30;
68
+ const dayStart = (0, workingHours_js_1.getWorkDayStart)(d, workingHours);
69
+ const dayEnd = (0, workingHours_js_1.getWorkDayEnd)(d, workingHours);
70
+ let current = new Date(dayStart);
71
+ while (current < dayEnd) {
72
+ const slotEnd = new Date(current.getTime() + slotDuration * 60 * 1000);
73
+ // Don't create slots that extend past working hours
74
+ if (slotEnd <= dayEnd) {
75
+ // Check if slot is during working time (not during breaks)
76
+ const midpoint = new Date(current.getTime() + (slotDuration * 60 * 1000) / 2);
77
+ const available = (0, workingHours_js_1.isWorkingTime)(midpoint, workingHours);
78
+ slots.push({
79
+ start: new Date(current),
80
+ end: new Date(slotEnd),
81
+ available
82
+ });
83
+ }
84
+ current = slotEnd;
85
+ }
86
+ return slots;
87
+ }
88
+ /**
89
+ * Generates time slots for a date range
90
+ * @param range - The date range to generate slots for
91
+ * @param config - Scheduling configuration
92
+ * @returns Array of slots for all days in range
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * const range = { start: new Date('2024-01-15'), end: new Date('2024-01-17') };
97
+ * const slots = generateSlotsForRange(range, { slotDuration: 60 });
98
+ * ```
99
+ */
100
+ function generateSlotsForRange(range, config = {}) {
101
+ const slots = [];
102
+ const current = new Date(range.start);
103
+ current.setHours(0, 0, 0, 0);
104
+ const endDate = new Date(range.end);
105
+ endDate.setHours(23, 59, 59, 999);
106
+ while (current <= endDate) {
107
+ const daySlots = generateSlots(current, config);
108
+ slots.push(...daySlots);
109
+ current.setDate(current.getDate() + 1);
110
+ }
111
+ return slots;
112
+ }
113
+ /**
114
+ * Gets available slots for a day, excluding existing bookings
115
+ * @param date - The date to check
116
+ * @param bookings - Existing bookings
117
+ * @param config - Scheduling configuration
118
+ * @returns Array of available slots
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const bookings = [{ start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') }];
123
+ * const available = getAvailableSlots(new Date('2024-01-15'), bookings);
124
+ * ```
125
+ */
126
+ function getAvailableSlots(date, bookings, config = {}) {
127
+ const cfg = { ...exports.DEFAULT_SCHEDULING_CONFIG, ...config };
128
+ const slots = generateSlots(date, cfg);
129
+ const bufferMs = (cfg.bufferMinutes ?? 0) * 60 * 1000;
130
+ return slots.map(slot => {
131
+ // Expand slot by buffer for conflict checking
132
+ const checkRange = {
133
+ start: new Date(slot.start.getTime() - bufferMs),
134
+ end: new Date(slot.end.getTime() + bufferMs)
135
+ };
136
+ const hasConflict = bookings.some(booking => (0, dateRange_js_1.dateRangeOverlap)(checkRange, booking));
137
+ return {
138
+ ...slot,
139
+ available: slot.available && !hasConflict
140
+ };
141
+ });
142
+ }
143
+ /**
144
+ * Finds the next available slot of specified duration
145
+ * @param after - Start searching after this date
146
+ * @param bookings - Existing bookings
147
+ * @param duration - Required slot duration in minutes
148
+ * @param config - Scheduling configuration
149
+ * @returns Next available slot or null if none found within 30 days
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * const nextSlot = findNextAvailable(new Date(), bookings, 60);
154
+ * if (nextSlot) console.log(`Next 1-hour slot at ${nextSlot.start}`);
155
+ * ```
156
+ */
157
+ function findNextAvailable(after, bookings, duration, config = {}) {
158
+ const startDate = toDate(after);
159
+ const cfg = { ...exports.DEFAULT_SCHEDULING_CONFIG, ...config, slotDuration: duration };
160
+ // Search up to 30 days ahead
161
+ for (let dayOffset = 0; dayOffset < 30; dayOffset++) {
162
+ const checkDate = new Date(startDate);
163
+ checkDate.setDate(checkDate.getDate() + dayOffset);
164
+ const availableSlots = getAvailableSlots(checkDate, bookings, cfg);
165
+ for (const slot of availableSlots) {
166
+ if (slot.available && slot.start >= startDate) {
167
+ return slot;
168
+ }
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ /**
174
+ * Checks if a slot is available (no conflicts with existing bookings)
175
+ * @param slot - The slot to check
176
+ * @param bookings - Existing bookings
177
+ * @returns True if slot is available
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const slot = { start: new Date('2024-01-15T14:00'), end: new Date('2024-01-15T15:00') };
182
+ * if (isSlotAvailable(slot, existingBookings)) {
183
+ * // Book the slot
184
+ * }
185
+ * ```
186
+ */
187
+ function isSlotAvailable(slot, bookings) {
188
+ return !bookings.some(booking => (0, dateRange_js_1.dateRangeOverlap)(slot, booking));
189
+ }
190
+ /**
191
+ * Finds bookings that conflict with a proposed time range
192
+ * @param bookings - Existing bookings
193
+ * @param proposed - Proposed time range
194
+ * @returns Array of conflicting bookings
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * const conflicts = findConflicts(existingBookings, { start: propStart, end: propEnd });
199
+ * if (conflicts.length > 0) {
200
+ * console.log('Conflicts with:', conflicts);
201
+ * }
202
+ * ```
203
+ */
204
+ function findConflicts(bookings, proposed) {
205
+ return bookings.filter(booking => (0, dateRange_js_1.dateRangeOverlap)(booking, proposed));
206
+ }
207
+ /**
208
+ * Checks if a proposed time range has any conflicts
209
+ * @param bookings - Existing bookings
210
+ * @param proposed - Proposed time range
211
+ * @returns True if there are conflicts
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * if (hasConflict(existingBookings, proposedMeeting)) {
216
+ * console.log('Time slot not available');
217
+ * }
218
+ * ```
219
+ */
220
+ function hasConflict(bookings, proposed) {
221
+ return bookings.some(booking => (0, dateRange_js_1.dateRangeOverlap)(booking, proposed));
222
+ }
223
+ /**
224
+ * Adds buffer time around a slot
225
+ * @param slot - The original slot
226
+ * @param bufferMinutes - Buffer time in minutes
227
+ * @returns New slot with buffer added
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const slot = { start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') };
232
+ * const buffered = addBuffer(slot, 15);
233
+ * // buffered.start = 09:45, buffered.end = 11:15
234
+ * ```
235
+ */
236
+ function addBuffer(slot, bufferMinutes) {
237
+ const bufferMs = bufferMinutes * 60 * 1000;
238
+ return {
239
+ start: new Date(slot.start.getTime() - bufferMs),
240
+ end: new Date(slot.end.getTime() + bufferMs)
241
+ };
242
+ }
243
+ /**
244
+ * Removes buffer time from a slot
245
+ * @param slot - The buffered slot
246
+ * @param bufferMinutes - Buffer time in minutes to remove
247
+ * @returns New slot with buffer removed
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * const bufferedSlot = { start: new Date('2024-01-15T09:45'), end: new Date('2024-01-15T11:15') };
252
+ * const original = removeBuffer(bufferedSlot, 15);
253
+ * // original.start = 10:00, original.end = 11:00
254
+ * ```
255
+ */
256
+ function removeBuffer(slot, bufferMinutes) {
257
+ const bufferMs = bufferMinutes * 60 * 1000;
258
+ return {
259
+ start: new Date(slot.start.getTime() + bufferMs),
260
+ end: new Date(slot.end.getTime() - bufferMs)
261
+ };
262
+ }
263
+ /**
264
+ * Expands recurring availability pattern into concrete slots
265
+ * @param pattern - Recurrence pattern
266
+ * @param range - Date range to expand within
267
+ * @param config - Scheduling configuration
268
+ * @returns Array of slots from the recurring pattern
269
+ *
270
+ * @example
271
+ * ```ts
272
+ * const pattern = {
273
+ * frequency: 'weekly',
274
+ * startDate: new Date('2024-01-01'),
275
+ * byWeekday: [1, 3, 5], // Mon, Wed, Fri
276
+ * until: new Date('2024-12-31')
277
+ * };
278
+ * const slots = expandRecurringAvailability(pattern, range);
279
+ * ```
280
+ */
281
+ function expandRecurringAvailability(pattern, range, config = {}) {
282
+ const occurrences = (0, recurrence_js_1.getOccurrencesBetween)(pattern, range.start, range.end);
283
+ const slots = [];
284
+ for (const occurrence of occurrences) {
285
+ const daySlots = generateSlots(occurrence, config);
286
+ slots.push(...daySlots);
287
+ }
288
+ return slots;
289
+ }
290
+ /**
291
+ * Merges adjacent or overlapping bookings
292
+ * @param bookings - Array of bookings to merge
293
+ * @returns Array of merged bookings
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * const bookings = [
298
+ * { start: new Date('2024-01-15T09:00'), end: new Date('2024-01-15T10:00') },
299
+ * { start: new Date('2024-01-15T10:00'), end: new Date('2024-01-15T11:00') }
300
+ * ];
301
+ * const merged = mergeBookings(bookings);
302
+ * // [{ start: 09:00, end: 11:00 }]
303
+ * ```
304
+ */
305
+ function mergeBookings(bookings) {
306
+ if (bookings.length === 0)
307
+ return [];
308
+ const ranges = (0, dateRange_js_1.mergeDateRanges)(bookings);
309
+ return ranges.map(range => ({
310
+ start: range.start,
311
+ end: range.end
312
+ }));
313
+ }
314
+ /**
315
+ * Splits a slot at a specific time
316
+ * @param slot - The slot to split
317
+ * @param at - The time to split at
318
+ * @returns Tuple of two slots, or null if split point is outside slot
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * const slot = { start: new Date('2024-01-15T09:00'), end: new Date('2024-01-15T11:00'), available: true };
323
+ * const [before, after] = splitSlot(slot, new Date('2024-01-15T10:00'));
324
+ * // before: 09:00-10:00, after: 10:00-11:00
325
+ * ```
326
+ */
327
+ function splitSlot(slot, at) {
328
+ const splitTime = toDate(at);
329
+ if (splitTime <= slot.start || splitTime >= slot.end) {
330
+ return null;
331
+ }
332
+ return [
333
+ {
334
+ start: new Date(slot.start),
335
+ end: new Date(splitTime),
336
+ available: slot.available
337
+ },
338
+ {
339
+ start: new Date(splitTime),
340
+ end: new Date(slot.end),
341
+ available: slot.available
342
+ }
343
+ ];
344
+ }
@@ -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