@dereekb/date 13.1.0 → 13.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  var util = require('@dereekb/util');
4
4
  var dateFns = require('date-fns');
5
- var arktype = require('arktype');
6
5
  var dateFnsTz = require('date-fns-tz');
6
+ var arktype = require('arktype');
7
7
  var tzdb = require('@vvo/tzdb');
8
8
  var rxjs = require('rxjs');
9
9
  var rrule = require('rrule');
@@ -687,13 +687,6 @@ function isDateRangeStart(value) {
687
687
  * ```
688
688
  */
689
689
  const sortDateRangeStartAscendingCompareFunction = sortByDateFunction((x) => x.start);
690
- /**
691
- * ArkType schema for {@link DateRange}.
692
- */
693
- const dateRangeType = arktype.type({
694
- start: 'Date',
695
- end: 'Date'
696
- });
697
690
  /**
698
691
  * Counts the total number of calendar days spanned by the range, inclusive of both endpoints.
699
692
  * Always returns at least 1, even for same-day ranges.
@@ -869,14 +862,6 @@ exports.DateRangeType = void 0;
869
862
  */
870
863
  DateRangeType["CALENDAR_MONTH"] = "calendar_month";
871
864
  })(exports.DateRangeType || (exports.DateRangeType = {}));
872
- /**
873
- * ArkType schema for {@link DateRangeParams}.
874
- */
875
- const dateRangeParamsType = arktype.type({
876
- type: arktype.type.enumerated(...Object.values(exports.DateRangeType)),
877
- date: 'Date',
878
- 'distance?': 'number'
879
- });
880
865
  /**
881
866
  * Creates a {@link DateRange} from the given type and optional parameters. Supports many range
882
867
  * strategies including fixed periods (day, week, month), directional ranges, and radii.
@@ -1473,75 +1458,6 @@ function getDaysOfWeekInDateRange(dateRange) {
1473
1458
  return days;
1474
1459
  }
1475
1460
 
1476
- /**
1477
- * ArkType schema for {@link DateDurationSpan}.
1478
- */
1479
- const dateDurationSpanType = arktype.type({
1480
- startsAt: 'Date',
1481
- duration: 'number >= 0'
1482
- });
1483
- /**
1484
- * Computes the end date for a duration span by adding the duration to the start time.
1485
- *
1486
- * @param span - the duration span to compute the end for
1487
- * @returns the date when the span ends
1488
- *
1489
- * @example
1490
- * ```ts
1491
- * const span = { startsAt: new Date('2024-01-01T10:00:00Z'), duration: 60 };
1492
- * dateDurationSpanEndDate(span); // 2024-01-01T11:00:00Z
1493
- * ```
1494
- */
1495
- function dateDurationSpanEndDate(span) {
1496
- return dateFns.addMinutes(span.startsAt, span.duration);
1497
- }
1498
- /**
1499
- * Converts a {@link DateDurationSpan} to a {@link DateRange} with start and end dates.
1500
- *
1501
- * @param span - the duration span to convert
1502
- * @returns a date range from startsAt to startsAt + duration
1503
- */
1504
- function durationSpanToDateRange(span) {
1505
- return {
1506
- start: span.startsAt,
1507
- end: dateFns.addMinutes(span.startsAt, span.duration)
1508
- };
1509
- }
1510
- /**
1511
- * Creates a {@link DateDurationSpan} from a {@link DateRange} by computing the duration in minutes between start and end.
1512
- *
1513
- * @param dateRange - the date range to convert
1514
- * @returns a duration span with the range's start as startsAt
1515
- */
1516
- function durationSpanFromDateRange(dateRange) {
1517
- return {
1518
- startsAt: dateRange.start,
1519
- duration: dateFns.differenceInMinutes(dateRange.end, dateRange.start)
1520
- };
1521
- }
1522
- /**
1523
- * Determines whether a duration span is in the past, present, or future relative to the given time.
1524
- *
1525
- * @param span - the duration span to check
1526
- * @param now - reference time (defaults to current time)
1527
- * @returns 'past', 'present', or 'future'
1528
- */
1529
- function durationSpanDateRelativeState(span, now) {
1530
- return dateRangeRelativeState(durationSpanToDateRange(span), now);
1531
- }
1532
- /**
1533
- * Converts a duration span's duration from minutes to fractional hours.
1534
- *
1535
- * @param span - the duration span to measure
1536
- * @returns the duration expressed as fractional hours (e.g. 90 minutes = 1.5)
1537
- */
1538
- function fractionalHoursInDurationSpan(span) {
1539
- return util.minutesToFractionalHours(span.duration);
1540
- }
1541
- function isSameDurationSpan(a, b) {
1542
- return util.safeCompareEquality(a, b, (a, b) => a.duration === b.duration && isSameDate(a.startsAt, b.startsAt));
1543
- }
1544
-
1545
1461
  /**
1546
1462
  * String code for the start of the current day.
1547
1463
  */
@@ -2453,158 +2369,6 @@ function copyHoursAndMinutesFromDateWithTimezoneNormal(input, copyFrom, timezone
2453
2369
  return result;
2454
2370
  }
2455
2371
 
2456
- /**
2457
- * Returns all recognized IANA timezone strings, including the explicit UTC entry.
2458
- *
2459
- * @example
2460
- * ```ts
2461
- * const zones = allTimezoneStrings();
2462
- * // ['Africa/Abidjan', ..., 'UTC']
2463
- * ```
2464
- */
2465
- function allTimezoneStrings() {
2466
- return tzdb.timeZonesNames.concat(util.UTC_TIMEZONE_STRING);
2467
- }
2468
- /**
2469
- * Lazily-computed set of all known timezone strings for O(1) membership checks.
2470
- *
2471
- * @example
2472
- * ```ts
2473
- * allKnownTimezoneStrings().has('America/New_York'); // true
2474
- * ```
2475
- */
2476
- const allKnownTimezoneStrings = util.cachedGetter(() => {
2477
- return new Set(allTimezoneStrings());
2478
- });
2479
- /**
2480
- * Lazily-computed array of {@link TimezoneInfo} for every known timezone.
2481
- *
2482
- * Abbreviations are resolved at the time of first access, so results reflect
2483
- * the DST state at that moment.
2484
- */
2485
- const allTimezoneInfos = util.cachedGetter(() => {
2486
- const now = new Date();
2487
- return allTimezoneStrings().map((x) => timezoneStringToTimezoneInfo(x, now));
2488
- });
2489
- /**
2490
- * Returns the {@link TimezoneInfo} for the current system timezone, falling back to UTC.
2491
- *
2492
- * @example
2493
- * ```ts
2494
- * const info = timezoneInfoForSystem();
2495
- * console.log(info.abbreviation); // e.g., 'CST'
2496
- * ```
2497
- */
2498
- function timezoneInfoForSystem() {
2499
- return timezoneStringToTimezoneInfo(guessCurrentTimezone() ?? util.UTC_TIMEZONE_STRING);
2500
- }
2501
- /**
2502
- * Returns the short abbreviation (e.g., `"EST"`, `"PDT"`) for the given timezone at the specified date.
2503
- *
2504
- * The date matters because abbreviations change with DST transitions.
2505
- * Returns `"UKNOWN"` if no timezone is provided.
2506
- *
2507
- * @example
2508
- * ```ts
2509
- * getTimezoneAbbreviation('America/New_York'); // 'EST' or 'EDT'
2510
- * ```
2511
- */
2512
- function getTimezoneAbbreviation(timezone, date = new Date()) {
2513
- return timezone === util.UTC_TIMEZONE_STRING ? util.UTC_TIMEZONE_STRING : timezone ? dateFnsTz.formatInTimeZone(date, timezone, 'zzz') : 'UKNOWN';
2514
- }
2515
- /**
2516
- * Returns the full display name (e.g., `"Eastern Standard Time"`) for the given timezone.
2517
- *
2518
- * Returns `"Unknown Timezone"` if no timezone is provided.
2519
- *
2520
- * @example
2521
- * ```ts
2522
- * getTimezoneLongName('America/New_York'); // 'Eastern Standard Time'
2523
- * ```
2524
- */
2525
- function getTimezoneLongName(timezone, date = new Date()) {
2526
- return timezone ? dateFnsTz.formatInTimeZone(date, timezone, 'zzzz') : 'Unknown Timezone';
2527
- }
2528
- /**
2529
- * Builds a {@link TimezoneInfo} for the given timezone, computing abbreviation and search variants.
2530
- *
2531
- * @example
2532
- * ```ts
2533
- * const info = timezoneStringToTimezoneInfo('America/Chicago');
2534
- * // info.abbreviation => 'CST' or 'CDT'
2535
- * // info.search => 'america chicago'
2536
- * ```
2537
- */
2538
- function timezoneStringToTimezoneInfo(timezone, date = new Date()) {
2539
- const abbreviation = getTimezoneAbbreviation(timezone, date);
2540
- const result = {
2541
- timezone,
2542
- search: timezoneStringToSearchableString(timezone),
2543
- lowercase: timezone.toLowerCase(),
2544
- abbreviation,
2545
- lowercaseAbbreviation: abbreviation.toLowerCase()
2546
- };
2547
- return result;
2548
- }
2549
- /**
2550
- * Filters timezone infos by a search string, matching against the searchable name,
2551
- * lowercase identifier, and abbreviation.
2552
- *
2553
- * For queries longer than 2 characters, substring matching on the searchable name is also used.
2554
- *
2555
- * @example
2556
- * ```ts
2557
- * const results = searchTimezoneInfos('eastern', allTimezoneInfos());
2558
- * ```
2559
- */
2560
- function searchTimezoneInfos(search, infos) {
2561
- const searchString = search.toLocaleLowerCase();
2562
- return infos.filter((x) => (search.length > 2 && x.search.includes(searchString)) || x.lowercase.startsWith(searchString) || x.lowercaseAbbreviation.startsWith(searchString) || x.abbreviation.includes(search) || x.search === x.timezone);
2563
- }
2564
- const timezoneStringToSearchableStringReplace = util.replaceStringsFunction({
2565
- replace: ['/', '_'],
2566
- replaceWith: ' '
2567
- });
2568
- /**
2569
- * Converts a timezone identifier into a lowercase, space-separated string for search indexing.
2570
- *
2571
- * Replaces `/` and `_` with spaces (e.g., `"America/New_York"` becomes `"america new york"`).
2572
- *
2573
- * @example
2574
- * ```ts
2575
- * timezoneStringToSearchableString('America/New_York'); // 'america new york'
2576
- * ```
2577
- */
2578
- function timezoneStringToSearchableString(timezone) {
2579
- return timezoneStringToSearchableStringReplace(timezone.toLocaleLowerCase());
2580
- }
2581
- /**
2582
- * Checks whether the input string is a recognized IANA timezone identifier.
2583
- *
2584
- * Uses the cached set from {@link allKnownTimezoneStrings} for O(1) lookup.
2585
- *
2586
- * @example
2587
- * ```ts
2588
- * isKnownTimezone('America/New_York'); // true
2589
- * isKnownTimezone('Mars/Olympus'); // false
2590
- * ```
2591
- */
2592
- function isKnownTimezone(input) {
2593
- return allKnownTimezoneStrings().has(input);
2594
- }
2595
-
2596
- /**
2597
- * ArkType schema that validates a string is a recognized IANA timezone.
2598
- *
2599
- * Delegates to {@link isKnownTimezone} for the actual check.
2600
- *
2601
- * @example
2602
- * ```ts
2603
- * const result = knownTimezoneType('America/Denver');
2604
- * ```
2605
- */
2606
- const knownTimezoneType = arktype.type('string > 0').narrow((val, ctx) => isKnownTimezone(val) || ctx.mustBe('a known timezone'));
2607
-
2608
2372
  /**
2609
2373
  * Creates a {@link FitDateRangeToDayPeriodFunction} that collapses a multi-day date range into a single-day period within the given timezone.
2610
2374
  *
@@ -3145,12 +2909,6 @@ function parseISO8601DayStringToDate(dayString) {
3145
2909
  function isValidDateCellIndex(input) {
3146
2910
  return input >= 0 && Number.isInteger(input);
3147
2911
  }
3148
- /**
3149
- * ArkType schema for {@link DateCell}.
3150
- */
3151
- const dateCellType = arktype.type({
3152
- i: 'number.integer >= 0'
3153
- });
3154
2912
  /**
3155
2913
  * Normalizes a number or {@link DateCell} to a DateCell object.
3156
2914
  *
@@ -3191,13 +2949,6 @@ function dateCellTimingStartsAtForStartOfDay(input = {}) {
3191
2949
  timezone
3192
2950
  };
3193
2951
  }
3194
- /**
3195
- * ArkType schema for {@link DateCellTiming}.
3196
- */
3197
- const dateCellTimingType = dateDurationSpanType.merge({
3198
- end: 'Date',
3199
- timezone: knownTimezoneType
3200
- });
3201
2952
  /**
3202
2953
  * Creates a {@link DateTimezoneUtcNormalInstance} from the input, guaranteeing that a timezone string is configured.
3203
2954
  *
@@ -3783,12 +3534,6 @@ function isValidFullDateCellTiming(timing) {
3783
3534
  return isValid;
3784
3535
  }
3785
3536
 
3786
- /**
3787
- * ArkType schema for {@link DateCellRange}.
3788
- */
3789
- const dateCellRangeType = dateCellType.merge({
3790
- 'to?': 'number.integer >= 0'
3791
- });
3792
3537
  /**
3793
3538
  * Returns true if the input is a DateCellRange.
3794
3539
  *
@@ -5242,19 +4987,81 @@ function isDateWithinDateCellRangeFunction(config) {
5242
4987
  }
5243
4988
 
5244
4989
  /**
5245
- * Creates a filter that passes date cell duration spans whose start time is at or before the given reference time.
5246
- *
5247
- * Useful for identifying events or blocks that have already begun relative to a point in time.
4990
+ * Computes the end date for a duration span by adding the duration to the start time.
5248
4991
  *
5249
- * @param now - Reference time to compare against. Defaults to the current time.
4992
+ * @param span - the duration span to compute the end for
4993
+ * @returns the date when the span ends
5250
4994
  *
5251
4995
  * @example
5252
4996
  * ```ts
5253
- * const hasStarted = dateCellDurationSpanHasStartedFilterFunction(new Date());
5254
- * const startedSpans = allSpans.filter(hasStarted);
4997
+ * const span = { startsAt: new Date('2024-01-01T10:00:00Z'), duration: 60 };
4998
+ * dateDurationSpanEndDate(span); // 2024-01-01T11:00:00Z
5255
4999
  * ```
5256
5000
  */
5257
- function dateCellDurationSpanHasStartedFilterFunction(now = new Date()) {
5001
+ function dateDurationSpanEndDate(span) {
5002
+ return dateFns.addMinutes(span.startsAt, span.duration);
5003
+ }
5004
+ /**
5005
+ * Converts a {@link DateDurationSpan} to a {@link DateRange} with start and end dates.
5006
+ *
5007
+ * @param span - the duration span to convert
5008
+ * @returns a date range from startsAt to startsAt + duration
5009
+ */
5010
+ function durationSpanToDateRange(span) {
5011
+ return {
5012
+ start: span.startsAt,
5013
+ end: dateFns.addMinutes(span.startsAt, span.duration)
5014
+ };
5015
+ }
5016
+ /**
5017
+ * Creates a {@link DateDurationSpan} from a {@link DateRange} by computing the duration in minutes between start and end.
5018
+ *
5019
+ * @param dateRange - the date range to convert
5020
+ * @returns a duration span with the range's start as startsAt
5021
+ */
5022
+ function durationSpanFromDateRange(dateRange) {
5023
+ return {
5024
+ startsAt: dateRange.start,
5025
+ duration: dateFns.differenceInMinutes(dateRange.end, dateRange.start)
5026
+ };
5027
+ }
5028
+ /**
5029
+ * Determines whether a duration span is in the past, present, or future relative to the given time.
5030
+ *
5031
+ * @param span - the duration span to check
5032
+ * @param now - reference time (defaults to current time)
5033
+ * @returns 'past', 'present', or 'future'
5034
+ */
5035
+ function durationSpanDateRelativeState(span, now) {
5036
+ return dateRangeRelativeState(durationSpanToDateRange(span), now);
5037
+ }
5038
+ /**
5039
+ * Converts a duration span's duration from minutes to fractional hours.
5040
+ *
5041
+ * @param span - the duration span to measure
5042
+ * @returns the duration expressed as fractional hours (e.g. 90 minutes = 1.5)
5043
+ */
5044
+ function fractionalHoursInDurationSpan(span) {
5045
+ return util.minutesToFractionalHours(span.duration);
5046
+ }
5047
+ function isSameDurationSpan(a, b) {
5048
+ return util.safeCompareEquality(a, b, (a, b) => a.duration === b.duration && isSameDate(a.startsAt, b.startsAt));
5049
+ }
5050
+
5051
+ /**
5052
+ * Creates a filter that passes date cell duration spans whose start time is at or before the given reference time.
5053
+ *
5054
+ * Useful for identifying events or blocks that have already begun relative to a point in time.
5055
+ *
5056
+ * @param now - Reference time to compare against. Defaults to the current time.
5057
+ *
5058
+ * @example
5059
+ * ```ts
5060
+ * const hasStarted = dateCellDurationSpanHasStartedFilterFunction(new Date());
5061
+ * const startedSpans = allSpans.filter(hasStarted);
5062
+ * ```
5063
+ */
5064
+ function dateCellDurationSpanHasStartedFilterFunction(now = new Date()) {
5258
5065
  return (x) => !dateFns.isAfter(x.startsAt, now); // startsAt <= now
5259
5066
  }
5260
5067
  /**
@@ -6060,14 +5867,6 @@ function isSameDateCellSchedule(a, b) {
6060
5867
  return a == b;
6061
5868
  }
6062
5869
  }
6063
- /**
6064
- * ArkType schema for {@link DateCellSchedule}.
6065
- */
6066
- const dateCellScheduleType = arktype.type({
6067
- w: [DATE_CELL_SCHEDULE_ENCODED_WEEK_REGEX, '&', 'string'],
6068
- 'd?': 'number.integer >= 0 []',
6069
- 'ex?': 'number.integer >= 0 []'
6070
- });
6071
5870
  /**
6072
5871
  * Returns true if the input is possibly a DateCellScheduleDateRange (has schedule fields and valid start/end dates).
6073
5872
  *
@@ -6577,17 +6376,144 @@ function dateCellIndexsForDateCellScheduleDayCodes(sundayIndex, dayCodes) {
6577
6376
  }
6578
6377
 
6579
6378
  /**
6580
- * ArkType schema that validates a value is a valid {@link DateCellTiming}.
6379
+ * Returns all recognized IANA timezone strings, including the explicit UTC entry.
6380
+ *
6381
+ * @example
6382
+ * ```ts
6383
+ * const zones = allTimezoneStrings();
6384
+ * // ['Africa/Abidjan', ..., 'UTC']
6385
+ * ```
6386
+ */
6387
+ function allTimezoneStrings() {
6388
+ return tzdb.timeZonesNames.concat(util.UTC_TIMEZONE_STRING);
6389
+ }
6390
+ /**
6391
+ * Lazily-computed set of all known timezone strings for O(1) membership checks.
6392
+ *
6393
+ * @example
6394
+ * ```ts
6395
+ * allKnownTimezoneStrings().has('America/New_York'); // true
6396
+ * ```
6397
+ */
6398
+ const allKnownTimezoneStrings = util.cachedGetter(() => {
6399
+ return new Set(allTimezoneStrings());
6400
+ });
6401
+ /**
6402
+ * Lazily-computed array of {@link TimezoneInfo} for every known timezone.
6403
+ *
6404
+ * Abbreviations are resolved at the time of first access, so results reflect
6405
+ * the DST state at that moment.
6406
+ */
6407
+ const allTimezoneInfos = util.cachedGetter(() => {
6408
+ const now = new Date();
6409
+ return allTimezoneStrings().map((x) => timezoneStringToTimezoneInfo(x, now));
6410
+ });
6411
+ /**
6412
+ * Returns the {@link TimezoneInfo} for the current system timezone, falling back to UTC.
6413
+ *
6414
+ * @example
6415
+ * ```ts
6416
+ * const info = timezoneInfoForSystem();
6417
+ * console.log(info.abbreviation); // e.g., 'CST'
6418
+ * ```
6419
+ */
6420
+ function timezoneInfoForSystem() {
6421
+ return timezoneStringToTimezoneInfo(guessCurrentTimezone() ?? util.UTC_TIMEZONE_STRING);
6422
+ }
6423
+ /**
6424
+ * Returns the short abbreviation (e.g., `"EST"`, `"PDT"`) for the given timezone at the specified date.
6425
+ *
6426
+ * The date matters because abbreviations change with DST transitions.
6427
+ * Returns `"UKNOWN"` if no timezone is provided.
6428
+ *
6429
+ * @example
6430
+ * ```ts
6431
+ * getTimezoneAbbreviation('America/New_York'); // 'EST' or 'EDT'
6432
+ * ```
6433
+ */
6434
+ function getTimezoneAbbreviation(timezone, date = new Date()) {
6435
+ return timezone === util.UTC_TIMEZONE_STRING ? util.UTC_TIMEZONE_STRING : timezone ? dateFnsTz.formatInTimeZone(date, timezone, 'zzz') : 'UKNOWN';
6436
+ }
6437
+ /**
6438
+ * Returns the full display name (e.g., `"Eastern Standard Time"`) for the given timezone.
6439
+ *
6440
+ * Returns `"Unknown Timezone"` if no timezone is provided.
6441
+ *
6442
+ * @example
6443
+ * ```ts
6444
+ * getTimezoneLongName('America/New_York'); // 'Eastern Standard Time'
6445
+ * ```
6446
+ */
6447
+ function getTimezoneLongName(timezone, date = new Date()) {
6448
+ return timezone ? dateFnsTz.formatInTimeZone(date, timezone, 'zzzz') : 'Unknown Timezone';
6449
+ }
6450
+ /**
6451
+ * Builds a {@link TimezoneInfo} for the given timezone, computing abbreviation and search variants.
6452
+ *
6453
+ * @example
6454
+ * ```ts
6455
+ * const info = timezoneStringToTimezoneInfo('America/Chicago');
6456
+ * // info.abbreviation => 'CST' or 'CDT'
6457
+ * // info.search => 'america chicago'
6458
+ * ```
6459
+ */
6460
+ function timezoneStringToTimezoneInfo(timezone, date = new Date()) {
6461
+ const abbreviation = getTimezoneAbbreviation(timezone, date);
6462
+ const result = {
6463
+ timezone,
6464
+ search: timezoneStringToSearchableString(timezone),
6465
+ lowercase: timezone.toLowerCase(),
6466
+ abbreviation,
6467
+ lowercaseAbbreviation: abbreviation.toLowerCase()
6468
+ };
6469
+ return result;
6470
+ }
6471
+ /**
6472
+ * Filters timezone infos by a search string, matching against the searchable name,
6473
+ * lowercase identifier, and abbreviation.
6474
+ *
6475
+ * For queries longer than 2 characters, substring matching on the searchable name is also used.
6476
+ *
6477
+ * @example
6478
+ * ```ts
6479
+ * const results = searchTimezoneInfos('eastern', allTimezoneInfos());
6480
+ * ```
6581
6481
  */
6582
- const validDateCellTimingType = arktype.type('unknown').narrow((val, ctx) => isValidDateCellTiming(val) || ctx.mustBe('a valid DateCellTiming'));
6482
+ function searchTimezoneInfos(search, infos) {
6483
+ const searchString = search.toLocaleLowerCase();
6484
+ return infos.filter((x) => (search.length > 2 && x.search.includes(searchString)) || x.lowercase.startsWith(searchString) || x.lowercaseAbbreviation.startsWith(searchString) || x.abbreviation.includes(search) || x.search === x.timezone);
6485
+ }
6486
+ const timezoneStringToSearchableStringReplace = util.replaceStringsFunction({
6487
+ replace: ['/', '_'],
6488
+ replaceWith: ' '
6489
+ });
6583
6490
  /**
6584
- * ArkType schema that validates a value is a valid {@link DateCellRange} (non-negative indexes, `to >= i`).
6491
+ * Converts a timezone identifier into a lowercase, space-separated string for search indexing.
6492
+ *
6493
+ * Replaces `/` and `_` with spaces (e.g., `"America/New_York"` becomes `"america new york"`).
6494
+ *
6495
+ * @example
6496
+ * ```ts
6497
+ * timezoneStringToSearchableString('America/New_York'); // 'america new york'
6498
+ * ```
6585
6499
  */
6586
- const validDateCellRangeType = arktype.type('unknown').narrow((val, ctx) => isValidDateCellRange(val) || ctx.mustBe('a valid DateCellRange'));
6500
+ function timezoneStringToSearchableString(timezone) {
6501
+ return timezoneStringToSearchableStringReplace(timezone.toLocaleLowerCase());
6502
+ }
6587
6503
  /**
6588
- * ArkType schema that validates a value is a sorted array of non-overlapping {@link DateCellRange} values.
6504
+ * Checks whether the input string is a recognized IANA timezone identifier.
6505
+ *
6506
+ * Uses the cached set from {@link allKnownTimezoneStrings} for O(1) lookup.
6507
+ *
6508
+ * @example
6509
+ * ```ts
6510
+ * isKnownTimezone('America/New_York'); // true
6511
+ * isKnownTimezone('Mars/Olympus'); // false
6512
+ * ```
6589
6513
  */
6590
- const validDateCellRangeSeriesType = arktype.type('unknown').narrow((val, ctx) => isValidDateCellRangeSeries(val) || ctx.mustBe('a valid DateCellRange series with items sorted in ascending order and no repeat indexes'));
6514
+ function isKnownTimezone(input) {
6515
+ return allKnownTimezoneStrings().has(input);
6516
+ }
6591
6517
 
6592
6518
  /**
6593
6519
  * Distinguishes between time-based calendar events and all-day/multi-day events.
@@ -6603,12 +6529,6 @@ exports.CalendarDateType = void 0;
6603
6529
  */
6604
6530
  CalendarDateType["DAYS"] = "days";
6605
6531
  })(exports.CalendarDateType || (exports.CalendarDateType = {}));
6606
- /**
6607
- * ArkType schema for {@link CalendarDate}.
6608
- */
6609
- const calendarDateType = dateDurationSpanType.merge({
6610
- type: arktype.type.enumerated(...Object.values(exports.CalendarDateType))
6611
- });
6612
6532
  /**
6613
6533
  * Creates a {@link CalendarDateFactory} that produces all-day calendar events with timezone-aware start times.
6614
6534
  *
@@ -6678,6 +6598,151 @@ function calendarDateForDateDurationSpan(dateDurationSpan) {
6678
6598
  };
6679
6599
  }
6680
6600
 
6601
+ // MARK: Timezone
6602
+ /**
6603
+ * ArkType DTO schema that validates a string is a recognized IANA timezone.
6604
+ *
6605
+ * Delegates to {@link isKnownTimezone} for the actual check.
6606
+ *
6607
+ * Intended for validating and parsing DTO/JSON data where timezones arrive as strings.
6608
+ *
6609
+ * @example
6610
+ * ```ts
6611
+ * const result = knownTimezoneType('America/Denver');
6612
+ * ```
6613
+ */
6614
+ const knownTimezoneType = arktype.type('string > 0').narrow((val, ctx) => (val != null && isKnownTimezone(val)) || ctx.mustBe('a known timezone'));
6615
+ // MARK: DateDurationSpan
6616
+ /**
6617
+ * ArkType DTO schema for {@link DateDurationSpan}.
6618
+ *
6619
+ * Parses date strings from JSON/DTO input into Date objects using `string.date.parse`.
6620
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6621
+ * into the corresponding runtime types.
6622
+ */
6623
+ const dateDurationSpanType = arktype.type({
6624
+ startsAt: 'string.date.parse',
6625
+ duration: 'number >= 0'
6626
+ });
6627
+ // MARK: DateRange
6628
+ /**
6629
+ * ArkType DTO schema for {@link DateRange}.
6630
+ *
6631
+ * Parses date strings from JSON/DTO input into Date objects using `string.date.parse`.
6632
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6633
+ * into the corresponding runtime types.
6634
+ */
6635
+ const dateRangeType = arktype.type({
6636
+ start: 'string.date.parse',
6637
+ end: 'string.date.parse'
6638
+ });
6639
+ // MARK: DateRangeParams
6640
+ /**
6641
+ * ArkType DTO schema for {@link DateRangeParams}.
6642
+ *
6643
+ * Parses date strings from JSON/DTO input into Date objects using `string.date.parse`.
6644
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6645
+ * into the corresponding runtime types.
6646
+ */
6647
+ const dateRangeParamsType = arktype.type({
6648
+ type: arktype.type.enumerated(...Object.values(exports.DateRangeType)),
6649
+ date: 'string.date.parse',
6650
+ 'distance?': 'number'
6651
+ });
6652
+ // MARK: DateCell
6653
+ /**
6654
+ * ArkType DTO schema for {@link DateCell}.
6655
+ *
6656
+ * Validates a cell index from JSON/DTO input.
6657
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6658
+ * into the corresponding runtime types.
6659
+ */
6660
+ const dateCellType = arktype.type({
6661
+ i: 'number.integer >= 0'
6662
+ });
6663
+ // MARK: DateCellRange
6664
+ /**
6665
+ * ArkType DTO schema for {@link DateCellRange}.
6666
+ *
6667
+ * Validates cell range indexes from JSON/DTO input.
6668
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6669
+ * into the corresponding runtime types.
6670
+ */
6671
+ const dateCellRangeType = dateCellType.merge({
6672
+ 'to?': 'number.integer >= 0'
6673
+ });
6674
+ // MARK: DateCellTiming
6675
+ /**
6676
+ * ArkType DTO schema for {@link DateCellTiming}.
6677
+ *
6678
+ * Parses date strings from JSON/DTO input into Date objects using `string.date.parse`.
6679
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6680
+ * into the corresponding runtime types.
6681
+ */
6682
+ const dateCellTimingType = dateDurationSpanType.merge({
6683
+ end: 'string.date.parse',
6684
+ timezone: knownTimezoneType
6685
+ });
6686
+ // MARK: CalendarDate
6687
+ /**
6688
+ * ArkType DTO schema for {@link CalendarDate}.
6689
+ *
6690
+ * Parses date strings from JSON/DTO input into Date objects using `string.date.parse`.
6691
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6692
+ * into the corresponding runtime types.
6693
+ */
6694
+ const calendarDateType = dateDurationSpanType.merge({
6695
+ type: arktype.type.enumerated(...Object.values(exports.CalendarDateType))
6696
+ });
6697
+ // MARK: DateCellSchedule
6698
+ /**
6699
+ * ArkType DTO schema for {@link DateCellSchedule}.
6700
+ *
6701
+ * Validates schedule data from JSON/DTO input.
6702
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6703
+ * into the corresponding runtime types.
6704
+ */
6705
+ const dateCellScheduleType = arktype.type({
6706
+ w: [DATE_CELL_SCHEDULE_ENCODED_WEEK_REGEX, '&', 'string'],
6707
+ 'd?': 'number.integer >= 0 []',
6708
+ 'ex?': 'number.integer >= 0 []'
6709
+ });
6710
+ // MARK: ModelRecurrenceInfo
6711
+ /**
6712
+ * ArkType DTO schema for {@link ModelRecurrenceInfo}.
6713
+ *
6714
+ * Parses date strings from JSON/DTO input into Date objects using `string.date.parse`.
6715
+ * Use this schema when validating and converting serialized data (e.g., API responses, JSON payloads)
6716
+ * into the corresponding runtime types.
6717
+ */
6718
+ const modelRecurrenceInfoType = arktype.type({
6719
+ 'timezone?': 'string',
6720
+ rrule: 'string',
6721
+ start: 'string.date.parse',
6722
+ end: 'string.date.parse',
6723
+ 'forever?': 'boolean'
6724
+ });
6725
+ // MARK: Validators
6726
+ /**
6727
+ * ArkType DTO schema that validates a value is a valid {@link DateCellTiming}.
6728
+ *
6729
+ * Parses date strings from JSON/DTO input into Date objects, then validates the resulting timing
6730
+ * using {@link isValidDateCellTiming}.
6731
+ */
6732
+ const validDateCellTimingType = dateCellTimingType.narrow((val, ctx) => (val != null && isValidDateCellTiming(val)) || ctx.mustBe('a valid DateCellTiming'));
6733
+ /**
6734
+ * ArkType DTO schema that validates a value is a valid {@link DateCellRange} (non-negative indexes, `to >= i`).
6735
+ *
6736
+ * Validates cell range data from JSON/DTO input.
6737
+ */
6738
+ const validDateCellRangeType = dateCellRangeType.narrow((val, ctx) => (val != null && isValidDateCellRange(val)) || ctx.mustBe('a valid DateCellRange'));
6739
+ /**
6740
+ * ArkType DTO schema that validates a value is a sorted array of non-overlapping {@link DateCellRange} values.
6741
+ *
6742
+ * Validates cell range series data from JSON/DTO input.
6743
+ */
6744
+ const validDateCellRangeSeriesType = arktype.type(dateCellRangeType.array()).narrow((val, ctx) => (val != null && isValidDateCellRangeSeries(val)) || ctx.mustBe('a valid DateCellRange series with items sorted in ascending order and no repeat indexes'));
6745
+
6681
6746
  /**
6682
6747
  * A {@link HashSet} specialized for Date values, using the millisecond timestamp as the hash key.
6683
6748
  *
@@ -8781,16 +8846,6 @@ class DateRRuleUtility {
8781
8846
  }
8782
8847
  }
8783
8848
 
8784
- /**
8785
- * ArkType schema for {@link ModelRecurrenceInfo}.
8786
- */
8787
- const modelRecurrenceInfoType = arktype.type({
8788
- 'timezone?': 'string',
8789
- rrule: 'string',
8790
- start: 'Date',
8791
- end: 'Date',
8792
- 'forever?': 'boolean'
8793
- });
8794
8849
  /**
8795
8850
  * Stateless utility for converting between recurrence input
8796
8851
  * ({@link ModelRecurrenceStart}) and the indexed storage form