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
@@ -0,0 +1,462 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Healthcare utilities for medical scheduling and compliance timing
4
+ * Provides medication schedules, shift patterns, on-call rotations, and compliance windows
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.DEFAULT_MEDICATION_CONFIG = exports.SHIFT_DURATIONS = exports.MEDICATION_FREQUENCIES = void 0;
8
+ exports.getMedicationTimes = getMedicationTimes;
9
+ exports.getNextMedicationTime = getNextMedicationTime;
10
+ exports.parseMedicationFrequency = parseMedicationFrequency;
11
+ exports.generateShiftSchedule = generateShiftSchedule;
12
+ exports.getShiftForTime = getShiftForTime;
13
+ exports.isOnShift = isOnShift;
14
+ exports.createOnCallRotation = createOnCallRotation;
15
+ exports.getOnCallStaff = getOnCallStaff;
16
+ exports.isWithinComplianceWindow = isWithinComplianceWindow;
17
+ exports.getComplianceDeadline = getComplianceDeadline;
18
+ exports.timeUntilDeadline = timeUntilDeadline;
19
+ exports.calculateRestBetweenShifts = calculateRestBetweenShifts;
20
+ const duration_js_1 = require("./duration.cjs");
21
+ // ============================================================================
22
+ // Constants
23
+ // ============================================================================
24
+ /**
25
+ * Number of doses per day for each frequency
26
+ */
27
+ exports.MEDICATION_FREQUENCIES = {
28
+ 'QD': 1,
29
+ 'BID': 2,
30
+ 'TID': 3,
31
+ 'QID': 4,
32
+ 'q4h': 6,
33
+ 'q6h': 4,
34
+ 'q8h': 3,
35
+ 'q12h': 2,
36
+ 'PRN': null
37
+ };
38
+ /**
39
+ * Hours per shift pattern
40
+ */
41
+ exports.SHIFT_DURATIONS = {
42
+ '8hr': 8,
43
+ '12hr': 12,
44
+ '24hr': 24
45
+ };
46
+ /**
47
+ * Default medication timing config
48
+ */
49
+ exports.DEFAULT_MEDICATION_CONFIG = {
50
+ wakeTime: '07:00',
51
+ sleepTime: '22:00',
52
+ withMeals: false
53
+ };
54
+ // ============================================================================
55
+ // Helper Functions
56
+ // ============================================================================
57
+ function toDate(input) {
58
+ if (input instanceof Date)
59
+ return new Date(input.getTime());
60
+ return new Date(input);
61
+ }
62
+ function parseTimeString(time) {
63
+ const [hour, minute] = time.split(':').map(Number);
64
+ return { hour, minute };
65
+ }
66
+ function setTimeOnDate(date, hour, minute) {
67
+ const result = new Date(date.getTime());
68
+ result.setHours(hour, minute, 0, 0);
69
+ return result;
70
+ }
71
+ // ============================================================================
72
+ // Medication Scheduling Functions
73
+ // ============================================================================
74
+ /**
75
+ * Get medication administration times for a given date and frequency
76
+ *
77
+ * @param date - The date to get medication times for
78
+ * @param frequency - Medical frequency abbreviation (QD, BID, TID, etc.)
79
+ * @param config - Optional configuration for wake/sleep times
80
+ * @returns Array of Date objects for each dose, empty array for PRN
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const times = getMedicationTimes(new Date('2024-01-15'), 'BID');
85
+ * // Returns [7:00 AM, 7:00 PM] (twice daily)
86
+ *
87
+ * const customTimes = getMedicationTimes(new Date('2024-01-15'), 'TID', {
88
+ * wakeTime: '06:00',
89
+ * sleepTime: '21:00'
90
+ * });
91
+ * // Returns [6:00 AM, 1:30 PM, 9:00 PM] (three times daily)
92
+ * ```
93
+ */
94
+ function getMedicationTimes(date, frequency, config) {
95
+ const d = toDate(date);
96
+ const cfg = { ...exports.DEFAULT_MEDICATION_CONFIG, ...config };
97
+ const doses = exports.MEDICATION_FREQUENCIES[frequency];
98
+ if (doses === null) {
99
+ return []; // PRN - as needed
100
+ }
101
+ const wake = parseTimeString(cfg.wakeTime);
102
+ const sleep = parseTimeString(cfg.sleepTime);
103
+ // Calculate awake hours
104
+ let awakeMinutes = (sleep.hour * 60 + sleep.minute) - (wake.hour * 60 + wake.minute);
105
+ if (awakeMinutes <= 0)
106
+ awakeMinutes += 24 * 60; // Handle overnight
107
+ const times = [];
108
+ if (doses === 1) {
109
+ // QD: morning dose
110
+ times.push(setTimeOnDate(d, wake.hour, wake.minute));
111
+ }
112
+ else {
113
+ // Distribute evenly across waking hours
114
+ const interval = awakeMinutes / doses;
115
+ for (let i = 0; i < doses; i++) {
116
+ const minutesFromWake = Math.round(i * interval);
117
+ let totalMinutes = wake.hour * 60 + wake.minute + minutesFromWake;
118
+ // Handle day rollover
119
+ if (totalMinutes >= 24 * 60) {
120
+ totalMinutes -= 24 * 60;
121
+ }
122
+ const hour = Math.floor(totalMinutes / 60);
123
+ const minute = totalMinutes % 60;
124
+ times.push(setTimeOnDate(d, hour, minute));
125
+ }
126
+ }
127
+ return times;
128
+ }
129
+ /**
130
+ * Get the next medication time after a given date/time
131
+ *
132
+ * @param after - The date/time to find the next medication time after
133
+ * @param frequency - Medical frequency abbreviation
134
+ * @param config - Optional configuration
135
+ * @returns Next medication Date, or null for PRN
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const next = getNextMedicationTime(new Date('2024-01-15T10:00:00'), 'BID');
140
+ * // Returns 7:00 PM on same day (next BID dose after 10 AM)
141
+ * ```
142
+ */
143
+ function getNextMedicationTime(after, frequency, config) {
144
+ if (frequency === 'PRN') {
145
+ return null;
146
+ }
147
+ const afterDate = toDate(after);
148
+ // Check today's doses first
149
+ const todayTimes = getMedicationTimes(afterDate, frequency, config);
150
+ for (const time of todayTimes) {
151
+ if (time.getTime() > afterDate.getTime()) {
152
+ return time;
153
+ }
154
+ }
155
+ // Get first dose tomorrow
156
+ const tomorrow = new Date(afterDate.getTime());
157
+ tomorrow.setDate(tomorrow.getDate() + 1);
158
+ const tomorrowTimes = getMedicationTimes(tomorrow, frequency, config);
159
+ return tomorrowTimes[0] || null;
160
+ }
161
+ /**
162
+ * Parse a medication frequency string to MedicationFrequency type
163
+ *
164
+ * @param freq - String to parse (case-insensitive)
165
+ * @returns MedicationFrequency or null if invalid
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * parseMedicationFrequency('bid'); // 'BID'
170
+ * parseMedicationFrequency('q8h'); // 'q8h'
171
+ * parseMedicationFrequency('invalid'); // null
172
+ * ```
173
+ */
174
+ function parseMedicationFrequency(freq) {
175
+ const normalized = freq.toLowerCase();
176
+ const mapping = {
177
+ 'qd': 'QD',
178
+ 'od': 'QD',
179
+ 'once daily': 'QD',
180
+ 'bid': 'BID',
181
+ 'twice daily': 'BID',
182
+ 'tid': 'TID',
183
+ 'three times daily': 'TID',
184
+ 'qid': 'QID',
185
+ 'four times daily': 'QID',
186
+ 'q4h': 'q4h',
187
+ 'every 4 hours': 'q4h',
188
+ 'q6h': 'q6h',
189
+ 'every 6 hours': 'q6h',
190
+ 'q8h': 'q8h',
191
+ 'every 8 hours': 'q8h',
192
+ 'q12h': 'q12h',
193
+ 'every 12 hours': 'q12h',
194
+ 'prn': 'PRN',
195
+ 'as needed': 'PRN'
196
+ };
197
+ return mapping[normalized] || null;
198
+ }
199
+ // ============================================================================
200
+ // Shift Scheduling Functions
201
+ // ============================================================================
202
+ /**
203
+ * Generate shift schedule for a date range
204
+ *
205
+ * @param start - Start of range
206
+ * @param end - End of range
207
+ * @param config - Shift configuration
208
+ * @returns Array of DateRange objects representing shifts
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * const shifts = generateShiftSchedule(
213
+ * new Date('2024-01-15'),
214
+ * new Date('2024-01-17'),
215
+ * { pattern: '12hr', startTime: { hour: 7, minute: 0 } }
216
+ * );
217
+ * // Returns 4 shifts: day/night on 15th, day/night on 16th
218
+ * ```
219
+ */
220
+ function generateShiftSchedule(start, end, config) {
221
+ const startDate = toDate(start);
222
+ const endDate = toDate(end);
223
+ const shiftHours = exports.SHIFT_DURATIONS[config.pattern];
224
+ const shiftsPerDay = 24 / shiftHours;
225
+ const shifts = [];
226
+ const current = new Date(startDate.getTime());
227
+ current.setHours(config.startTime.hour, config.startTime.minute, 0, 0);
228
+ // If start time is after the input start, go back one shift cycle
229
+ if (current.getTime() > startDate.getTime()) {
230
+ current.setTime(current.getTime() - shiftHours * 60 * 60 * 1000);
231
+ }
232
+ while (current.getTime() < endDate.getTime()) {
233
+ const shiftStart = new Date(current.getTime());
234
+ const shiftEnd = new Date(current.getTime() + shiftHours * 60 * 60 * 1000);
235
+ // Only include shifts that overlap with the requested range
236
+ if (shiftEnd.getTime() > startDate.getTime() && shiftStart.getTime() < endDate.getTime()) {
237
+ shifts.push({ start: shiftStart, end: shiftEnd });
238
+ }
239
+ current.setTime(current.getTime() + shiftHours * 60 * 60 * 1000);
240
+ }
241
+ return shifts;
242
+ }
243
+ /**
244
+ * Get the shift containing a specific time
245
+ *
246
+ * @param date - The date/time to check
247
+ * @param config - Shift configuration
248
+ * @returns DateRange of the shift containing the time
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const shift = getShiftForTime(
253
+ * new Date('2024-01-15T14:00:00'),
254
+ * { pattern: '8hr', startTime: { hour: 7, minute: 0 } }
255
+ * );
256
+ * // Returns { start: 7:00 AM, end: 3:00 PM } (day shift)
257
+ * ```
258
+ */
259
+ function getShiftForTime(date, config) {
260
+ const d = toDate(date);
261
+ const shiftHours = exports.SHIFT_DURATIONS[config.pattern];
262
+ const shiftMs = shiftHours * 60 * 60 * 1000;
263
+ // Find the shift start that contains this time
264
+ const dayStart = new Date(d.getTime());
265
+ dayStart.setHours(config.startTime.hour, config.startTime.minute, 0, 0);
266
+ // Calculate how many shift cycles from dayStart
267
+ let diff = d.getTime() - dayStart.getTime();
268
+ if (diff < 0) {
269
+ // Time is before today's first shift start
270
+ diff += 24 * 60 * 60 * 1000;
271
+ dayStart.setTime(dayStart.getTime() - 24 * 60 * 60 * 1000);
272
+ }
273
+ const shiftIndex = Math.floor(diff / shiftMs);
274
+ const shiftStart = new Date(dayStart.getTime() + shiftIndex * shiftMs);
275
+ const shiftEnd = new Date(shiftStart.getTime() + shiftMs);
276
+ return { start: shiftStart, end: shiftEnd };
277
+ }
278
+ /**
279
+ * Check if a date/time is during a specific shift
280
+ *
281
+ * @param date - The date/time to check
282
+ * @param shiftStart - When the shift started
283
+ * @param config - Shift configuration
284
+ * @returns true if the time is during the shift
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * isOnShift(
289
+ * new Date('2024-01-15T10:00:00'),
290
+ * new Date('2024-01-15T07:00:00'),
291
+ * { pattern: '8hr', startTime: { hour: 7, minute: 0 } }
292
+ * ); // true (10 AM is during 7 AM - 3 PM shift)
293
+ * ```
294
+ */
295
+ function isOnShift(date, shiftStart, config) {
296
+ const d = toDate(date);
297
+ const start = toDate(shiftStart);
298
+ const shiftHours = exports.SHIFT_DURATIONS[config.pattern];
299
+ const shiftEnd = new Date(start.getTime() + shiftHours * 60 * 60 * 1000);
300
+ return d.getTime() >= start.getTime() && d.getTime() < shiftEnd.getTime();
301
+ }
302
+ // ============================================================================
303
+ // On-Call Rotation Functions
304
+ // ============================================================================
305
+ /**
306
+ * Create an on-call rotation schedule
307
+ *
308
+ * @param start - Start of rotation period
309
+ * @param end - End of rotation period
310
+ * @param staff - Array of staff names to rotate through
311
+ * @param hoursPerShift - Hours per on-call shift (default 24)
312
+ * @returns Array of OnCallSlot assignments
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * const rotation = createOnCallRotation(
317
+ * new Date('2024-01-15'),
318
+ * new Date('2024-01-18'),
319
+ * ['Dr. Smith', 'Dr. Jones', 'Dr. Brown'],
320
+ * 24
321
+ * );
322
+ * // Returns 3 slots, one per doctor per day
323
+ * ```
324
+ */
325
+ function createOnCallRotation(start, end, staff, hoursPerShift = 24) {
326
+ if (staff.length === 0) {
327
+ return [];
328
+ }
329
+ const startDate = toDate(start);
330
+ const endDate = toDate(end);
331
+ const shiftMs = hoursPerShift * 60 * 60 * 1000;
332
+ const slots = [];
333
+ const current = new Date(startDate.getTime());
334
+ current.setHours(0, 0, 0, 0); // Start at midnight
335
+ let staffIndex = 0;
336
+ while (current.getTime() < endDate.getTime()) {
337
+ const slotEnd = new Date(Math.min(current.getTime() + shiftMs, endDate.getTime()));
338
+ slots.push({
339
+ staff: staff[staffIndex % staff.length],
340
+ start: new Date(current.getTime()),
341
+ end: slotEnd
342
+ });
343
+ staffIndex++;
344
+ current.setTime(current.getTime() + shiftMs);
345
+ }
346
+ return slots;
347
+ }
348
+ /**
349
+ * Get the staff member on call at a specific time
350
+ *
351
+ * @param date - The date/time to check
352
+ * @param rotation - The on-call rotation schedule
353
+ * @returns Staff name or null if no one is on call
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * const onCall = getOnCallStaff(new Date('2024-01-16T03:00:00'), rotation);
358
+ * // Returns 'Dr. Jones' (whoever is on call at 3 AM on the 16th)
359
+ * ```
360
+ */
361
+ function getOnCallStaff(date, rotation) {
362
+ const d = toDate(date);
363
+ for (const slot of rotation) {
364
+ if (d.getTime() >= slot.start.getTime() && d.getTime() < slot.end.getTime()) {
365
+ return slot.staff;
366
+ }
367
+ }
368
+ return null;
369
+ }
370
+ // ============================================================================
371
+ // Compliance Window Functions
372
+ // ============================================================================
373
+ /**
374
+ * Check if an event occurred within its compliance window
375
+ *
376
+ * @param event - When the event occurred
377
+ * @param deadline - The compliance deadline
378
+ * @returns true if event is before or at deadline
379
+ *
380
+ * @example
381
+ * ```ts
382
+ * isWithinComplianceWindow(
383
+ * new Date('2024-01-15T10:00:00'),
384
+ * new Date('2024-01-15T12:00:00')
385
+ * ); // true (event occurred before deadline)
386
+ * ```
387
+ */
388
+ function isWithinComplianceWindow(event, deadline) {
389
+ const eventDate = toDate(event);
390
+ const deadlineDate = toDate(deadline);
391
+ return eventDate.getTime() <= deadlineDate.getTime();
392
+ }
393
+ /**
394
+ * Calculate the compliance deadline from an event
395
+ *
396
+ * @param event - The triggering event
397
+ * @param windowHours - Hours until deadline
398
+ * @returns Deadline Date
399
+ *
400
+ * @example
401
+ * ```ts
402
+ * const deadline = getComplianceDeadline(
403
+ * new Date('2024-01-15T08:00:00'),
404
+ * 72
405
+ * );
406
+ * // Returns 2024-01-18T08:00:00 (72 hours later)
407
+ * ```
408
+ */
409
+ function getComplianceDeadline(event, windowHours) {
410
+ const eventDate = toDate(event);
411
+ return new Date(eventDate.getTime() + windowHours * 60 * 60 * 1000);
412
+ }
413
+ /**
414
+ * Calculate time remaining until a compliance deadline
415
+ *
416
+ * @param event - Current time or event time
417
+ * @param deadline - The compliance deadline
418
+ * @returns Duration until deadline, or null if already past
419
+ *
420
+ * @example
421
+ * ```ts
422
+ * const remaining = timeUntilDeadline(
423
+ * new Date('2024-01-15T10:00:00'),
424
+ * new Date('2024-01-16T10:00:00')
425
+ * );
426
+ * // Returns Duration of 24 hours
427
+ * ```
428
+ */
429
+ function timeUntilDeadline(event, deadline) {
430
+ const eventDate = toDate(event);
431
+ const deadlineDate = toDate(deadline);
432
+ const diff = deadlineDate.getTime() - eventDate.getTime();
433
+ if (diff < 0) {
434
+ return null; // Already past deadline
435
+ }
436
+ return duration_js_1.Duration.fromMilliseconds(diff);
437
+ }
438
+ // ============================================================================
439
+ // Utility Functions
440
+ // ============================================================================
441
+ /**
442
+ * Calculate rest hours between two shifts
443
+ *
444
+ * @param shift1End - End of first shift
445
+ * @param shift2Start - Start of second shift
446
+ * @returns Hours of rest between shifts
447
+ *
448
+ * @example
449
+ * ```ts
450
+ * const rest = calculateRestBetweenShifts(
451
+ * new Date('2024-01-15T19:00:00'),
452
+ * new Date('2024-01-16T07:00:00')
453
+ * );
454
+ * // Returns 12 (hours of rest)
455
+ * ```
456
+ */
457
+ function calculateRestBetweenShifts(shift1End, shift2Start) {
458
+ const end = toDate(shift1End);
459
+ const start = toDate(shift2Start);
460
+ const diffMs = start.getTime() - end.getTime();
461
+ return diffMs / (60 * 60 * 1000);
462
+ }