scschedule 2.0.2 → 2.1.1

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 (62) hide show
  1. package/README.md +494 -10
  2. package/dist/cleanupExpiredOverridesFromSchedule.d.ts +7 -0
  3. package/dist/cleanupExpiredOverridesFromSchedule.js +29 -0
  4. package/dist/constants.d.ts +69 -0
  5. package/dist/constants.js +71 -0
  6. package/dist/getAvailableRangesFromSchedule.d.ts +7 -0
  7. package/dist/getAvailableRangesFromSchedule.js +38 -0
  8. package/dist/getNextAvailableFromSchedule.d.ts +55 -0
  9. package/dist/getNextAvailableFromSchedule.js +92 -0
  10. package/dist/getNextUnavailableFromSchedule.d.ts +44 -0
  11. package/dist/getNextUnavailableFromSchedule.js +112 -0
  12. package/dist/index.d.ts +9 -1
  13. package/dist/index.js +11 -2
  14. package/dist/internal/doOverridesOverlap.d.ts +5 -0
  15. package/dist/internal/doOverridesOverlap.js +14 -0
  16. package/dist/internal/doRulesOverlap.d.ts +15 -0
  17. package/dist/internal/doRulesOverlap.js +35 -0
  18. package/dist/internal/doTimeRangesOverlap.d.ts +16 -0
  19. package/dist/internal/doTimeRangesOverlap.js +49 -0
  20. package/dist/internal/getApplicableRuleForDate.d.ts +24 -0
  21. package/dist/internal/getApplicableRuleForDate.js +82 -0
  22. package/dist/internal/getEffectiveTimesForWeekday.d.ts +12 -0
  23. package/dist/internal/getEffectiveTimesForWeekday.js +42 -0
  24. package/dist/internal/index.d.ts +19 -0
  25. package/dist/internal/index.js +21 -0
  26. package/dist/internal/isTimeInTimeRange.d.ts +7 -0
  27. package/dist/internal/isTimeInTimeRange.js +19 -0
  28. package/dist/internal/isValidTimezone.d.ts +4 -0
  29. package/dist/internal/isValidTimezone.js +12 -0
  30. package/dist/internal/normalizeScheduleForValidation.d.ts +13 -0
  31. package/dist/internal/normalizeScheduleForValidation.js +36 -0
  32. package/dist/internal/splitCrossMidnightTimeRange.d.ts +5 -0
  33. package/dist/internal/splitCrossMidnightTimeRange.js +22 -0
  34. package/dist/internal/validateNoEmptyWeekdays.d.ts +7 -0
  35. package/dist/internal/validateNoEmptyWeekdays.js +50 -0
  36. package/dist/internal/validateNoOverlappingOverrides.d.ts +5 -0
  37. package/dist/internal/validateNoOverlappingOverrides.js +57 -0
  38. package/dist/internal/validateNoOverlappingRules.d.ts +13 -0
  39. package/dist/internal/validateNoOverlappingRules.js +56 -0
  40. package/dist/internal/validateNoOverlappingTimesInRule.d.ts +5 -0
  41. package/dist/internal/validateNoOverlappingTimesInRule.js +54 -0
  42. package/dist/internal/validateNoSpilloverConflictsAtOverrideBoundaries.d.ts +11 -0
  43. package/dist/internal/validateNoSpilloverConflictsAtOverrideBoundaries.js +149 -0
  44. package/dist/internal/validateNonEmptyTimes.d.ts +5 -0
  45. package/dist/internal/validateNonEmptyTimes.js +35 -0
  46. package/dist/internal/validateOverrideDateOrder.d.ts +6 -0
  47. package/dist/internal/validateOverrideDateOrder.js +27 -0
  48. package/dist/internal/validateOverrideWeekdaysMatchDates.d.ts +10 -0
  49. package/dist/internal/validateOverrideWeekdaysMatchDates.js +63 -0
  50. package/dist/internal/validateScDateFormats.d.ts +5 -0
  51. package/dist/internal/validateScDateFormats.js +109 -0
  52. package/dist/internal/validateTimezone.d.ts +5 -0
  53. package/dist/internal/validateTimezone.js +16 -0
  54. package/dist/isScheduleAvailable.d.ts +6 -0
  55. package/dist/isScheduleAvailable.js +37 -0
  56. package/dist/types.d.ts +268 -0
  57. package/dist/types.js +1 -0
  58. package/dist/validateSchedule.d.ts +15 -0
  59. package/dist/validateSchedule.js +57 -0
  60. package/package.json +7 -7
  61. package/dist/index.d.ts.map +0 -1
  62. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -1,24 +1,33 @@
