scschedule 2.1.1 → 3.1.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 (44) hide show
  1. package/README.md +161 -74
  2. package/dist/cleanupExpiredOverridesFromSchedule.d.ts +4 -0
  3. package/dist/cleanupExpiredOverridesFromSchedule.js +4 -0
  4. package/dist/constants.d.ts +1 -10
  5. package/dist/constants.js +1 -10
  6. package/dist/getAvailableRangesFromSchedule.d.ts +10 -0
  7. package/dist/getAvailableRangesFromSchedule.js +29 -13
  8. package/dist/getNextAvailableFromSchedule.d.ts +9 -8
  9. package/dist/getNextAvailableFromSchedule.js +21 -17
  10. package/dist/getNextUnavailableFromSchedule.d.ts +19 -9
  11. package/dist/getNextUnavailableFromSchedule.js +120 -63
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.js +4 -2
  14. package/dist/internal/doTimeRangesOverlap.d.ts +1 -1
  15. package/dist/internal/getApplicableRuleForDate.d.ts +11 -11
  16. package/dist/internal/getApplicableRuleForDate.js +11 -7
  17. package/dist/internal/getEffectiveTimesForWeekday.d.ts +2 -1
  18. package/dist/internal/getEffectiveTimesForWeekday.js +9 -14
  19. package/dist/internal/index.d.ts +1 -4
  20. package/dist/internal/index.js +0 -4
  21. package/dist/internal/isTimeInTimeRange.d.ts +2 -1
  22. package/dist/internal/splitCrossMidnightTimeRange.d.ts +1 -1
  23. package/dist/internal/splitCrossMidnightTimeRange.js +3 -2
  24. package/dist/internal/types.d.ts +13 -0
  25. package/dist/internal/types.js +1 -0
  26. package/dist/internal/validateNoEmptyWeekdays.js +2 -1
  27. package/dist/internal/validateNoOverlappingRules.js +5 -4
  28. package/dist/internal/validateNoSpilloverConflictsAtOverrideBoundaries.d.ts +4 -2
  29. package/dist/internal/validateNoSpilloverConflictsAtOverrideBoundaries.js +95 -88
  30. package/dist/internal/validateScDateFormats.js +4 -5
  31. package/dist/isScheduleAvailable.d.ts +9 -0
  32. package/dist/isScheduleAvailable.js +23 -6
  33. package/dist/types.d.ts +18 -64
  34. package/dist/validateSchedule.d.ts +4 -2
  35. package/dist/validateSchedule.js +4 -8
  36. package/package.json +2 -2
  37. package/dist/internal/isValidTimezone.d.ts +0 -4
  38. package/dist/internal/isValidTimezone.js +0 -12
  39. package/dist/internal/validateNoOverlappingTimesInRule.d.ts +0 -5
  40. package/dist/internal/validateNoOverlappingTimesInRule.js +0 -54
  41. package/dist/internal/validateNonEmptyTimes.d.ts +0 -5
  42. package/dist/internal/validateNonEmptyTimes.js +0 -35
  43. package/dist/internal/validateTimezone.d.ts +0 -5
  44. package/dist/internal/validateTimezone.js +0 -16
package/README.md CHANGED
@@ -16,7 +16,7 @@ scschedule is a TypeScript library for managing time-based availability patterns
16
16
  - **Recurring patterns**: Define weekly schedules with different hours for different days
17
17
  - **Override system**: Add exceptions for holidays, special events, or schedule changes
18
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
19
+ - **Time zone aware**: Time zone passed at the call site where needed (DST-safe arithmetic)
20
20
  - **DST handling**: Properly handles daylight saving time transitions
21
21
  - **Immutable**: All operations return new instances
22
22
  - **Type-safe validation**: Discriminated union errors with detailed information
@@ -46,13 +46,13 @@ import {
46
46
  } from 'scschedule'
47
47
  import { sDate, sTime, sWeekdays, getTimestampNow } from 'scdate'
48
48
 
