ts-time-utils 3.0.4 → 4.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 (86) hide show
  1. package/README.md +186 -6
  2. package/dist/calculate.d.ts +25 -0
  3. package/dist/calculate.d.ts.map +1 -1
  4. package/dist/calculate.js +125 -0
  5. package/dist/calendar.d.ts +45 -0
  6. package/dist/calendar.d.ts.map +1 -1
  7. package/dist/calendar.js +68 -0
  8. package/dist/calendars.d.ts +156 -0
  9. package/dist/calendars.d.ts.map +1 -0
  10. package/dist/calendars.js +348 -0
  11. package/dist/compare.d.ts +27 -0
  12. package/dist/compare.d.ts.map +1 -1
  13. package/dist/compare.js +46 -0
  14. package/dist/esm/calculate.d.ts +25 -0
  15. package/dist/esm/calculate.d.ts.map +1 -1
  16. package/dist/esm/calculate.js +125 -0
  17. package/dist/esm/calendar.d.ts +45 -0
  18. package/dist/esm/calendar.d.ts.map +1 -1
  19. package/dist/esm/calendar.js +68 -0
  20. package/dist/esm/calendars.d.ts +156 -0
  21. package/dist/esm/calendars.d.ts.map +1 -0
  22. package/dist/esm/calendars.js +348 -0
  23. package/dist/esm/compare.d.ts +27 -0
  24. package/dist/esm/compare.d.ts.map +1 -1
  25. package/dist/esm/compare.js +46 -0
  26. package/dist/esm/finance.d.ts +236 -0
  27. package/dist/esm/finance.d.ts.map +1 -0
  28. package/dist/esm/finance.js +495 -0
  29. package/dist/esm/healthcare.d.ts +260 -0
  30. package/dist/esm/healthcare.d.ts.map +1 -0
  31. package/dist/esm/healthcare.js +447 -0
  32. package/dist/esm/holidays.d.ts +11 -1
  33. package/dist/esm/holidays.d.ts.map +1 -1
  34. package/dist/esm/holidays.js +220 -1
  35. package/dist/esm/index.d.ts +19 -7
  36. package/dist/esm/index.d.ts.map +1 -1
  37. package/dist/esm/index.js +23 -9
  38. package/dist/esm/iterate.d.ts +55 -0
  39. package/dist/esm/iterate.d.ts.map +1 -1
  40. package/dist/esm/iterate.js +86 -0
  41. package/dist/esm/locale.d.ts +53 -0
  42. package/dist/esm/locale.d.ts.map +1 -1
  43. package/dist/esm/locale.js +141 -0
  44. package/dist/esm/precision.d.ts +225 -0
  45. package/dist/esm/precision.d.ts.map +1 -0
  46. package/dist/esm/precision.js +491 -0
  47. package/dist/esm/scheduling.d.ts +206 -0
  48. package/dist/esm/scheduling.d.ts.map +1 -0
  49. package/dist/esm/scheduling.js +329 -0
  50. package/dist/esm/temporal.d.ts +237 -0
  51. package/dist/esm/temporal.d.ts.map +1 -0
  52. package/dist/esm/temporal.js +660 -0
  53. package/dist/esm/validate.d.ts +30 -0
  54. package/dist/esm/validate.d.ts.map +1 -1
  55. package/dist/esm/validate.js +48 -0
  56. package/dist/finance.d.ts +236 -0
  57. package/dist/finance.d.ts.map +1 -0
  58. package/dist/finance.js +495 -0
  59. package/dist/healthcare.d.ts +260 -0
  60. package/dist/healthcare.d.ts.map +1 -0
  61. package/dist/healthcare.js +447 -0
  62. package/dist/holidays.d.ts +11 -1
  63. package/dist/holidays.d.ts.map +1 -1
  64. package/dist/holidays.js +220 -1
  65. package/dist/index.d.ts +19 -7
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +23 -9
  68. package/dist/iterate.d.ts +55 -0
  69. package/dist/iterate.d.ts.map +1 -1
  70. package/dist/iterate.js +86 -0
  71. package/dist/locale.d.ts +53 -0
  72. package/dist/locale.d.ts.map +1 -1
  73. package/dist/locale.js +141 -0
  74. package/dist/precision.d.ts +225 -0
  75. package/dist/precision.d.ts.map +1 -0
  76. package/dist/precision.js +491 -0
  77. package/dist/scheduling.d.ts +206 -0
  78. package/dist/scheduling.d.ts.map +1 -0
  79. package/dist/scheduling.js +329 -0
  80. package/dist/temporal.d.ts +237 -0
  81. package/dist/temporal.d.ts.map +1 -0
  82. package/dist/temporal.js +660 -0
  83. package/dist/validate.d.ts +30 -0
  84. package/dist/validate.d.ts.map +1 -1
  85. package/dist/validate.js +48 -0
  86. package/package.json +31 -1
