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,512 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Finance utilities for market-aware date calculations
4
+ * Provides US market hours, trading days, settlement dates, and options expiration
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.US_MARKET_HOLIDAYS = exports.MARKET_HOURS = void 0;
8
+ exports.isMarketHoliday = isMarketHoliday;
9
+ exports.isTradingDay = isTradingDay;
10
+ exports.isMarketOpen = isMarketOpen;
11
+ exports.getMarketHours = getMarketHours;
12
+ exports.getMarketOpen = getMarketOpen;
13
+ exports.getMarketClose = getMarketClose;
14
+ exports.getNextMarketOpen = getNextMarketOpen;
15
+ exports.getNextMarketClose = getNextMarketClose;
16
+ exports.getSettlementDate = getSettlementDate;
17
+ exports.getTradeDateFromSettlement = getTradeDateFromSettlement;
18
+ exports.eachTradingDay = eachTradingDay;
19
+ exports.countTradingDays = countTradingDays;
20
+ exports.addTradingDays = addTradingDays;
21
+ exports.getOptionsExpiration = getOptionsExpiration;
22
+ /** Market hours for US exchanges */
23
+ exports.MARKET_HOURS = {
24
+ NYSE: {
25
+ open: { hour: 9, minute: 30 },
26
+ close: { hour: 16, minute: 0 },
27
+ timezone: 'America/New_York',
28
+ preMarket: { hour: 4, minute: 0 },
29
+ afterHours: { hour: 20, minute: 0 }
30
+ },
31
+ NASDAQ: {
32
+ open: { hour: 9, minute: 30 },
33
+ close: { hour: 16, minute: 0 },
34
+ timezone: 'America/New_York',
35
+ preMarket: { hour: 4, minute: 0 },
36
+ afterHours: { hour: 20, minute: 0 }
37
+ }
38
+ };
39
+ /** US market holidays (NYSE/NASDAQ follow same schedule) */
40
+ exports.US_MARKET_HOLIDAYS = [
41
+ "New Year's Day",
42
+ 'Martin Luther King Jr. Day',
43
+ "Presidents' Day",
44
+ 'Good Friday',
45
+ 'Memorial Day',
46
+ 'Juneteenth',
47
+ 'Independence Day',
48
+ 'Labor Day',
49
+ 'Thanksgiving Day',
50
+ 'Christmas Day'
51
+ ];
52
+ /**
53
+ * Helper to convert DateInput to Date
54
+ */
55
+ function toDate(input) {
56
+ if (input instanceof Date)
57
+ return new Date(input);
58
+ return new Date(input);
59
+ }
60
+ /**
61
+ * Calculate Easter Sunday using the Anonymous Gregorian algorithm
62
+ */
63
+ function getEasterSunday(year) {
64
+ const a = year % 19;
65
+ const b = Math.floor(year / 100);
66
+ const c = year % 100;
67
+ const d = Math.floor(b / 4);
68
+ const e = b % 4;
69
+ const f = Math.floor((b + 8) / 25);
70
+ const g = Math.floor((b - f + 1) / 3);
71
+ const h = (19 * a + b - d - g + 15) % 30;
72
+ const i = Math.floor(c / 4);
73
+ const k = c % 4;
74
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
75
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
76
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
77
+ const day = ((h + l - 7 * m + 114) % 31) + 1;
78
+ return new Date(year, month - 1, day);
79
+ }
80
+ /**
81
+ * Get nth occurrence of a weekday in a month
82
+ */
83
+ function getNthWeekdayOfMonth(year, month, dayOfWeek, n) {
84
+ const firstDay = new Date(year, month, 1);
85
+ const firstWeekday = firstDay.getDay();
86
+ let dayOffset = dayOfWeek - firstWeekday;
87
+ if (dayOffset < 0)
88
+ dayOffset += 7;
89
+ const date = 1 + dayOffset + (n - 1) * 7;
90
+ return new Date(year, month, date);
91
+ }
92
+ /**
93
+ * Get last occurrence of a weekday in a month
94
+ */
95
+ function getLastWeekdayOfMonth(year, month, dayOfWeek) {
96
+ const lastDay = new Date(year, month + 1, 0);
97
+ const lastDate = lastDay.getDate();
98
+ const lastWeekday = lastDay.getDay();
99
+ let diff = lastWeekday - dayOfWeek;
100
+ if (diff < 0)
101
+ diff += 7;
102
+ return new Date(year, month, lastDate - diff);
103
+ }
104
+ /**
105
+ * Adjust date if it falls on a weekend (observed holiday rules)
106
+ * - If Saturday, observe on Friday
107
+ * - If Sunday, observe on Monday
108
+ */
109
+ function adjustForWeekend(date) {
110
+ const day = date.getDay();
111
+ if (day === 0) { // Sunday -> Monday
112
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
113
+ }
114
+ else if (day === 6) { // Saturday -> Friday
115
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
116
+ }
117
+ return date;
118
+ }
119
+ /**
120
+ * Get all US market holidays for a year
121
+ */
122
+ function getUSMarketHolidays(year) {
123
+ const holidays = [];
124
+ // New Year's Day (January 1, or observed)
125
+ holidays.push(adjustForWeekend(new Date(year, 0, 1)));
126
+ // Martin Luther King Jr. Day (3rd Monday of January)
127
+ holidays.push(getNthWeekdayOfMonth(year, 0, 1, 3));
128
+ // Presidents' Day (3rd Monday of February)
129
+ holidays.push(getNthWeekdayOfMonth(year, 1, 1, 3));
130
+ // Good Friday (2 days before Easter)
131
+ const easter = getEasterSunday(year);
132
+ holidays.push(new Date(easter.getFullYear(), easter.getMonth(), easter.getDate() - 2));
133
+ // Memorial Day (last Monday of May)
134
+ holidays.push(getLastWeekdayOfMonth(year, 4, 1));
135
+ // Juneteenth (June 19, or observed)
136
+ holidays.push(adjustForWeekend(new Date(year, 5, 19)));
137
+ // Independence Day (July 4, or observed)
138
+ holidays.push(adjustForWeekend(new Date(year, 6, 4)));
139
+ // Labor Day (1st Monday of September)
140
+ holidays.push(getNthWeekdayOfMonth(year, 8, 1, 1));
141
+ // Thanksgiving Day (4th Thursday of November)
142
+ holidays.push(getNthWeekdayOfMonth(year, 10, 4, 4));
143
+ // Christmas Day (December 25, or observed)
144
+ holidays.push(adjustForWeekend(new Date(year, 11, 25)));
145
+ return holidays;
146
+ }
147
+ /**
148
+ * Compare two dates by year, month, day only (ignoring time)
149
+ */
150
+ function isSameDay(date1, date2) {
151
+ return date1.getFullYear() === date2.getFullYear() &&
152
+ date1.getMonth() === date2.getMonth() &&
153
+ date1.getDate() === date2.getDate();
154
+ }
155
+ /**
156
+ * Check if a date is a US market holiday
157
+ * @param date - Date to check
158
+ * @param market - Market (default: NYSE)
159
+ * @returns True if the date is a market holiday
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * isMarketHoliday(new Date('2024-12-25')); // true (Christmas)
164
+ * isMarketHoliday(new Date('2024-01-02')); // false
165
+ * ```
166
+ */
167
+ function isMarketHoliday(date, market = 'NYSE') {
168
+ const d = toDate(date);
169
+ const year = d.getFullYear();
170
+ const holidays = getUSMarketHolidays(year);
171
+ return holidays.some(h => isSameDay(d, h));
172
+ }
173
+ /**
174
+ * Check if a date is a trading day (weekday and not a market holiday)
175
+ * @param date - Date to check
176
+ * @param market - Market (default: NYSE)
177
+ * @returns True if the date is a trading day
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * isTradingDay(new Date('2024-01-15')); // true (Monday)
182
+ * isTradingDay(new Date('2024-01-13')); // false (Saturday)
183
+ * ```
184
+ */
185
+ function isTradingDay(date, market = 'NYSE') {
186
+ const d = toDate(date);
187
+ const day = d.getDay();
188
+ // Weekend check
189
+ if (day === 0 || day === 6)
190
+ return false;
191
+ // Holiday check
192
+ return !isMarketHoliday(d, market);
193
+ }
194
+ /**
195
+ * Check if the market is currently open
196
+ * @param date - Date/time to check
197
+ * @param market - Market (default: NYSE)
198
+ * @returns True if market is open at the specified time
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * // Check if NYSE is open now
203
+ * isMarketOpen(new Date(), 'NYSE');
204
+ *
205
+ * // Check specific time
206
+ * isMarketOpen(new Date('2024-01-15T10:30:00-05:00')); // true
207
+ * ```
208
+ */
209
+ function isMarketOpen(date, market = 'NYSE') {
210
+ const d = toDate(date);
211
+ // First check if it's a trading day
212
+ if (!isTradingDay(d, market))
213
+ return false;
214
+ const hours = exports.MARKET_HOURS[market];
215
+ // Convert to market timezone for comparison
216
+ // For simplicity, we assume the input is already in market timezone
217
+ // or we compare hours directly
218
+ const hour = d.getHours();
219
+ const minute = d.getMinutes();
220
+ const timeInMinutes = hour * 60 + minute;
221
+ const openInMinutes = hours.open.hour * 60 + hours.open.minute;
222
+ const closeInMinutes = hours.close.hour * 60 + hours.close.minute;
223
+ return timeInMinutes >= openInMinutes && timeInMinutes < closeInMinutes;
224
+ }
225
+ /**
226
+ * Get market hours configuration
227
+ * @param market - Market (default: NYSE)
228
+ * @returns Market hours configuration (deep copy)
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * const hours = getMarketHours('NASDAQ');
233
+ * console.log(hours.open); // { hour: 9, minute: 30 }
234
+ * ```
235
+ */
236
+ function getMarketHours(market = 'NYSE') {
237
+ const source = exports.MARKET_HOURS[market];
238
+ return {
239
+ open: { ...source.open },
240
+ close: { ...source.close },
241
+ timezone: source.timezone,
242
+ preMarket: source.preMarket ? { ...source.preMarket } : undefined,
243
+ afterHours: source.afterHours ? { ...source.afterHours } : undefined
244
+ };
245
+ }
246
+ /**
247
+ * Get market open time for a specific date
248
+ * @param date - Date to get market open for
249
+ * @param market - Market (default: NYSE)
250
+ * @returns Date set to market open time
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * const open = getMarketOpen(new Date('2024-01-15'));
255
+ * console.log(open); // 2024-01-15T09:30:00
256
+ * ```
257
+ */
258
+ function getMarketOpen(date, market = 'NYSE') {
259
+ const d = toDate(date);
260
+ const hours = exports.MARKET_HOURS[market];
261
+ const result = new Date(d);
262
+ result.setHours(hours.open.hour, hours.open.minute, 0, 0);
263
+ return result;
264
+ }
265
+ /**
266
+ * Get market close time for a specific date
267
+ * @param date - Date to get market close for
268
+ * @param market - Market (default: NYSE)
269
+ * @returns Date set to market close time
270
+ *
271
+ * @example
272
+ * ```ts
273
+ * const close = getMarketClose(new Date('2024-01-15'));
274
+ * console.log(close); // 2024-01-15T16:00:00
275
+ * ```
276
+ */
277
+ function getMarketClose(date, market = 'NYSE') {
278
+ const d = toDate(date);
279
+ const hours = exports.MARKET_HOURS[market];
280
+ const result = new Date(d);
281
+ result.setHours(hours.close.hour, hours.close.minute, 0, 0);
282
+ return result;
283
+ }
284
+ /**
285
+ * Get next market open time after a given date
286
+ * @param after - Start searching after this date
287
+ * @param market - Market (default: NYSE)
288
+ * @returns Next market open date/time
289
+ *
290
+ * @example
291
+ * ```ts
292
+ * // If it's Friday evening, returns Monday 9:30 AM
293
+ * const nextOpen = getNextMarketOpen(new Date('2024-01-12T17:00:00'));
294
+ * ```
295
+ */
296
+ function getNextMarketOpen(after, market = 'NYSE') {
297
+ const d = toDate(after);
298
+ const hours = exports.MARKET_HOURS[market];
299
+ // Start with current day's open
300
+ let candidate = getMarketOpen(d, market);
301
+ // If we're past today's open, start from tomorrow
302
+ if (d >= candidate) {
303
+ candidate.setDate(candidate.getDate() + 1);
304
+ }
305
+ // Find next trading day
306
+ while (!isTradingDay(candidate, market)) {
307
+ candidate.setDate(candidate.getDate() + 1);
308
+ }
309
+ // Set to market open time
310
+ candidate.setHours(hours.open.hour, hours.open.minute, 0, 0);
311
+ return candidate;
312
+ }
313
+ /**
314
+ * Get next market close time after a given date
315
+ * @param after - Start searching after this date
316
+ * @param market - Market (default: NYSE)
317
+ * @returns Next market close date/time
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * const nextClose = getNextMarketClose(new Date('2024-01-15T10:00:00'));
322
+ * // Returns 2024-01-15T16:00:00 (same day close)
323
+ * ```
324
+ */
325
+ function getNextMarketClose(after, market = 'NYSE') {
326
+ const d = toDate(after);
327
+ const hours = exports.MARKET_HOURS[market];
328
+ // Start with current day's close
329
+ let candidate = getMarketClose(d, market);
330
+ // If we're past today's close or not a trading day, go to next trading day
331
+ if (d >= candidate || !isTradingDay(d, market)) {
332
+ candidate.setDate(candidate.getDate() + 1);
333
+ while (!isTradingDay(candidate, market)) {
334
+ candidate.setDate(candidate.getDate() + 1);
335
+ }
336
+ }
337
+ // Set to market close time
338
+ candidate.setHours(hours.close.hour, hours.close.minute, 0, 0);
339
+ return candidate;
340
+ }
341
+ /**
342
+ * Calculate settlement date (T+N) from trade date
343
+ * @param tradeDate - Trade date
344
+ * @param days - Number of business days for settlement (e.g., 1 for T+1, 2 for T+2)
345
+ * @param market - Market (default: NYSE)
346
+ * @returns Settlement date
347
+ *
348
+ * @example
349
+ * ```ts
350
+ * // T+2 settlement
351
+ * const settlement = getSettlementDate(new Date('2024-01-15'), 2);
352
+ * // Returns 2024-01-17 (skipping weekends/holidays)
353
+ * ```
354
+ */
355
+ function getSettlementDate(tradeDate, days, market = 'NYSE') {
356
+ const d = toDate(tradeDate);
357
+ const result = new Date(d);
358
+ result.setHours(0, 0, 0, 0);
359
+ let remaining = days;
360
+ while (remaining > 0) {
361
+ result.setDate(result.getDate() + 1);
362
+ if (isTradingDay(result, market)) {
363
+ remaining--;
364
+ }
365
+ }
366
+ return result;
367
+ }
368
+ /**
369
+ * Calculate trade date from settlement date (reverse T+N)
370
+ * @param settlementDate - Settlement date
371
+ * @param days - Number of business days for settlement
372
+ * @param market - Market (default: NYSE)
373
+ * @returns Trade date
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * const tradeDate = getTradeDateFromSettlement(new Date('2024-01-17'), 2);
378
+ * // Returns 2024-01-15
379
+ * ```
380
+ */
381
+ function getTradeDateFromSettlement(settlementDate, days, market = 'NYSE') {
382
+ const d = toDate(settlementDate);
383
+ const result = new Date(d);
384
+ result.setHours(0, 0, 0, 0);
385
+ let remaining = days;
386
+ while (remaining > 0) {
387
+ result.setDate(result.getDate() - 1);
388
+ if (isTradingDay(result, market)) {
389
+ remaining--;
390
+ }
391
+ }
392
+ return result;
393
+ }
394
+ /**
395
+ * Iterate through each trading day in a range
396
+ * @param start - Start date
397
+ * @param end - End date
398
+ * @param market - Market (default: NYSE)
399
+ * @returns Array of trading days
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * const days = eachTradingDay(new Date('2024-01-15'), new Date('2024-01-19'));
404
+ * // Returns Mon, Tue, Wed, Thu, Fri (if no holidays)
405
+ * ```
406
+ */
407
+ function eachTradingDay(start, end, market = 'NYSE') {
408
+ const startDate = toDate(start);
409
+ const endDate = toDate(end);
410
+ startDate.setHours(0, 0, 0, 0);
411
+ endDate.setHours(0, 0, 0, 0);
412
+ const days = [];
413
+ const current = new Date(startDate);
414
+ while (current <= endDate) {
415
+ if (isTradingDay(current, market)) {
416
+ days.push(new Date(current));
417
+ }
418
+ current.setDate(current.getDate() + 1);
419
+ }
420
+ return days;
421
+ }
422
+ /**
423
+ * Count trading days between two dates
424
+ * @param start - Start date
425
+ * @param end - End date
426
+ * @param market - Market (default: NYSE)
427
+ * @returns Number of trading days
428
+ *
429
+ * @example
430
+ * ```ts
431
+ * const count = countTradingDays(new Date('2024-01-15'), new Date('2024-01-19'));
432
+ * // Returns 5 (Mon-Fri if no holidays)
433
+ * ```
434
+ */
435
+ function countTradingDays(start, end, market = 'NYSE') {
436
+ return eachTradingDay(start, end, market).length;
437
+ }
438
+ /**
439
+ * Add trading days to a date
440
+ * @param date - Start date
441
+ * @param days - Number of trading days to add (can be negative)
442
+ * @param market - Market (default: NYSE)
443
+ * @returns Resulting date
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * const result = addTradingDays(new Date('2024-01-15'), 5);
448
+ * // Returns 5 trading days later
449
+ * ```
450
+ */
451
+ function addTradingDays(date, days, market = 'NYSE') {
452
+ const d = toDate(date);
453
+ const result = new Date(d);
454
+ result.setHours(0, 0, 0, 0);
455
+ if (days === 0)
456
+ return result;
457
+ const direction = days > 0 ? 1 : -1;
458
+ let remaining = Math.abs(days);
459
+ while (remaining > 0) {
460
+ result.setDate(result.getDate() + direction);
461
+ if (isTradingDay(result, market)) {
462
+ remaining--;
463
+ }
464
+ }
465
+ return result;
466
+ }
467
+ /**
468
+ * Get options expiration date
469
+ * @param year - Year
470
+ * @param month - Month (1-12)
471
+ * @param type - Expiration type (default: 'monthly')
472
+ * @returns Options expiration date
473
+ *
474
+ * @example
475
+ * ```ts
476
+ * // Monthly options expire on 3rd Friday
477
+ * const exp = getOptionsExpiration(2024, 1, 'monthly');
478
+ *
479
+ * // Weekly options expire every Friday
480
+ * const weekly = getOptionsExpiration(2024, 1, 'weekly');
481
+ *
482
+ * // Quarterly options expire on 3rd Friday of Mar, Jun, Sep, Dec
483
+ * const quarterly = getOptionsExpiration(2024, 3, 'quarterly');
484
+ * ```
485
+ */
486
+ function getOptionsExpiration(year, month, type = 'monthly') {
487
+ // Adjust month to 0-indexed
488
+ const monthIndex = month - 1;
489
+ switch (type) {
490
+ case 'monthly':
491
+ case 'quarterly': {
492
+ // 3rd Friday of the month
493
+ const thirdFriday = getNthWeekdayOfMonth(year, monthIndex, 5, 3);
494
+ // If it's a holiday, move to Thursday
495
+ if (isMarketHoliday(thirdFriday)) {
496
+ thirdFriday.setDate(thirdFriday.getDate() - 1);
497
+ }
498
+ return thirdFriday;
499
+ }
500
+ case 'weekly': {
501
+ // First Friday of the month for weekly
502
+ const firstFriday = getNthWeekdayOfMonth(year, monthIndex, 5, 1);
503
+ // If it's a holiday, move to Thursday
504
+ if (isMarketHoliday(firstFriday)) {
505
+ firstFriday.setDate(firstFriday.getDate() - 1);
506
+ }
507
+ return firstFriday;
508
+ }
509
+ default:
510
+ return getNthWeekdayOfMonth(year, monthIndex, 5, 3);
511
+ }
512
+ }