49
- // Define a restaurant schedule: Tuesday-Saturday, 11:00-22:00
49
+ // Define a restaurant schedule: Monday-Saturday, 11:00-22:00
50
50
  const restaurant: Schedule = {
51
- timezone: 'America/Puerto_Rico',
52
51
  weekly: [
53
52
  {
54
- weekdays: sWeekdays('-MTWTFS'), // Tue-Sat
55
- times: [{ from: sTime('11:00'), to: sTime('22:00') }],
53
+ weekdays: sWeekdays('-MTWTFS'), // Mon-Sat
54
+ from: sTime('11:00'),
55
+ to: sTime('22:00'),
56
56
  },
57
57
  ],
58
58
  }
@@ -68,8 +68,8 @@ const now = getTimestampNow('America/Puerto_Rico')
68
68
  const isOpen = isScheduleAvailable(restaurant, now)
69
69
  console.log(`Restaurant is ${isOpen ? 'open' : 'closed'}`)
70
70
 
71
- // Find next opening time
72
- const nextOpen = getNextAvailableFromSchedule(restaurant, now)
71
+ // Find next opening time (search up to 30 days ahead)
72
+ const nextOpen = getNextAvailableFromSchedule(restaurant, now, 30)
73
73
  if (nextOpen) {
74
74
  console.log(`Next opening: ${nextOpen.timestamp}`)
75
75
  }