@@ -0,0 +1,491 @@
1
+ /**
2
+ * @fileoverview High-precision time utilities
3
+ * Handles nanoseconds, BigInt timestamps, sub-millisecond operations
4
+ */
5
+ /**
6
+ * Create a nanosecond timestamp from components
7
+ */
8
+ export function createNanosecondTimestamp(milliseconds, nanoseconds = 0) {
9
+ // Normalize: ensure nanoseconds is 0-999999
10
+ const extraMs = Math.floor(nanoseconds / 1000000);
11
+ const normalizedNs = nanoseconds % 1000000;
12
+ const totalMs = milliseconds + extraMs;
13
+ const totalNanoseconds = BigInt(totalMs) * BigInt(1000000) + BigInt(normalizedNs);
14
+ return {
15
+ milliseconds: totalMs,
16
+ nanoseconds: normalizedNs,
17
+ totalNanoseconds,
18
+ };
19
+ }
20
+ /**
21
+ * Create a nanosecond timestamp from BigInt
22
+ */
23
+ export function fromNanoseconds(totalNs) {
24
+ const ms = Number(totalNs / BigInt(1000000));
25
+ const ns = Number(totalNs % BigInt(1000000));
26
+ return {
27
+ milliseconds: ms,
28
+ nanoseconds: ns,
29
+ totalNanoseconds: totalNs,
30
+ };
31
+ }
32
+ /**
33
+ * Create a nanosecond timestamp from a Date
34
+ */
35
+ export function dateToNanoseconds(date) {
36
+ return createNanosecondTimestamp(date.getTime(), 0);
37
+ }
38
+ /**
39
+ * Convert nanosecond timestamp to Date (loses sub-millisecond precision)
40
+ */
41
+ export function nanosecondsToDate(timestamp) {
42
+ return new Date(timestamp.milliseconds);
43
+ }
44
+ /**
45
+ * Add two nanosecond timestamps
46
+ */
47
+ export function addNanoseconds(a, b) {
48
+ return fromNanoseconds(a.totalNanoseconds + b.totalNanoseconds);
49
+ }
50
+ /**
51
+ * Subtract nanosecond timestamps
52
+ */
53
+ export function subtractNanoseconds(a, b) {
54
+ return fromNanoseconds(a.totalNanoseconds - b.totalNanoseconds);
55
+ }
56
+ /**
57
+ * Compare nanosecond timestamps
58
+ * @returns -1 if a < b, 0 if equal, 1 if a > b
59
+ */
60
+ export function compareNanoseconds(a, b) {
61
+ if (a.totalNanoseconds < b.totalNanoseconds)
62
+ return -1;
63
+ if (a.totalNanoseconds > b.totalNanoseconds)
64
+ return 1;
65
+ return 0;
66
+ }
67
+ /**
68
+ * Get current time with nanosecond precision (if available)
69
+ * Falls back to millisecond precision if performance.now() not available
70
+ */
71
+ export function nowNanoseconds() {
72
+ const now = Date.now();
73
+ // Try to get sub-millisecond precision from performance.now()
74
+ if (typeof performance !== 'undefined' && performance.now) {
75
+ const perfNow = performance.now();
76
+ const microSeconds = Math.round((perfNow % 1) * 1000);
77
+ return createNanosecondTimestamp(now, microSeconds * 1000);
78
+ }
79
+ return createNanosecondTimestamp(now, 0);
80
+ }
81
+ /**
82
+ * Format nanosecond timestamp to ISO string with sub-millisecond precision
83
+ */
84
+ export function formatNanoseconds(timestamp) {
85
+ const date = nanosecondsToDate(timestamp);
86
+ const isoBase = date.toISOString().slice(0, -1); // Remove trailing 'Z'
87
+ // Add nanoseconds (6 additional digits after milliseconds)
88
+ const nsStr = timestamp.nanoseconds.toString().padStart(6, '0');
89
+ return `${isoBase}${nsStr}Z`;
90
+ }
91
+ /**
92
+ * Parse ISO string with nanosecond precision
93
+ */
94
+ export function parseNanoseconds(isoString) {
95
+ // Match ISO format: 2024-03-25T14:30:45.123456789Z
96
+ const match = isoString.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3,9})Z?$/);
97
+ if (!match) {
98
+ // Try standard parsing
99
+ const date = new Date(isoString);
100
+ if (isNaN(date.getTime()))
101
+ return null;
102
+ return dateToNanoseconds(date);
103
+ }
104
+ const [, year, month, day, hour, minute, second, fraction] = match;
105
+ // Build base date with milliseconds
106
+ const ms = parseInt(fraction.slice(0, 3).padEnd(3, '0'), 10);
107
+ const date = new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10), parseInt(hour, 10), parseInt(minute, 10), parseInt(second, 10), ms);
108
+ // Calculate nanoseconds beyond milliseconds
109
+ const nsFraction = fraction.slice(3).padEnd(6, '0');
110
+ const nanoseconds = parseInt(nsFraction, 10);
111
+ return createNanosecondTimestamp(date.getTime(), nanoseconds);
112
+ }
113
+ // ============= High-Resolution Duration =============
114
+ /**
115
+ * Create a high-resolution duration
116
+ */
117
+ export function createHighResDuration(seconds, nanoseconds = 0) {
118
+ // Normalize: carry over extra seconds from nanoseconds
119
+ const extraSeconds = Math.floor(nanoseconds / 1000000000);
120
+ const normalizedNs = nanoseconds % 1000000000;
121
+ return {
122
+ seconds: seconds + extraSeconds,
123
+ nanoseconds: normalizedNs,
124
+ };
125
+ }
126
+ /**
127
+ * Add two high-resolution durations
128
+ */
129
+ export function addHighResDuration(a, b) {
130
+ const totalNs = a.nanoseconds + b.nanoseconds;
131
+ const extraSeconds = Math.floor(totalNs / 1000000000);
132
+ const ns = totalNs % 1000000000;
133
+ return {
134
+ seconds: a.seconds + b.seconds + extraSeconds,
135
+ nanoseconds: ns,
136
+ };
137
+ }
138
+ /**
139
+ * Subtract high-resolution durations
140
+ */
141
+ export function subtractHighResDuration(a, b) {
142
+ let totalNs = a.nanoseconds - b.nanoseconds;
143
+ let seconds = a.seconds - b.seconds;
144
+ if (totalNs < 0) {
145
+ seconds -= 1;
146
+ totalNs += 1000000000;
147
+ }
148
+ return { seconds, nanoseconds: totalNs };
149
+ }
150
+ /**
151
+ * Convert high-resolution duration to milliseconds
152
+ */
153
+ export function highResDurationToMs(duration) {
154
+ return duration.seconds * 1000 + duration.nanoseconds / 1000000;
155
+ }
156
+ /**
157
+ * Convert milliseconds to high-resolution duration
158
+ */
159
+ export function msToHighResDuration(ms) {
160
+ const seconds = Math.floor(ms / 1000);
161
+ const nanoseconds = Math.round((ms % 1000) * 1000000);
162
+ return { seconds, nanoseconds };
163
+ }
164
+ // ============= BigInt Timestamp Support =============
165
+ /**
166
+ * Convert Unix epoch milliseconds to BigInt
167
+ */
168
+ export function toBigIntMs(date) {
169
+ return BigInt(date.getTime());
170
+ }
171
+ /**
172
+ * Convert BigInt milliseconds to Date
173
+ */
174
+ export function fromBigIntMs(ms) {
175
+ return new Date(Number(ms));
176
+ }
177
+ /**
178
+ * Convert Unix epoch seconds to BigInt
179
+ */
180
+ export function toBigIntSeconds(date) {
181
+ return BigInt(Math.floor(date.getTime() / 1000));
182
+ }
183
+ /**
184
+ * Convert BigInt seconds to Date
185
+ */
186
+ export function fromBigIntSeconds(seconds) {
187
+ return new Date(Number(seconds) * 1000);
188
+ }
189
+ /**
190
+ * Add milliseconds (as BigInt) to a date
191
+ */
192
+ export function addBigIntMs(date, ms) {
193
+ const current = toBigIntMs(date);
194
+ return fromBigIntMs(current + ms);
195
+ }
196
+ /**
197
+ * Subtract milliseconds (as BigInt) from a date
198
+ */
199
+ export function subtractBigIntMs(date, ms) {
200
+ const current = toBigIntMs(date);
201
+ return fromBigIntMs(current - ms);
202
+ }
203
+ /**
204
+ * Calculate difference between dates in BigInt milliseconds
205
+ */
206
+ export function diffBigIntMs(a, b) {
207
+ return toBigIntMs(a) - toBigIntMs(b);
208
+ }
209
+ /**
210
+ * Check if a date falls in a DST transition gap (skipped time)
211
+ * @param date - Date to check
212
+ * @param timeZone - IANA timezone (optional, uses local if not provided)
213
+ */
214
+ export function isInDSTGap(date, timeZone) {
215
+ if (timeZone) {
216
+ // For specific timezone, we need to check around this time
217
+ const formatter = new Intl.DateTimeFormat('en-US', {
218
+ timeZone,
219
+ hour: 'numeric',
220
+ minute: 'numeric',
221
+ year: 'numeric',
222
+ month: 'numeric',
223
+ day: 'numeric',
224
+ hour12: false,
225
+ });
226
+ // Get 1 minute before and after
227
+ const before = new Date(date.getTime() - 60000);
228
+ const after = new Date(date.getTime() + 60000);
229
+ const beforeParts = formatter.formatToParts(before);
230
+ const afterParts = formatter.formatToParts(after);
231
+ const getMinuteValue = (parts) => {
232
+ const h = parseInt(parts.find(p => p.type === 'hour')?.value || '0', 10);
233
+ const m = parseInt(parts.find(p => p.type === 'minute')?.value || '0', 10);
234
+ return h * 60 + m;
235
+ };
236
+ // If there's a jump of more than 2 minutes between 1 min before and 1 min after,
237
+ // we're likely in a gap
238
+ const beforeMinute = getMinuteValue(beforeParts);
239
+ const afterMinute = getMinuteValue(afterParts);
240
+ const diff = afterMinute - beforeMinute;
241
+ // Account for day boundary
242
+ const adjustedDiff = diff < 0 ? diff + 1440 : diff;
243
+ return adjustedDiff > 62; // Allow 2 minute variance + DST gap (typically 60 min)
244
+ }
245
+ // For local timezone, compare UTC offset around this time
246
+ const before = new Date(date.getTime() - 60000);
247
+ const after = new Date(date.getTime() + 60000);
248
+ const offsetBefore = before.getTimezoneOffset();
249
+ const offsetAfter = after.getTimezoneOffset();
250
+ // If offset changes and goes from larger to smaller (spring forward), it's a gap
251
+ return offsetBefore > offsetAfter;
252
+ }
253
+ /**
254
+ * Check if a date falls in a DST overlap (ambiguous time)
255
+ * @param date - Date to check
256
+ * @param timeZone - IANA timezone (optional, uses local if not provided)
257
+ */
258
+ export function isInDSTOverlap(date, timeZone) {
259
+ if (timeZone) {
260
+ // Similar approach as isInDSTGap but for fall-back
261
+ // This is harder to detect accurately without full TZ database
262
+ return false; // Simplified: would need TZ data for accurate detection
263
+ }
264
+ // For local timezone
265
+ const before = new Date(date.getTime() - 60000);
266
+ const after = new Date(date.getTime() + 60000);
267
+ const offsetBefore = before.getTimezoneOffset();
268
+ const offsetAfter = after.getTimezoneOffset();
269
+ // If offset goes from smaller to larger (fall back), it's an overlap
270
+ return offsetBefore < offsetAfter;
271
+ }
272
+ /**
273
+ * Find DST transitions in a year for local timezone
274
+ */
275
+ export function getDSTTransitionsInYear(year) {
276
+ const transitions = [];
277
+ let prevOffset = new Date(year, 0, 1).getTimezoneOffset();
278
+ // Check each day of the year
279
+ for (let month = 0; month < 12; month++) {
280
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
281
+ for (let day = 1; day <= daysInMonth; day++) {
282
+ const date = new Date(year, month, day);
283
+ const currentOffset = date.getTimezoneOffset();
284
+ if (currentOffset !== prevOffset) {
285
+ // Found a transition - now find exact hour
286
+ for (let hour = 0; hour < 24; hour++) {
287
+ const hourDate = new Date(year, month, day, hour);
288
+ const hourOffset = hourDate.getTimezoneOffset();
289
+ if (hourOffset !== prevOffset) {
290
+ const toDST = hourOffset < prevOffset;
291
+ const adjustmentMinutes = Math.abs(hourOffset - prevOffset);
292
+ transitions.push({
293
+ utc: new Date(Date.UTC(year, month, day, hour)),
294
+ local: hourDate,
295
+ toDST,
296
+ offsetBefore: -prevOffset, // Convert to positive = UTC+X
297
+ offsetAfter: -hourOffset,
298
+ adjustmentMinutes,
299
+ });
300
+ prevOffset = hourOffset;
301
+ break;
302
+ }
303
+ }
304
+ }
305
+ prevOffset = currentOffset;
306
+ }
307
+ }
308
+ return transitions;
309
+ }
310
+ /**
311
+ * Resolve ambiguous time in DST overlap
312
+ * @param date - Potentially ambiguous date
313
+ * @param prefer - Prefer 'earlier' or 'later' interpretation
314
+ */
315
+ export function resolveAmbiguousTime(date, prefer = 'earlier') {
316
+ if (!isInDSTOverlap(date)) {
317
+ return date;
318
+ }
319
+ // During overlap, the same local time occurs twice
320
+ // The 'earlier' occurrence is DST (smaller offset)
321
+ // The 'later' occurrence is standard time (larger offset)
322
+ const offset = date.getTimezoneOffset();
323
+ if (prefer === 'earlier') {
324
+ // Return DST interpretation (typically 1 hour earlier in UTC)
325
+ return new Date(date.getTime() - 60 * 60 * 1000);
326
+ }
327
+ else {
328
+ // Return standard time interpretation
329
+ return date;
330
+ }
331
+ }
332
+ // ============= Invalid Date Handling =============
333
+ /**
334
+ * Validated Date wrapper that guarantees a valid date
335
+ */
336
+ export class ValidDate {
337
+ constructor(date) {
338
+ this._date = date;
339
+ }
340
+ /**
341
+ * Create a ValidDate, throws if invalid
342
+ */
343
+ static from(date) {
344
+ if (isNaN(date.getTime())) {
345
+ throw new Error('Invalid Date');
346
+ }
347
+ return new ValidDate(date);
348
+ }
349
+ /**
350
+ * Create a ValidDate, returns null if invalid
351
+ */
352
+ static tryFrom(date) {
353
+ if (isNaN(date.getTime())) {
354
+ return null;
355
+ }
356
+ return new ValidDate(date);
357
+ }
358
+ /**
359
+ * Create from timestamp, throws if invalid
360
+ */
361
+ static fromTimestamp(ms) {
362
+ const date = new Date(ms);
363
+ return ValidDate.from(date);
364
+ }
365
+ /**
366
+ * Create from ISO string, throws if invalid
367
+ */
368
+ static fromISO(isoString) {
369
+ const date = new Date(isoString);
370
+ return ValidDate.from(date);
371
+ }
372
+ /**
373
+ * Get the underlying Date object
374
+ */
375
+ get value() {
376
+ return new Date(this._date.getTime()); // Return copy for immutability
377
+ }
378
+ /**
379
+ * Get timestamp
380
+ */
381
+ get time() {
382
+ return this._date.getTime();
383
+ }
384
+ /**
385
+ * Format to ISO string
386
+ */
387
+ toISOString() {
388
+ return this._date.toISOString();
389
+ }
390
+ /**
391
+ * Format to locale string
392
+ */
393
+ toLocaleString(locale, options) {
394
+ return this._date.toLocaleString(locale, options);
395
+ }
396
+ }
397
+ /**
398
+ * Ensure a date is valid, with fallback
399
+ */
400
+ export function ensureValidDate(date, fallback = new Date()) {
401
+ return isNaN(date.getTime()) ? fallback : date;
402
+ }
403
+ /**
404
+ * Parse date with validation
405
+ */
406
+ export function parseValidDate(input) {
407
+ if (input instanceof Date) {
408
+ return isNaN(input.getTime()) ? null : input;
409
+ }
410
+ const date = new Date(input);
411
+ return isNaN(date.getTime()) ? null : date;
412
+ }
413
+ /**
414
+ * Assert date is valid, throws if not
415
+ */
416
+ export function assertValidDate(date, message) {
417
+ if (isNaN(date.getTime())) {
418
+ throw new Error(message || 'Invalid Date');
419
+ }
420
+ }
421
+ // ============= Leap Second Awareness =============
422
+ /**
423
+ * Known leap seconds (added at end of these dates, 23:59:60 UTC)
424
+ * List from https://www.ietf.org/timezones/data/leap-seconds.list
425
+ */
426
+ export const LEAP_SECONDS = [
427
+ new Date('1972-06-30T23:59:59Z'),
428
+ new Date('1972-12-31T23:59:59Z'),
429
+ new Date('1973-12-31T23:59:59Z'),
430
+ new Date('1974-12-31T23:59:59Z'),
431
+ new Date('1975-12-31T23:59:59Z'),
432
+ new Date('1976-12-31T23:59:59Z'),
433
+ new Date('1977-12-31T23:59:59Z'),
434
+ new Date('1978-12-31T23:59:59Z'),
435
+ new Date('1979-12-31T23:59:59Z'),
436
+ new Date('1981-06-30T23:59:59Z'),
437
+ new Date('1982-06-30T23:59:59Z'),
438
+ new Date('1983-06-30T23:59:59Z'),
439
+ new Date('1985-06-30T23:59:59Z'),
440
+ new Date('1987-12-31T23:59:59Z'),
441
+ new Date('1989-12-31T23:59:59Z'),
442
+ new Date('1990-12-31T23:59:59Z'),
443
+ new Date('1992-06-30T23:59:59Z'),
444
+ new Date('1993-06-30T23:59:59Z'),
445
+ new Date('1994-06-30T23:59:59Z'),
446
+ new Date('1995-12-31T23:59:59Z'),
447
+ new Date('1997-06-30T23:59:59Z'),
448
+ new Date('1998-12-31T23:59:59Z'),
449
+ new Date('2005-12-31T23:59:59Z'),
450
+ new Date('2008-12-31T23:59:59Z'),
451
+ new Date('2012-06-30T23:59:59Z'),
452
+ new Date('2015-06-30T23:59:59Z'),
453
+ new Date('2016-12-31T23:59:59Z'),
454
+ ];
455
+ /**
456
+ * Get number of leap seconds between two dates
457
+ */
458
+ export function leapSecondsBetween(start, end) {
459
+ let count = 0;
460
+ for (const ls of LEAP_SECONDS) {
461
+ if (ls >= start && ls < end) {
462
+ count++;
463
+ }
464
+ }
465
+ return count;
466
+ }
467
+ /**
468
+ * Check if a date is near a leap second (within 1 second)
469
+ */
470
+ export function isNearLeapSecond(date) {
471
+ const time = date.getTime();
472
+ return LEAP_SECONDS.some(ls => Math.abs(ls.getTime() - time) <= 1000);
473
+ }
474
+ /**
475
+ * Convert TAI (International Atomic Time) to UTC
476
+ * TAI = UTC + accumulated leap seconds + 10 (initial offset)
477
+ */
478
+ export function taiToUtc(taiMs) {
479
+ // Count leap seconds before this TAI time
480
+ const utcApprox = new Date(taiMs);
481
+ const leapSeconds = leapSecondsBetween(new Date(0), utcApprox);
482
+ // TAI started 10 seconds ahead of UTC at Unix epoch
483
+ return new Date(taiMs - (leapSeconds + 10) * 1000);
484
+ }
485
+ /**
486
+ * Convert UTC to TAI
487
+ */
488
+ export function utcToTai(date) {
489
+ const leapSeconds = leapSecondsBetween(new Date(0), date);
490
+ return date.getTime() + (leapSeconds + 10) * 1000;
491
+ }
@@ -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"}