1
1
  # scschedule
2
2
 
3
- **Schedule management utilities**
3
+ **Schedule management library for time-based availability patterns**
4
4
 
5
5
  [![github license](https://img.shields.io/github/license/ericvera/scdate.svg?style=flat-square)](https://github.com/ericvera/scdate/blob/master/LICENSE)
6
6
  [![npm version](https://img.shields.io/npm/v/scschedule.svg?style=flat-square)](https://npmjs.org/package/scschedule)
7
7
 
8
+ ## Overview
9
+
10
+ scschedule is a TypeScript library for managing time-based availability patterns. Built on top of [scdate](https://npmjs.org/package/scdate) for consistent date/time handling, it provides a powerful yet simple way to define and query when entities are available or unavailable using recurring patterns and date-specific overrides.
11
+
12
+ **Use cases**: Business hours, resource availability, service windows, time-restricted access control, scheduling systems, and any application that needs to determine availability based on time patterns.
13
+
8
14
  ## Features
9
15
 
10
- - 🔧 Built on top of scdate for robust date/time handling
11
- - 📅 Schedule management utilities
12
- - Full TypeScript support
13
- - 📦 Zero runtime overhead
14
- - 🔒 Immutable data structures
16
+ - **Recurring patterns**: Define weekly schedules with different hours for different days
17
+ - **Override system**: Add exceptions for holidays, special events, or schedule changes
18
+ - **Cross-midnight support**: Handle time ranges that span midnight (e.g., 22:00-02:00)
19
+ - **Timezone aware**: All operations use schedule's timezone for calculations
20
+ - **DST handling**: Properly handles daylight saving time transitions
21
+ - **Immutable**: All operations return new instances
22
+ - **Type-safe validation**: Discriminated union errors with detailed information
23
+ - **Tree-shakeable**: Each function in its own file for optimal bundling
15
24
 
16
25
  ## Installation
17
26
 
18
27
  ```bash
19
- npm install scschedule
28
+ npm install scschedule scdate
20
29
  # or
21
- yarn add scschedule
30
+ yarn add scschedule scdate
22
31
  ```
23
32
 
24
33
  ## Requirements
@@ -26,9 +35,478 @@ yarn add scschedule
26
35
  - Node.js >= 22
27
36
  - TypeScript >= 5.0 (for TypeScript users)
28
37
 
29
- ## Documentation
38
+ ## Quick Start
39
+
40
+ ```typescript
41
+ import {
42
+ Schedule,
43
+ isScheduleAvailable,
44
+ getNextAvailableFromSchedule,
45
+ validateSchedule,
46
+ } from 'scschedule'
47
+ import { sDate, sTime, sWeekdays, getTimestampNow } from 'scdate'
48
+
49
+ // Define a restaurant schedule: Tuesday-Saturday, 11:00-22:00
50
+ const restaurant: Schedule = {
51
+ timezone: 'America/Puerto_Rico',
52
+ weekly: [
53
+ {
54
+ weekdays: sWeekdays('-MTWTFS'), // Tue-Sat
55
+ times: [{ from: sTime('11:00'), to: sTime('22:00') }],
56
+ },
57
+ ],
58
+ }
59
+
60
+ // Validate the schedule
61
+ const validation = validateSchedule(restaurant)
62
+ if (!validation.valid) {
63
+ console.error('Invalid schedule:', validation.errors)
64
+ }
65
+
66
+ // Check if open now
67
+ const now = getTimestampNow('America/Puerto_Rico')
68
+ const isOpen = isScheduleAvailable(restaurant, now)
69
+ console.log(`Restaurant is ${isOpen ? 'open' : 'closed'}`)
70
+
71
+ // Find next opening time
72
+ const nextOpen = getNextAvailableFromSchedule(restaurant, now)
73
+ if (nextOpen) {
74
+ console.log(`Next opening: ${nextOpen.timestamp}`)
75
+ }
76
+ ```
77
+
78
+ ## Core Concepts
79
+
80
+ ### Schedule Structure
81
+
82
+ A `Schedule` consists of:
83
+
84
+ - **timezone**: The timezone for all time calculations
85
+ - **weekly**: Base recurring schedule (array of `WeeklyScheduleRule`)
86
+ - **overrides** (optional): Date-specific exceptions (array of `OverrideScheduleRule`)
87
+
88
+ ```typescript
89
+ interface Schedule {
90
+ timezone: string
91
+ weekly: WeeklyScheduleRule[]
92
+ overrides?: OverrideScheduleRule[]
93
+ }
94
+ ```
95
+
96
+ ### Weekly Rules
97
+
98
+ Define recurring availability patterns for specific days of the week:
99
+
100
+ ```typescript
101
+ interface WeeklyScheduleRule {
102
+ weekdays: SWeekdays // e.g., 'SMTWTFS' or '-MTWTF-'
103
+ times: TimeRange[] // Array of time ranges
104
+ }
105
+
106
+ interface TimeRange {
107
+ from: STime // e.g., '09:00'
108
+ to: STime // e.g., '17:00'
109
+ }
110
+ ```
111
+
112
+ ### Override Rules
113
+
114
+ Override the weekly schedule for specific date ranges:
115
+
116
+ ```typescript
117
+ interface OverrideScheduleRule {
118
+ from: SDate // Start date (required)
119
+ to?: SDate // End date (optional - if omitted, applies indefinitely)
120
+ rules: WeeklyScheduleRule[] // Empty array = unavailable/closed
121
+ }
122
+ ```
123
+
124
+ **Override semantics**:
125
+
126
+ - **Closed for a day**: `{ from: '2025-12-25', rules: [] }` (Christmas Day)
127
+ - **Closed indefinitely**: `{ from: '2025-12-25', rules: [] }` (no `to` date)
128
+ - **Special hours**: Override with different weekly patterns for the date range
129
+
130
+ ### Rule Priority
131
+
132
+ Rules are evaluated in order of priority:
133
+
134
+ 1. **Specific override** (with both `from` and `to`) - highest priority, shortest duration wins when multiple could apply
135
+ 2. **Indefinite override** (with only `from`, no `to`) - when multiple indefinite overrides could apply, the one with the latest `from` date wins (most recent policy)
136
+ 3. **Weekly rules** - lowest priority (base schedule)
137
+
138
+ ## API Reference
139
+
140
+ ### Validation
141
+
142
+ #### `validateSchedule(schedule: Schedule): ValidationResult`
143
+
144
+ Validates a schedule and returns detailed errors.
145
+
146
+ ```typescript
147
+ import { validateSchedule, ValidationIssue } from 'scschedule'
148
+
149
+ const result = validateSchedule(mySchedule)
150
+
151
+ if (!result.valid) {
152
+ result.errors.forEach((error) => {
153
+ switch (error.issue) {
154
+ case ValidationIssue.InvalidTimezone:
155
+ console.error(`Invalid timezone: ${error.timezone}`)
156
+ break
157
+ case ValidationIssue.OverlappingSpecificOverrides:
158
+ console.error(
159
+ `Overlapping overrides at indexes: ${error.overrideIndexes}`,
160
+ )
161
+ break
162
+ // ... handle other error types
163
+ }
164
+ })
165
+ }
166
+ ```
167
+
168
+ **Validation checks**:
169
+
170
+ - Valid timezone (in `Intl.supportedValuesOf('timeZone')`)
171
+ - No duplicate overrides (identical from/to dates)
172
+ - No overlapping specific overrides (hierarchical nesting allowed)
173
+ - No overlapping time ranges within rules (same weekday)
174
+ - All rules have at least one time range
175
+ - Valid scdate formats (SDate, STime, SWeekdays)
176
+ - No empty weekdays patterns (e.g., '-------' with no days selected)
177
+ - Override weekdays must match at least one date in the override's date range
178
+
179
+ ### Schedule Management
180
+
181
+ #### `cleanupExpiredOverridesFromSchedule(schedule: Schedule, beforeDate: SDate | string): Schedule`
182
+
183
+ Removes expired overrides (with a `to` date) that ended before a given date. Indefinite overrides (no `to` date) are never removed. Returns a new Schedule instance.
184
+
185
+ ```typescript
186
+ import { cleanupExpiredOverridesFromSchedule } from 'scschedule'
187
+ import { sDate } from 'scdate'
188
+
189
+ const cleaned = cleanupExpiredOverridesFromSchedule(
190
+ mySchedule,
191
+ sDate('2025-01-01'),
192
+ )
193
+ ```
194
+
195
+ ### Availability Queries
196
+
197
+ #### `isScheduleAvailable(schedule: Schedule, timestamp: STimestamp | string): boolean`
198
+
199
+ Check if schedule is available at a specific timestamp.
200
+
201
+ ```typescript
202
+ import { isScheduleAvailable } from 'scschedule'
203
+ import { getTimestampNow } from 'scdate'
204
+
205
+ const isOpen = isScheduleAvailable(
206
+ restaurant,
207
+ getTimestampNow('America/Puerto_Rico'),
208
+ )
209
+ ```
210
+
211
+ #### `getNextAvailableFromSchedule(schedule: Schedule, fromTimestamp: STimestamp | string, maxDaysToSearch?: number): STimestamp | undefined`
212
+
213
+ Find the next available timestamp from a given time. Searches up to `maxDaysToSearch` days (default: 365).
214
+
215
+ ```typescript
216
+ import { getNextAvailableFromSchedule } from 'scschedule'
217
+
218
+ const nextOpen = getNextAvailableFromSchedule(restaurant, now)
219
+ if (nextOpen) {
220
+ console.log(`Opens at: ${nextOpen.timestamp}`)
221
+ }
222
+ ```
223
+
224
+ #### `getNextUnavailableFromSchedule(schedule: Schedule, fromTimestamp: STimestamp | string): STimestamp | undefined`
225
+
226
+ Find the next unavailable timestamp from a given time.
30
227
 
31
- Coming soon...
228
+ ```typescript
229
+ import { getNextUnavailableFromSchedule } from 'scschedule'
230
+
231
+ const nextClosed = getNextUnavailableFromSchedule(restaurant, now)
232
+ if (nextClosed) {
233
+ console.log(`Closes at: ${nextClosed.timestamp}`)
234
+ }
235
+ ```
236
+
237
+ #### `getAvailableRangesFromSchedule(schedule: Schedule, startDate: SDate | string, endDate: SDate | string): AvailabilityRange[]`
238
+
239
+ Get all available time ranges within a date range.
240
+
241
+ ```typescript
242
+ import { getAvailableRangesFromSchedule } from 'scschedule'
243
+ import { sDate } from 'scdate'
244
+
245
+ const ranges = getAvailableRangesFromSchedule(
246
+ restaurant,
247
+ sDate('2025-11-11'),
248
+ sDate('2025-11-17'),
249
+ )
250
+
251
+ ranges.forEach((range) => {
252
+ console.log(`Available: ${range.from.timestamp} to ${range.to.timestamp}`)
253
+ })
254
+ ```
255
+
256
+ ## Usage Examples
257
+
258
+ ### Basic Business Hours
259
+
260
+ ```typescript
261
+ import { Schedule } from 'scschedule'
262
+ import { sTime, sWeekdays } from 'scdate'
263
+
264
+ const restaurant: Schedule = {
265
+ timezone: 'America/Puerto_Rico',
266
+ weekly: [
267
+ {
268
+ weekdays: sWeekdays('-MTWTFS'), // Tue-Sat
269
+ times: [{ from: sTime('11:00'), to: sTime('22:00') }],
270
+ },
271
+ ],
272
+ }
273
+ ```
274
+
275
+ ### Split Schedules (Lunch & Dinner)
276
+
277
+ ```typescript
278
+ const restaurant: Schedule = {
279
+ timezone: 'America/Puerto_Rico',
280
+ weekly: [
281
+ {
282
+ weekdays: sWeekdays('-MTWTFS'), // Tue-Sat
283
+ times: [
284
+ { from: sTime('11:00'), to: sTime('14:00') }, // Lunch
285
+ { from: sTime('17:00'), to: sTime('22:00') }, // Dinner
286
+ ],
287
+ },
288
+ ],
289
+ }
290
+ ```
291
+
292
+ ### Different Hours for Different Days
293
+
294
+ ```typescript
295
+ const restaurant: Schedule = {
296
+ timezone: 'America/Puerto_Rico',
297
+ weekly: [
298
+ {
299
+ // Weekdays: longer hours
300
+ weekdays: sWeekdays('-MTWTF-'), // Mon-Fri
301
+ times: [{ from: sTime('10:00'), to: sTime('23:00') }],
302
+ },
303
+ {
304
+ // Weekends: shorter hours
305
+ weekdays: sWeekdays('S-----S'), // Sat-Sun
306
+ times: [{ from: sTime('12:00'), to: sTime('20:00') }],
307
+ },
308
+ ],
309
+ }
310
+ ```
311
+
312
+ ### Holiday Closure
313
+
314
+ ```typescript
315
+ import { sDate } from 'scdate'
316
+
317
+ const withHoliday: Schedule = {
318
+ ...restaurant,
319
+ overrides: [
320
+ {
321
+ from: sDate('2025-12-25'), // Christmas Day
322
+ rules: [], // Empty array = closed
323
+ },
324
+ ],
325
+ }
326
+ ```
327
+
328
+ ### Extended Holiday Hours
329
+
330
+ ```typescript
331
+ const withExtendedHours: Schedule = {
332
+ ...restaurant,
333
+ overrides: [
334
+ {
335
+ from: sDate('2025-12-01'),
336
+ to: sDate('2025-12-31'),
337
+ rules: [
338
+ {
339
+ // Extended hours for December, weekends only
340
+ weekdays: sWeekdays('S-----S'),
341
+ times: [{ from: sTime('08:00'), to: sTime('23:00') }],
342
+ },
343
+ ],
344
+ },
345
+ ],
346
+ }
347
+ ```
348
+
349
+ ### Permanent Schedule Change
350
+
351
+ ```typescript
352
+ const newSchedule: Schedule = {
353
+ ...restaurant,
354
+ overrides: [
355
+ {
356
+ // New schedule starting Jan 1, 2026 (indefinite)
357
+ from: sDate('2026-01-01'),
358
+ // No 'to' date = applies indefinitely
359
+ rules: [
360
+ {
361
+ weekdays: sWeekdays('SMTWTFS'), // All days
362
+ times: [{ from: sTime('09:00'), to: sTime('21:00') }],
363
+ },
364
+ ],
365
+ },
366
+ ],
367
+ }
368
+ ```
369
+
370
+ ### Cross-Midnight Hours (Late Night)
371
+
372
+ ```typescript
373
+ const lateNightBar: Schedule = {
374
+ timezone: 'America/Puerto_Rico',
375
+ weekly: [
376
+ {
377
+ weekdays: sWeekdays('----TFS'), // Thu-Sat
378
+ times: [{ from: sTime('20:00'), to: sTime('03:00') }], // 8PM-3AM
379
+ },
380
+ ],
381
+ }
382
+
383
+ // This means:
384
+ // - Thursday: 20:00-23:59, Friday: 00:00-03:00
385
+ // - Friday: 20:00-23:59, Saturday: 00:00-03:00
386
+ // - Saturday: 20:00-23:59, Sunday: 00:00-03:00
387
+ ```
388
+
389
+ ### Multiple Schedules (Layered Availability)
390
+
391
+ ```typescript
392
+ import { isScheduleAvailable } from 'scschedule'
393
+
394
+ const businessHours: Schedule = {
395
+ /* ... */
396
+ }
397
+ const breakfastMenu: Schedule = {
398
+ /* ... */
399
+ }
400
+
401
+ // Both must be available
402
+ const canOrderBreakfast =
403
+ isScheduleAvailable(businessHours, timestamp) &&
404
+ isScheduleAvailable(breakfastMenu, timestamp)
405
+ ```
406
+
407
+ ## Validation Error Types
408
+
409
+ The library uses discriminated unions for type-safe error handling:
410
+
411
+ ```typescript
412
+ type ValidationError =
413
+ | {
414
+ issue: ValidationIssue.InvalidTimezone
415
+ timezone: string
416
+ }
417
+ | {
418
+ issue: ValidationIssue.DuplicateOverrides
419
+ overrideIndexes: [number, number]
420
+ }
421
+ | {
422
+ issue: ValidationIssue.OverlappingSpecificOverrides
423
+ overrideIndexes: [number, number]
424
+ }
425
+ | {
426
+ issue: ValidationIssue.OverlappingTimesInRule
427
+ location:
428
+ | { type: RuleLocationType.Weekly; ruleIndex: number }
429
+ | {
430
+ type: RuleLocationType.Override
431
+ overrideIndex: number
432
+ ruleIndex: number
433
+ }
434
+ timeRangeIndexes: [number, number]
435
+ }
436
+ | {
437
+ issue: ValidationIssue.EmptyTimes
438
+ location:
439
+ | { type: RuleLocationType.Weekly; ruleIndex: number }
440
+ | {
441
+ type: RuleLocationType.Override
442
+ overrideIndex: number
443
+ ruleIndex: number
444
+ }
445
+ }
446
+ | {
447
+ issue: ValidationIssue.InvalidScDateFormat
448
+ field: string
449
+ value: string
450
+ expectedFormat: string
451
+ }
452
+ | {
453
+ issue: ValidationIssue.EmptyWeekdays
454
+ location:
455
+ | { type: RuleLocationType.Weekly; ruleIndex: number }
456
+ | {
457
+ type: RuleLocationType.Override
458
+ overrideIndex: number
459
+ ruleIndex: number
460
+ }
461
+ }
462
+ | {
463
+ issue: ValidationIssue.OverrideWeekdaysMismatch
464
+ overrideIndex: number
465
+ ruleIndex: number
466
+ weekdays: string
467
+ dateRange: { from: string; to: string }
468
+ }
469
+ ```
470
+
471
+ ## Best Practices
472
+
473
+ 1. **Always validate schedules** before using them in production
474
+ 2. **Clean up expired overrides** periodically to keep schedules manageable
475
+ 3. **Use specific date ranges** for overrides when possible - indefinite overrides are useful for permanent schedule changes
476
+ 4. **When using multiple indefinite overrides**, remember that the most recent one (latest `from` date) takes precedence
477
+ 5. **Test cross-midnight ranges** thoroughly if your schedule uses them
478
+ 6. **Consider timezone** carefully - all times are interpreted in the schedule's timezone
479
+ 7. **Handle DST transitions** by testing schedules during spring forward and fall back
480
+
481
+ ## Edge Cases
482
+
483
+ ### DST Transitions
484
+
485
+ The library handles DST transitions using scdate's timezone functions. Times that fall in "missing hours" (spring forward) are treated as unavailable.
486
+
487
+ ### Cross-Midnight Ranges
488
+
489
+ Time ranges that cross midnight (e.g., `22:00-02:00`) are split internally and always spill into the next calendar day, regardless of whether that day is in the weekdays pattern.
490
+
491
+ ### Overlapping Overrides
492
+
493
+ Specific overrides (with both `from` and `to`) cannot overlap - this is a validation error. However, indefinite overrides can coexist with each other and with future specific overrides. When multiple indefinite overrides could apply to a date, the one with the latest `from` date is used (most recent policy wins).
494
+
495
+ ## TypeScript Support
496
+
497
+ The library is written in TypeScript and provides full type definitions. All types are exported for use in your code:
498
+
499
+ ```typescript
500
+ import type {
501
+ Schedule,
502
+ WeeklyScheduleRule,
503
+ OverrideScheduleRule,
504
+ TimeRange,
505
+ AvailabilityRange,
506
+ ValidationError,
507
+ ValidationResult,
508
+ } from 'scschedule'
509
+ ```
32
510
 
33
511
  ## Dependencies
34
512
 
@@ -36,6 +514,12 @@ This package depends on:
36
514
 
37
515
  - `scdate`: Core date and time handling library
38
516
 
517
+ All of scdate's peer dependencies are also required.
518
+
39
519
  ## License
40
520
 
41
521
  MIT
522
+
523
+ ## Contributing
524
+
525
+ Issues and pull requests are welcome on [GitHub](https://github.com/ericvera/scdate).
@@ -0,0 +1,7 @@
1
+ import { type SDate } from 'scdate';
2
+ import type { Schedule } from './types.js';
3
+ /**
4
+ * Removes expired overrides from a schedule that ended before the
5
+ * specified date. It does not remove indefinite overrides (no to date).
6
+ */
7
+ export declare const cleanupExpiredOverridesFromSchedule: (schedule: Schedule, beforeDate: SDate | string) => Schedule;
@@ -0,0 +1,29 @@
1
+ import { isSameDateOrAfter } from 'scdate';
2
+ /**
3
+ * Removes expired overrides from a schedule that ended before the
4
+ * specified date. It does not remove indefinite overrides (no to date).
5
+ */
6
+ export const cleanupExpiredOverridesFromSchedule = (schedule, beforeDate) => {
7
+ // If there are no overrides, return the schedule as-is
8
+ if (!schedule.overrides || schedule.overrides.length === 0) {
9
+ return schedule;
10
+ }
11
+ // Filter out overrides that have ended before the given date
12
+ const filteredOverrides = schedule.overrides.filter((override) => {
13
+ // Keep indefinite overrides (no 'to' date)
14
+ if (!override.to) {
15
+ return true;
16
+ }
17
+ // Keep overrides that end on or after beforeDate
18
+ return isSameDateOrAfter(override.to, beforeDate);
19
+ });
20
+ // Return new Schedule instance with filtered overrides
21
+ if (filteredOverrides.length === 0) {
22
+ const { overrides, ...rest } = schedule;
23
+ return rest;
24
+ }
25
+ return {
26
+ ...schedule,
27
+ overrides: filteredOverrides,
28
+ };
29
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Enumeration of all possible validation issues that can occur when validating
3
+ * a schedule. Used in ValidationError to identify the specific type of
4
+ * validation failure.
5
+ */
6
+ export declare enum ValidationIssue {
7
+ /** The timezone string is not a valid IANA timezone identifier */
8
+ InvalidTimezone = "invalid-timezone",
9
+ /** Two or more specific overrides have identical date ranges */
10
+ DuplicateOverrides = "duplicate-overrides",
11
+ /** Two or more specific overrides have overlapping date ranges */
12
+ OverlappingSpecificOverrides = "overlapping-specific-overrides",
13
+ /** Time ranges within a single rule overlap with each other */
14
+ OverlappingTimesInRule = "overlapping-times-in-rule",
15
+ /**
16
+ * A rule has an empty times array (should have at least one time range or be
17
+ * removed)
18
+ */
19
+ EmptyTimes = "empty-times",
20
+ /**
21
+ * A field contains an invalid scdate format (SDate, STime, SWeekdays, or
22
+ * STimestamp)
23
+ */
24
+ InvalidScDateFormat = "invalid-scdate-format",
25
+ /**
26
+ * A rule has a weekdays pattern with no days selected (e.g., '-------')
27
+ */
28
+ EmptyWeekdays = "empty-weekdays",
29
+ /**
30
+ * An override has weekdays that don't match any actual dates in the
31
+ * override's date range, making it effectively closed (should use empty rules
32
+ * instead)
33
+ */
34
+ OverrideWeekdaysMismatch = "override-weekdays-mismatch",
35
+ /**
36
+ * Two or more rules in the weekly schedule have overlapping weekdays and
37
+ * time ranges
38
+ */
39
+ OverlappingRulesInWeekly = "overlapping-rules-in-weekly",
40
+ /**
41
+ * Two or more rules within the same override have overlapping weekdays and
42
+ * time ranges
43
+ */
44
+ OverlappingRulesInOverride = "overlapping-rules-in-override",
45
+ /**
46
+ * Cross-midnight spillover (from weekly rule or previous override) conflicts
47
+ * with override's first day time ranges
48
+ */
49
+ SpilloverConflictIntoOverrideFirstDay = "spillover-conflict-into-override-first-day",
50
+ /**
51
+ * Cross-midnight spillover from override's last day conflicts with next
52
+ * day's time ranges (weekly or another override)
53
+ */
54
+ SpilloverConflictOverrideIntoNext = "spillover-conflict-override-into-next",
55
+ /**
56
+ * An override has a 'to' date that is before the 'from' date
57
+ */
58
+ InvalidOverrideDateOrder = "invalid-override-date-order"
59
+ }
60
+ /**
61
+ * Enumeration of rule location types used in validation errors to indicate
62
+ * where a validation issue occurred within a schedule structure.
63
+ */
64
+ export declare enum RuleLocationType {
65
+ /** The rule is in the weekly schedule section */
66
+ Weekly = "weekly",
67
+ /** The rule is in an override section */
68
+ Override = "override"
69
+ }