@@ -81,14 +81,12 @@ if (nextOpen) {
81
81
 
82
82
  A `Schedule` consists of:
83
83
 
84
- - **timezone**: The timezone for all time calculations
85
- - **weekly**: Base recurring schedule (array of `WeeklyScheduleRule`)
84
+ - **weekly**: Base recurring schedule `true` (available 24/7), an array of `WeeklyScheduleRule` (time-based), or `[]` (never available; overrides can open windows)
86
85
  - **overrides** (optional): Date-specific exceptions (array of `OverrideScheduleRule`)
87
86
 
88
87
  ```typescript
89
88
  interface Schedule {
90
- timezone: string
91
- weekly: WeeklyScheduleRule[]
89
+ weekly: WeeklyScheduleRule[] | true
92
90
  overrides?: OverrideScheduleRule[]
93
91
  }
94
92
  ```
@@ -100,10 +98,6 @@ Define recurring availability patterns for specific days of the week:
100
98
  ```typescript
101
99
  interface WeeklyScheduleRule {
102
100
  weekdays: SWeekdays // e.g., 'SMTWTFS' or '-MTWTF-'
103
- times: TimeRange[] // Array of time ranges
104
- }
105
-
106
- interface TimeRange {
107
101
  from: STime // e.g., '09:00'
108
102
  to: STime // e.g., '17:00'
109
103
  }
@@ -139,6 +133,17 @@ Rules are evaluated in order of priority:
139
133
 
140
134
  ### Validation
141
135
 
136
+ #### `isValidTimeZone(timeZone: string): boolean`
137
+
138
+ Re-exported from `scdate`. Checks if a string is a valid IANA time zone identifier (using `Intl.supportedValuesOf('timeZone')`).
139
+
140
+ ```typescript
141
+ import { isValidTimeZone } from 'scschedule' // or from 'scdate'
142
+
143
+ isValidTimeZone('America/New_York') // true
144
+ isValidTimeZone('Invalid/Timezone') // false
145
+ ```
146
+
142
147
  #### `validateSchedule(schedule: Schedule): ValidationResult`
143
148
 
144
149
  Validates a schedule and returns detailed errors.
@@ -151,9 +156,6 @@ const result = validateSchedule(mySchedule)
151
156
  if (!result.valid) {
152
157
  result.errors.forEach((error) => {
153
158
  switch (error.issue) {
154
- case ValidationIssue.InvalidTimezone:
155
- console.error(`Invalid timezone: ${error.timezone}`)
156
- break
157
159
  case ValidationIssue.OverlappingSpecificOverrides:
158
160
  console.error(
159
161
  `Overlapping overrides at indexes: ${error.overrideIndexes}`,
@@ -167,13 +169,16 @@ if (!result.valid) {
167
169
 
168
170
  **Validation checks**:
169
171
 
170
- - Valid timezone (in `Intl.supportedValuesOf('timeZone')`)
172
+ - Valid scdate formats (SDate, STime, SWeekdays)
173
+ - Override `to` date must not be before `from` date
171
174
  - No duplicate overrides (identical from/to dates)
172
175
  - No overlapping specific overrides (hierarchical nesting allowed)
173
- - No overlapping time ranges within rules (same weekday)
176
+ - No overlapping time ranges within a single rule (same weekday)
177
+ - No overlapping rules within the weekly schedule (shared weekdays with overlapping times)
178
+ - No overlapping rules within the same override (shared weekdays with overlapping times)
179
+ - No cross-midnight spillover conflicts at override boundaries
174
180
  - 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)
181
+ - No empty weekdays patterns (e.g., `'-------'` with no days selected)
177
182
  - Override weekdays must match at least one date in the override's date range
178
183
 
179
184
  ### Schedule Management
@@ -208,27 +213,32 @@ const isOpen = isScheduleAvailable(
208
213
  )
209
214
  ```
210
215
 
211
- #### `getNextAvailableFromSchedule(schedule: Schedule, fromTimestamp: STimestamp | string, maxDaysToSearch?: number): STimestamp | undefined`
216
+ #### `getNextAvailableFromSchedule(schedule: Schedule, fromTimestamp: STimestamp | string, maxDaysToSearch: number): STimestamp | undefined`
212
217
 
213
- Find the next available timestamp from a given time. Searches up to `maxDaysToSearch` days (default: 365).
218
+ Find the next available timestamp from a given time. Searches up to `maxDaysToSearch` days forward.
214
219
 
215
220
  ```typescript
216
221
  import { getNextAvailableFromSchedule } from 'scschedule'
217
222
 
218
- const nextOpen = getNextAvailableFromSchedule(restaurant, now)
223
+ const nextOpen = getNextAvailableFromSchedule(restaurant, now, 30)
219
224
  if (nextOpen) {
220
225
  console.log(`Opens at: ${nextOpen.timestamp}`)
221
226
  }
222
227
  ```
223
228
 
224
- #### `getNextUnavailableFromSchedule(schedule: Schedule, fromTimestamp: STimestamp | string): STimestamp | undefined`
229
+ #### `getNextUnavailableFromSchedule(schedule: Schedule, timeZone: string, fromTimestamp: STimestamp | string, maxDaysToSearch: number): STimestamp | undefined`
225
230
 
226
- Find the next unavailable timestamp from a given time.
231
+ Find the next unavailable timestamp from a given time. Requires a time zone for DST-safe timestamp arithmetic. Searches up to `maxDaysToSearch` days forward.
227
232
 
228
233
  ```typescript
229
234
  import { getNextUnavailableFromSchedule } from 'scschedule'
230
235
 
231
- const nextClosed = getNextUnavailableFromSchedule(restaurant, now)
236
+ const nextClosed = getNextUnavailableFromSchedule(
237
+ restaurant,
238
+ 'America/Puerto_Rico',
239
+ now,
240
+ 30,
241
+ )
232
242
  if (nextClosed) {
233
243
  console.log(`Closes at: ${nextClosed.timestamp}`)
234
244
  }
@@ -262,11 +272,11 @@ import { Schedule } from 'scschedule'
262
272
  import { sTime, sWeekdays } from 'scdate'
263
273
 
264
274
  const restaurant: Schedule = {
265
- timezone: 'America/Puerto_Rico',
266
275
  weekly: [
267
276
  {
268
- weekdays: sWeekdays('-MTWTFS'), // Tue-Sat
269
- times: [{ from: sTime('11:00'), to: sTime('22:00') }],
277
+ weekdays: sWeekdays('-MTWTFS'), // Mon-Sat
278
+ from: sTime('11:00'),
279
+ to: sTime('22:00'),
270
280
  },
271
281
  ],
272
282
  }
@@ -276,14 +286,16 @@ const restaurant: Schedule = {
276
286
 
277
287
  ```typescript
278
288
  const restaurant: Schedule = {
279
- timezone: 'America/Puerto_Rico',
280
289
  weekly: [
281
290
  {
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
- ],
291
+ weekdays: sWeekdays('-MTWTFS'), // Mon-Sat
292
+ from: sTime('11:00'),
293
+ to: sTime('14:00'), // Lunch
294
+ },
295
+ {
296
+ weekdays: sWeekdays('-MTWTFS'), // Mon-Sat
297
+ from: sTime('17:00'),
298
+ to: sTime('22:00'), // Dinner
287
299
  },
288
300
  ],
289
301
  }
@@ -293,17 +305,18 @@ const restaurant: Schedule = {
293
305
 
294
306
  ```typescript
295
307
  const restaurant: Schedule = {
296
- timezone: 'America/Puerto_Rico',
297
308
  weekly: [
298
309
  {
299
310
  // Weekdays: longer hours
300
311
  weekdays: sWeekdays('-MTWTF-'), // Mon-Fri
301
- times: [{ from: sTime('10:00'), to: sTime('23:00') }],
312
+ from: sTime('10:00'),
313
+ to: sTime('23:00'),
302
314
  },
303
315
  {
304
316
  // Weekends: shorter hours
305
317
  weekdays: sWeekdays('S-----S'), // Sat-Sun
306
- times: [{ from: sTime('12:00'), to: sTime('20:00') }],
318
+ from: sTime('12:00'),
319
+ to: sTime('20:00'),
307
320
  },
308
321
  ],
309
322
  }
@@ -338,7 +351,8 @@ const withExtendedHours: Schedule = {
338
351
  {
339
352
  // Extended hours for December, weekends only
340
353
  weekdays: sWeekdays('S-----S'),
341
- times: [{ from: sTime('08:00'), to: sTime('23:00') }],
354
+ from: sTime('08:00'),
355
+ to: sTime('23:00'),
342
356
  },
343
357
  ],
344
358
  },
@@ -359,7 +373,8 @@ const newSchedule: Schedule = {
359
373
  rules: [
360
374
  {
361
375
  weekdays: sWeekdays('SMTWTFS'), // All days
362
- times: [{ from: sTime('09:00'), to: sTime('21:00') }],
376
+ from: sTime('09:00'),
377
+ to: sTime('21:00'),
363
378
  },
364
379
  ],
365
380
  },
@@ -371,11 +386,11 @@ const newSchedule: Schedule = {
371
386
 
372
387
  ```typescript
373
388
  const lateNightBar: Schedule = {
374
- timezone: 'America/Puerto_Rico',
375
389
  weekly: [
376
390
  {
377
391
  weekdays: sWeekdays('----TFS'), // Thu-Sat
378
- times: [{ from: sTime('20:00'), to: sTime('03:00') }], // 8PM-3AM
392
+ from: sTime('20:00'),
393
+ to: sTime('03:00'), // 8PM-3AM
379
394
  },
380
395
  ],
381
396
  }
@@ -386,16 +401,67 @@ const lateNightBar: Schedule = {
386
401
  // - Saturday: 20:00-23:59, Sunday: 00:00-03:00
387
402
  ```
388
403
 
404
+ ### Always Available (`weekly: true`)
405
+
406
+ Use `weekly: true` when an entity is available 24/7 by default. This is useful for items that inherit availability from a parent schedule (e.g., menu items that go through the restaurant's schedule filter first).
407
+
408
+ ```typescript
409
+ // Menu item available 24/7 (restaurant hours handle the filtering)
410
+ const menuItem: Schedule = {
411
+ weekly: true,
412
+ overrides: [
413
+ {
414
+ // Except Christmas Day
415
+ from: sDate('2025-12-25'),
416
+ to: sDate('2025-12-25'),
417
+ rules: [],
418
+ },
419
+ ],
420
+ }
421
+ ```
422
+
423
+ ### Closed by Default (`weekly: []`)
424
+
425
+ Use `weekly: []` when an entity is unavailable by default and only opens during specific override periods.
426
+
427
+ ```typescript
428
+ // Pop-up shop: closed by default, open only during specific events
429
+ const popUpShop: Schedule = {
430
+ weekly: [],
431
+ overrides: [
432
+ {
433
+ from: sDate('2025-12-20'),
434
+ to: sDate('2025-12-24'),
435
+ rules: [
436
+ {
437
+ weekdays: sWeekdays('SMTWTFS'),
438
+ from: sTime('10:00'),
439
+ to: sTime('18:00'),
440
+ },
441
+ ],
442
+ },
443
+ ],
444
+ }
445
+ ```
446
+
389
447
  ### Multiple Schedules (Layered Availability)
390
448
 
391
449
  ```typescript
392
450
  import { isScheduleAvailable } from 'scschedule'
393
451
 
394
452
  const businessHours: Schedule = {
395
- /* ... */
453
+ weekly: [
454
+ {
455
+ weekdays: sWeekdays('-MTWTFS'),
456
+ from: sTime('11:00'),
457
+ to: sTime('22:00'),
458
+ },
459
+ ],
396
460
  }
461
+
462
+ // Menu available 24/7 — restaurant hours do the filtering
397
463
  const breakfastMenu: Schedule = {
398
- /* ... */
464
+ weekly: true,
399
465
  }
400
466
 
401
467
  // Both must be available
@@ -411,8 +477,16 @@ The library uses discriminated unions for type-safe error handling:
411
477
  ```typescript
412
478
  type ValidationError =
413
479
  | {
414
- issue: ValidationIssue.InvalidTimezone
415
- timezone: string
480
+ issue: ValidationIssue.InvalidScDateFormat
481
+ field: string
482
+ value: string
483
+ expectedFormat: string
484
+ }
485
+ | {
486
+ issue: ValidationIssue.InvalidOverrideDateOrder
487
+ overrideIndex: number
488
+ from: string
489
+ to: string
416
490
  }
417
491
  | {
418
492
  issue: ValidationIssue.DuplicateOverrides
@@ -423,31 +497,15 @@ type ValidationError =
423
497
  overrideIndexes: [number, number]
424
498
  }
425
499
  | {
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
- }
500
+ issue: ValidationIssue.OverlappingRulesInWeekly
501
+ ruleIndexes: [number, number]
502
+ weekday: Weekday
445
503
  }
446
504
  | {
447
- issue: ValidationIssue.InvalidScDateFormat
448
- field: string
449
- value: string
450
- expectedFormat: string
505
+ issue: ValidationIssue.OverlappingRulesInOverride
506
+ overrideIndex: number
507
+ ruleIndexes: [number, number]
508
+ weekday: Weekday
451
509
  }
452
510
  | {
453
511
  issue: ValidationIssue.EmptyWeekdays
@@ -466,6 +524,24 @@ type ValidationError =
466
524
  weekdays: string
467
525
  dateRange: { from: string; to: string }
468
526
  }
527
+ | {
528
+ issue: ValidationIssue.SpilloverConflictIntoOverrideFirstDay
529
+ overrideIndex: number
530
+ date: string
531
+ overrideRuleIndex: number
532
+ sourceWeeklyRuleIndex?: number
533
+ sourceOverrideIndex?: number
534
+ sourceOverrideRuleIndex?: number
535
+ }
536
+ | {
537
+ issue: ValidationIssue.SpilloverConflictOverrideIntoNext
538
+ overrideIndex: number
539
+ date: string
540
+ overrideRuleIndex: number
541
+ nextDayWeeklyRuleIndex?: number
542
+ nextDayOverrideIndex?: number
543
+ nextDayOverrideRuleIndex?: number
544
+ }
469
545
  ```
470
546
 
471
547
  ## Best Practices
@@ -475,14 +551,14 @@ type ValidationError =
475
551
  3. **Use specific date ranges** for overrides when possible - indefinite overrides are useful for permanent schedule changes
476
552
  4. **When using multiple indefinite overrides**, remember that the most recent one (latest `from` date) takes precedence
477
553
  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
554
+ 6. **Validate time zones** separately using `isValidTimeZone()` before passing them to functions that require one
479
555
  7. **Handle DST transitions** by testing schedules during spring forward and fall back
480
556
 
481
557
  ## Edge Cases
482
558
 
483
559
  ### DST Transitions
484
560
 
485
- The library handles DST transitions using scdate's timezone functions. Times that fall in "missing hours" (spring forward) are treated as unavailable.
561
+ The library handles DST transitions using scdate's time zone functions. Times that fall in "missing hours" (spring forward) are treated as unavailable.
486
562
 
487
563
  ### Cross-Midnight Ranges
488
564
 
@@ -501,11 +577,22 @@ import type {
501
577
  Schedule,
502
578
  WeeklyScheduleRule,
503
579
  OverrideScheduleRule,
504
- TimeRange,
505
580
  AvailabilityRange,
506
581
  ValidationError,
507
582
  ValidationResult,
583
+ SDateString,
584
+ STimeString,
585
+ STimestampString,
586
+ SWeekdaysString,
508
587
  } from 'scschedule'
588
+
589
+ import { ValidationIssue, RuleLocationType } from 'scschedule'
590
+ ```
591
+
592
+ Note: The `Weekday` enum (used in some `ValidationError` variants) is exported from `scdate`, not `scschedule`:
593
+
594
+ ```typescript
595
+ import { Weekday } from 'scdate'
509
596
  ```
510
597
 
511
598
  ## Dependencies
@@ -3,5 +3,9 @@ import type { Schedule } from './types.js';
3
3
  /**
4
4
  * Removes expired overrides from a schedule that ended before the
5
5
  * specified date. It does not remove indefinite overrides (no to date).
6
+ *
7
+ * @param schedule The schedule to clean up.
8
+ * @param beforeDate Overrides that ended before this date are removed. It
9
+ * can be an SDate or a string in the YYYY-MM-DD format.
6
10
  */
7
11
  export declare const cleanupExpiredOverridesFromSchedule: (schedule: Schedule, beforeDate: SDate | string) => Schedule;
@@ -2,6 +2,10 @@ import { isSameDateOrAfter } from 'scdate';
2
2
  /**
3
3
  * Removes expired overrides from a schedule that ended before the
4
4
  * specified date. It does not remove indefinite overrides (no to date).
5
+ *
6
+ * @param schedule The schedule to clean up.
7
+ * @param beforeDate Overrides that ended before this date are removed. It
8
+ * can be an SDate or a string in the YYYY-MM-DD format.
5
9
  */
6
10
  export const cleanupExpiredOverridesFromSchedule = (schedule, beforeDate) => {
7
11
  // If there are no overrides, return the schedule as-is
@@ -4,19 +4,10 @@
4
4
  * validation failure.
5
5
  */
6
6
  export declare enum ValidationIssue {
7
- /** The timezone string is not a valid IANA timezone identifier */
8
- InvalidTimezone = "invalid-timezone",
9
7
  /** Two or more specific overrides have identical date ranges */
10
8
  DuplicateOverrides = "duplicate-overrides",
11
9
  /** Two or more specific overrides have overlapping date ranges */
12
10
  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
11
  /**
21
12
  * A field contains an invalid scdate format (SDate, STime, SWeekdays, or
22
13
  * STimestamp)
@@ -49,7 +40,7 @@ export declare enum ValidationIssue {
49
40
  SpilloverConflictIntoOverrideFirstDay = "spillover-conflict-into-override-first-day",
50
41
  /**
51
42
  * Cross-midnight spillover from override's last day conflicts with next
52
- * day's time ranges (weekly or another override)
43
+ * day's time ranges (weekly rules, weekly: true, or another override)
53
44
  */
54
45
  SpilloverConflictOverrideIntoNext = "spillover-conflict-override-into-next",
55
46
  /**
package/dist/constants.js CHANGED
@@ -5,19 +5,10 @@
5
5
  */
6
6
  export var ValidationIssue;
7
7
  (function (ValidationIssue) {
8
- /** The timezone string is not a valid IANA timezone identifier */
9
- ValidationIssue["InvalidTimezone"] = "invalid-timezone";
10
8
  /** Two or more specific overrides have identical date ranges */
11
9
  ValidationIssue["DuplicateOverrides"] = "duplicate-overrides";
12
10
  /** Two or more specific overrides have overlapping date ranges */
13
11
  ValidationIssue["OverlappingSpecificOverrides"] = "overlapping-specific-overrides";
14
- /** Time ranges within a single rule overlap with each other */
15
- ValidationIssue["OverlappingTimesInRule"] = "overlapping-times-in-rule";
16
- /**
17
- * A rule has an empty times array (should have at least one time range or be
18
- * removed)
19
- */
20
- ValidationIssue["EmptyTimes"] = "empty-times";
21
12
  /**
22
13
  * A field contains an invalid scdate format (SDate, STime, SWeekdays, or
23
14
  * STimestamp)
@@ -50,7 +41,7 @@ export var ValidationIssue;
50
41
  ValidationIssue["SpilloverConflictIntoOverrideFirstDay"] = "spillover-conflict-into-override-first-day";
51
42
  /**
52
43
  * Cross-midnight spillover from override's last day conflicts with next
53
- * day's time ranges (weekly or another override)
44
+ * day's time ranges (weekly rules, weekly: true, or another override)
54
45
  */
55
46
  ValidationIssue["SpilloverConflictOverrideIntoNext"] = "spillover-conflict-override-into-next";
56
47
  /**
@@ -3,5 +3,15 @@ import type { AvailabilityRange, Schedule } from './types.js';
3
3
  /**
4
4
  * Returns all available time ranges within a schedule for the specified
5
5
  * date range.
6
+ *
7
+ * Iterates day-by-day from startDate to endDate (inclusive). For each day,
8
+ * when rules are `true` (always available), emits a full-day range
9
+ * (00:00-23:59). Otherwise, emits each matching time range, including
10
+ * cross-midnight ranges that extend into the next day.
11
+ *
12
+ * @param schedule The schedule to get availability from.
13
+ * @param startDate The start of the date range (inclusive).
14
+ * @param endDate The end of the date range (inclusive).
15
+ * @returns An array of availability ranges within the date range.
6
16
  */
7
17
  export declare const getAvailableRangesFromSchedule: (schedule: Schedule, startDate: SDate | string, endDate: SDate | string) => AvailabilityRange[];
@@ -3,6 +3,16 @@ import { getApplicableRuleForDate } from './internal/getApplicableRuleForDate.js
3
3
  /**
4
4
  * Returns all available time ranges within a schedule for the specified
5
5
  * date range.
6
+ *
7
+ * Iterates day-by-day from startDate to endDate (inclusive). For each day,
8
+ * when rules are `true` (always available), emits a full-day range
9
+ * (00:00-23:59). Otherwise, emits each matching time range, including
10
+ * cross-midnight ranges that extend into the next day.
11
+ *
12
+ * @param schedule The schedule to get availability from.
13
+ * @param startDate The start of the date range (inclusive).
14
+ * @param endDate The end of the date range (inclusive).
15
+ * @returns An array of availability ranges within the date range.
6
16
  */
7
17
  export const getAvailableRangesFromSchedule = (schedule, startDate, endDate) => {
8
18
  const ranges = [];
@@ -11,25 +21,31 @@ export const getAvailableRangesFromSchedule = (schedule, startDate, endDate) =>
11
21
  while (isSameDateOrBefore(currentDate, endDate)) {
12
22
  const weekday = getWeekdayFromDate(currentDate);
13
23
  const { rules } = getApplicableRuleForDate(schedule, currentDate);
24
+ // If weekly is true, the entire day is available
25
+ if (rules === true) {
26
+ ranges.push({
27
+ from: getTimestampFromDateAndTime(currentDate, '00:00'),
28
+ to: getTimestampFromDateAndTime(currentDate, '23:59'),
29
+ });
30
+ currentDate = addDaysToDate(currentDate, 1);
31
+ continue;
32
+ }
14
33
  // Find all time ranges for this day
15
34
  for (const rule of rules) {
16
35
  // Skip if this weekday is not in the rule
17
36
  if (!doesWeekdaysIncludeWeekday(rule.weekdays, weekday)) {
18
37
  continue;
19
38
  }
20
- // Process each time range
21
- for (const timeRange of rule.times) {
22
- // Handle cross-midnight ranges (from > to means it crosses midnight)
23
- const isCrossMidnight = isAfterTime(timeRange.from, timeRange.to);
24
- const rangeStart = getTimestampFromDateAndTime(currentDate, timeRange.from);
25
- const rangeEnd = isCrossMidnight
26
- ? getTimestampFromDateAndTime(addDaysToDate(currentDate, 1), timeRange.to)
27
- : getTimestampFromDateAndTime(currentDate, timeRange.to);
28
- ranges.push({
29
- from: rangeStart,
30
- to: rangeEnd,
31
- });
32
- }
39
+ // Handle cross-midnight ranges (from > to means it crosses midnight)
40
+ const isCrossMidnight = isAfterTime(rule.from, rule.to);
41
+ const rangeStart = getTimestampFromDateAndTime(currentDate, rule.from);
42
+ const rangeEnd = isCrossMidnight
43
+ ? getTimestampFromDateAndTime(addDaysToDate(currentDate, 1), rule.to)
44
+ : getTimestampFromDateAndTime(currentDate, rule.to);
45
+ ranges.push({
46
+ from: rangeStart,
47
+ to: rangeEnd,
48
+ });
33
49
  }
34
50
  // Move to the next day
35
51
  currentDate = addDaysToDate(currentDate, 1);
@@ -6,6 +6,7 @@ import type { Schedule } from './types.js';
6
6
  *
7
7
  * This function searches forward from the given timestamp to find when the
8
8
  * schedule next becomes available. It handles:
9
+ * - Always-available days (`weekly: true` or `rules: true` from an override)
9
10
  * - Same-day availability (finding the next time range on the current day)
10
11
  * - Cross-midnight spillover (ranges that extend past midnight are detected
11
12
  * via isScheduleAvailable)
@@ -15,8 +16,9 @@ import type { Schedule } from './types.js';
15
16
  * The algorithm works by:
16
17
  * 1. Checking if fromTimestamp is already available (including spillover from
17
18
  * the previous day's cross-midnight ranges)
18
- * 2. If not, finding the earliest time range start on the current day that
19
- * occurs after fromTimestamp
19
+ * 2. If not, iterating day-by-day:
20
+ * - If rules are `true`, the entire day is available (returns 00:00)
21
+ * - Otherwise, finding the earliest time range start after fromTimestamp
20
22
  * 3. If no ranges found on current day, moving to the next day and repeating
21
23
  *
22
24
  * Note: Spillover ranges don't need explicit tracking because:
@@ -26,12 +28,11 @@ import type { Schedule } from './types.js';
26
28
  * - The "next available" time is always a time range start, never a spillover
27
29
  * timestamp
28
30
  *
29
- * @param schedule - The schedule to check availability against
30
- * @param fromTimestamp - The starting timestamp to search from
31
- * @param maxDaysToSearch - Maximum number of days to search forward (default:
32
- * 365)
31
+ * @param schedule The schedule to check availability against.
32
+ * @param fromTimestamp The starting timestamp to search from.
33
+ * @param maxDaysToSearch Maximum number of days to search forward.
33
34
  * @returns The next available timestamp, or undefined if none found within
34
- * the search window
35
+ * the search window.
35
36
  *
36
37
  * @example
37
38
  * // Schedule: Mon-Fri 09:00-17:00
@@ -52,4 +53,4 @@ import type { Schedule } from './types.js';
52
53
  * // Custom search window: only search 30 days ahead
53
54
  * getNextAvailableFromSchedule(schedule, timestamp, 30)
54
55
  */
55
- export declare const getNextAvailableFromSchedule: (schedule: Schedule, fromTimestamp: STimestamp | string, maxDaysToSearch?: number) => STimestamp | undefined;
56
+ export declare const getNextAvailableFromSchedule: (schedule: Schedule, fromTimestamp: STimestamp | string, maxDaysToSearch: number) => STimestamp | undefined;