ezmedicationinput 0.1.1 → 0.1.3

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/dist/safety.js CHANGED
@@ -1,10 +1,13 @@
1
- import { DISCOURAGED_TOKENS } from "./maps";
2
- export function checkDiscouraged(token, options) {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkDiscouraged = checkDiscouraged;
4
+ const maps_1 = require("./maps");
5
+ function checkDiscouraged(token, options) {
3
6
  const lower = token.toLowerCase();
4
- if (!(lower in DISCOURAGED_TOKENS)) {
7
+ if (!(lower in maps_1.DISCOURAGED_TOKENS)) {
5
8
  return { allowed: true };
6
9
  }
7
- const code = DISCOURAGED_TOKENS[lower];
10
+ const code = maps_1.DISCOURAGED_TOKENS[lower];
8
11
  if (options && options.allowDiscouraged === false) {
9
12
  throw new Error(`Discouraged token '${token}' is not allowed`);
10
13
  }
package/dist/schedule.js CHANGED
@@ -1,4 +1,8 @@
1
- import { EventTiming } from "./types";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nextDueDoses = nextDueDoses;
4
+ const types_1 = require("./types");
5
+ const array_1 = require("./utils/array");
2
6
  /**
3
7
  * Default institution times used when a dosage only specifies frequency without
4
8
  * explicit EventTiming anchors. Clinics can override these through the
@@ -29,12 +33,33 @@ const dateTimeFormatCache = new Map();
29
33
  const weekdayFormatCache = new Map();
30
34
  /** Simple zero-padding helper for numeric components. */
31
35
  function pad(value, length = 2) {
32
- return value.toString().padStart(length, "0");
36
+ const absolute = Math.abs(value);
37
+ let output = absolute.toString();
38
+ while (output.length < length) {
39
+ output = `0${output}`;
40
+ }
41
+ return value < 0 ? `-${output}` : output;
42
+ }
43
+ function formatToParts(formatter, date) {
44
+ const withParts = formatter;
45
+ if (typeof withParts.formatToParts === "function") {
46
+ return withParts.formatToParts(date);
47
+ }
48
+ const iso = date.toISOString();
49
+ return [
50
+ { type: "year", value: iso.slice(0, 4) },
51
+ { type: "month", value: iso.slice(5, 7) },
52
+ { type: "day", value: iso.slice(8, 10) },
53
+ { type: "hour", value: iso.slice(11, 13) },
54
+ { type: "minute", value: iso.slice(14, 16) },
55
+ { type: "second", value: iso.slice(17, 19) }
56
+ ];
33
57
  }
34
58
  /**
35
59
  * Normalizes HH:mm or HH:mm:ss clocks into a consistent HH:mm:ss string.
36
60
  */
37
61
  function normalizeClock(clock) {
62
+ var _a;
38
63
  const parts = clock.split(":");
39
64
  if (parts.length < 2 || parts.length > 3) {
40
65
  throw new Error(`Invalid clock value: ${clock}`);
@@ -42,7 +67,7 @@ function normalizeClock(clock) {
42
67
  const [hourPart, minutePart, secondPart] = [
43
68
  parts[0],
44
69
  parts[1],
45
- parts[2] ?? "00"
70
+ (_a = parts[2]) !== null && _a !== void 0 ? _a : "00"
46
71
  ];
47
72
  const hour = Number(hourPart);
48
73
  const minute = Number(minutePart);
@@ -64,7 +89,7 @@ function normalizeClock(clock) {
64
89
  function getDateTimeFormat(timeZone) {
65
90
  let formatter = dateTimeFormatCache.get(timeZone);
66
91
  if (!formatter) {
67
- formatter = new Intl.DateTimeFormat("en-CA", {
92
+ const options = {
68
93
  timeZone,
69
94
  calendar: "iso8601",
70
95
  numberingSystem: "latn",
@@ -75,7 +100,8 @@ function getDateTimeFormat(timeZone) {
75
100
  hour: "2-digit",
76
101
  minute: "2-digit",
77
102
  second: "2-digit"
78
- });
103
+ };
104
+ formatter = new Intl.DateTimeFormat("en-CA", options);
79
105
  dateTimeFormatCache.set(timeZone, formatter);
80
106
  }
81
107
  return formatter;
@@ -97,9 +123,10 @@ function getWeekdayFormat(timeZone) {
97
123
  * zone.
98
124
  */
99
125
  function getTimeParts(date, timeZone) {
126
+ var _a, _b;
100
127
  const formatter = getDateTimeFormat(timeZone);
101
128
  const parts = {};
102
- const rawParts = formatter.formatToParts(date);
129
+ const rawParts = formatToParts(formatter, date);
103
130
  for (const part of rawParts) {
104
131
  if (part.type === "literal") {
105
132
  continue;
@@ -128,7 +155,7 @@ function getTimeParts(date, timeZone) {
128
155
  // instant forward slightly so we can capture the correct calendar date and
129
156
  // reset the hour component back to zero.
130
157
  const forward = new Date(date.getTime() + 60 * 1000);
131
- const forwardParts = formatter.formatToParts(forward);
158
+ const forwardParts = formatToParts(formatter, forward);
132
159
  for (const part of forwardParts) {
133
160
  if (part.type === "literal") {
134
161
  continue;
@@ -144,8 +171,8 @@ function getTimeParts(date, timeZone) {
144
171
  }
145
172
  }
146
173
  parts.hour = 0;
147
- parts.minute = parts.minute ?? 0;
148
- parts.second = parts.second ?? 0;
174
+ parts.minute = (_a = parts.minute) !== null && _a !== void 0 ? _a : 0;
175
+ parts.second = (_b = parts.second) !== null && _b !== void 0 ? _b : 0;
149
176
  }
150
177
  if (parts.year === undefined ||
151
178
  parts.month === undefined ||
@@ -278,49 +305,51 @@ function applyOffset(clock, offsetMinutes) {
278
305
  }
279
306
  /** Provides the default meal pairing used for AC/PC expansions. */
280
307
  function getDefaultMealPairs(config) {
281
- return [EventTiming.Breakfast, EventTiming.Lunch, EventTiming.Dinner];
308
+ return [types_1.EventTiming.Breakfast, types_1.EventTiming.Lunch, types_1.EventTiming.Dinner];
282
309
  }
283
310
  const SPECIFIC_BEFORE_MEALS = {
284
- [EventTiming["Before Breakfast"]]: EventTiming.Breakfast,
285
- [EventTiming["Before Lunch"]]: EventTiming.Lunch,
286
- [EventTiming["Before Dinner"]]: EventTiming.Dinner
311
+ [types_1.EventTiming["Before Breakfast"]]: types_1.EventTiming.Breakfast,
312
+ [types_1.EventTiming["Before Lunch"]]: types_1.EventTiming.Lunch,
313
+ [types_1.EventTiming["Before Dinner"]]: types_1.EventTiming.Dinner
287
314
  };
288
315
  const SPECIFIC_AFTER_MEALS = {
289
- [EventTiming["After Breakfast"]]: EventTiming.Breakfast,
290
- [EventTiming["After Lunch"]]: EventTiming.Lunch,
291
- [EventTiming["After Dinner"]]: EventTiming.Dinner
316
+ [types_1.EventTiming["After Breakfast"]]: types_1.EventTiming.Breakfast,
317
+ [types_1.EventTiming["After Lunch"]]: types_1.EventTiming.Lunch,
318
+ [types_1.EventTiming["After Dinner"]]: types_1.EventTiming.Dinner
292
319
  };
293
320
  /**
294
321
  * Expands a single EventTiming code into concrete wall-clock entries.
295
322
  */
296
323
  function expandTiming(code, config, repeat) {
297
- const mealOffsets = config.mealOffsets ?? {};
324
+ var _a, _b, _c, _d, _e, _f, _g, _h;
325
+ const mealOffsets = (_a = config.mealOffsets) !== null && _a !== void 0 ? _a : {};
326
+ const eventClock = (_b = config.eventClock) !== null && _b !== void 0 ? _b : {};
298
327
  const normalized = [];
299
- const clockValue = config.eventClock[code];
328
+ const clockValue = eventClock[code];
300
329
  if (clockValue) {
301
330
  normalized.push({ time: normalizeClock(clockValue), dayShift: 0 });
302
331
  }
303
- else if (code === EventTiming["Before Meal"]) {
332
+ else if (code === types_1.EventTiming["Before Meal"]) {
304
333
  for (const meal of getDefaultMealPairs(config)) {
305
- const base = config.eventClock[meal];
334
+ const base = eventClock[meal];
306
335
  if (!base) {
307
336
  continue;
308
337
  }
309
- normalized.push(applyOffset(normalizeClock(base), mealOffsets[code] ?? 0));
338
+ normalized.push(applyOffset(normalizeClock(base), (_c = mealOffsets[code]) !== null && _c !== void 0 ? _c : 0));
310
339
  }
311
340
  }
312
- else if (code === EventTiming["After Meal"]) {
341
+ else if (code === types_1.EventTiming["After Meal"]) {
313
342
  for (const meal of getDefaultMealPairs(config)) {
314
- const base = config.eventClock[meal];
343
+ const base = eventClock[meal];
315
344
  if (!base) {
316
345
  continue;
317
346
  }
318
- normalized.push(applyOffset(normalizeClock(base), mealOffsets[code] ?? 0));
347
+ normalized.push(applyOffset(normalizeClock(base), (_d = mealOffsets[code]) !== null && _d !== void 0 ? _d : 0));
319
348
  }
320
349
  }
321
- else if (code === EventTiming.Meal) {
350
+ else if (code === types_1.EventTiming.Meal) {
322
351
  for (const meal of getDefaultMealPairs(config)) {
323
- const base = config.eventClock[meal];
352
+ const base = eventClock[meal];
324
353
  if (!base) {
325
354
  continue;
326
355
  }
@@ -329,25 +358,26 @@ function expandTiming(code, config, repeat) {
329
358
  }
330
359
  else if (code in SPECIFIC_BEFORE_MEALS) {
331
360
  const mealCode = SPECIFIC_BEFORE_MEALS[code];
332
- const base = config.eventClock[mealCode];
361
+ const base = eventClock[mealCode];
333
362
  if (base) {
334
363
  const baseClock = normalizeClock(base);
335
- const offset = mealOffsets[code] ?? mealOffsets[EventTiming["Before Meal"]] ?? 0;
364
+ const offset = (_f = (_e = mealOffsets[code]) !== null && _e !== void 0 ? _e : mealOffsets[types_1.EventTiming["Before Meal"]]) !== null && _f !== void 0 ? _f : 0;
336
365
  normalized.push(offset ? applyOffset(baseClock, offset) : { time: baseClock, dayShift: 0 });
337
366
  }
338
367
  }
339
368
  else if (code in SPECIFIC_AFTER_MEALS) {
340
369
  const mealCode = SPECIFIC_AFTER_MEALS[code];
341
- const base = config.eventClock[mealCode];
370
+ const base = eventClock[mealCode];
342
371
  if (base) {
343
372
  const baseClock = normalizeClock(base);
344
- const offset = mealOffsets[code] ?? mealOffsets[EventTiming["After Meal"]] ?? 0;
373
+ const offset = (_h = (_g = mealOffsets[code]) !== null && _g !== void 0 ? _g : mealOffsets[types_1.EventTiming["After Meal"]]) !== null && _h !== void 0 ? _h : 0;
345
374
  normalized.push(offset ? applyOffset(baseClock, offset) : { time: baseClock, dayShift: 0 });
346
375
  }
347
376
  }
348
377
  if (repeat.offset && normalized.length) {
349
378
  return normalized.map((entry) => {
350
- const adjusted = applyOffset(entry.time, repeat.offset ?? 0);
379
+ var _a;
380
+ const adjusted = applyOffset(entry.time, (_a = repeat.offset) !== null && _a !== void 0 ? _a : 0);
351
381
  return {
352
382
  time: adjusted.time,
353
383
  dayShift: entry.dayShift + adjusted.dayShift
@@ -361,7 +391,7 @@ function expandWhenCodes(whenCodes, config, repeat) {
361
391
  const entries = [];
362
392
  const seen = new Set();
363
393
  for (const code of whenCodes) {
364
- if (code === EventTiming.Immediate) {
394
+ if (code === types_1.EventTiming.Immediate) {
365
395
  continue;
366
396
  }
367
397
  const expansions = expandTiming(code, config, repeat);
@@ -381,36 +411,45 @@ function expandWhenCodes(whenCodes, config, repeat) {
381
411
  return a.time.localeCompare(b.time);
382
412
  });
383
413
  }
414
+ function mergeFrequencyDefaults(base, override) {
415
+ var _a, _b, _c, _d;
416
+ if (!base && !override) {
417
+ return undefined;
418
+ }
419
+ const merged = {};
420
+ if ((base === null || base === void 0 ? void 0 : base.byCode) || (override === null || override === void 0 ? void 0 : override.byCode)) {
421
+ merged.byCode = Object.assign(Object.assign({}, ((_a = base === null || base === void 0 ? void 0 : base.byCode) !== null && _a !== void 0 ? _a : {})), ((_b = override === null || override === void 0 ? void 0 : override.byCode) !== null && _b !== void 0 ? _b : {}));
422
+ }
423
+ if ((base === null || base === void 0 ? void 0 : base.byFrequency) || (override === null || override === void 0 ? void 0 : override.byFrequency)) {
424
+ merged.byFrequency = Object.assign(Object.assign({}, ((_c = base === null || base === void 0 ? void 0 : base.byFrequency) !== null && _c !== void 0 ? _c : {})), ((_d = override === null || override === void 0 ? void 0 : override.byFrequency) !== null && _d !== void 0 ? _d : {}));
425
+ }
426
+ return merged;
427
+ }
384
428
  /** Resolves fallback clock arrays for frequency-only schedules. */
385
429
  function resolveFrequencyClocks(timing, config) {
430
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
386
431
  const defaults = {
387
- byCode: {
388
- ...DEFAULT_FREQUENCY_DEFAULTS.byCode,
389
- ...(config.frequencyDefaults?.byCode ?? {})
390
- },
391
- byFrequency: {
392
- ...DEFAULT_FREQUENCY_DEFAULTS.byFrequency,
393
- ...(config.frequencyDefaults?.byFrequency ?? {})
394
- }
432
+ byCode: Object.assign(Object.assign({}, DEFAULT_FREQUENCY_DEFAULTS.byCode), ((_b = (_a = config.frequencyDefaults) === null || _a === void 0 ? void 0 : _a.byCode) !== null && _b !== void 0 ? _b : {})),
433
+ byFrequency: Object.assign(Object.assign({}, DEFAULT_FREQUENCY_DEFAULTS.byFrequency), ((_d = (_c = config.frequencyDefaults) === null || _c === void 0 ? void 0 : _c.byFrequency) !== null && _d !== void 0 ? _d : {}))
395
434
  };
396
435
  const collected = new Set();
397
- const code = timing.code?.coding?.find((coding) => coding.code)?.code;
398
- const normalizedCode = code?.toUpperCase();
399
- if (normalizedCode && defaults.byCode?.[normalizedCode]) {
436
+ const code = (_g = (_f = (_e = timing.code) === null || _e === void 0 ? void 0 : _e.coding) === null || _f === void 0 ? void 0 : _f.find((coding) => coding.code)) === null || _g === void 0 ? void 0 : _g.code;
437
+ const normalizedCode = code === null || code === void 0 ? void 0 : code.toUpperCase();
438
+ if (normalizedCode && ((_h = defaults.byCode) === null || _h === void 0 ? void 0 : _h[normalizedCode])) {
400
439
  for (const clock of defaults.byCode[normalizedCode]) {
401
440
  collected.add(normalizeClock(clock));
402
441
  }
403
442
  }
404
443
  const repeat = timing.repeat;
405
- if (repeat?.frequency && repeat.period && repeat.periodUnit) {
444
+ if ((repeat === null || repeat === void 0 ? void 0 : repeat.frequency) && repeat.period && repeat.periodUnit) {
406
445
  const key = `freq:${repeat.frequency}/${repeat.periodUnit}`;
407
- if (defaults.byFrequency?.[key]) {
446
+ if ((_j = defaults.byFrequency) === null || _j === void 0 ? void 0 : _j[key]) {
408
447
  for (const clock of defaults.byFrequency[key]) {
409
448
  collected.add(normalizeClock(clock));
410
449
  }
411
450
  }
412
451
  const perPeriodKey = `freq:${repeat.frequency}/per:${repeat.period}${repeat.periodUnit}`;
413
- if (defaults.byFrequency?.[perPeriodKey]) {
452
+ if ((_k = defaults.byFrequency) === null || _k === void 0 ? void 0 : _k[perPeriodKey]) {
414
453
  for (const clock of defaults.byFrequency[perPeriodKey]) {
415
454
  collected.add(normalizeClock(clock));
416
455
  }
@@ -418,41 +457,50 @@ function resolveFrequencyClocks(timing, config) {
418
457
  }
419
458
  return Array.from(collected).sort();
420
459
  }
421
- /** Determines the greater of orderedAt/from for baseline comparisons. */
422
- function computeBaseline(orderedAt, from) {
423
- return orderedAt > from ? orderedAt : from;
424
- }
425
460
  /**
426
461
  * Produces the next dose timestamps in ascending order according to the
427
462
  * provided configuration and dosage metadata.
428
463
  */
429
- export function nextDueDoses(dosage, options) {
464
+ function nextDueDoses(dosage, options) {
465
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
430
466
  if (!options || typeof options !== "object") {
431
467
  throw new Error("Options argument is required for nextDueDoses");
432
468
  }
433
- const { limit } = options;
469
+ if (options.from === undefined) {
470
+ throw new Error("The 'from' option is required for nextDueDoses");
471
+ }
472
+ const limit = (_a = options.limit) !== null && _a !== void 0 ? _a : 10;
434
473
  if (!Number.isFinite(limit) || limit <= 0) {
435
474
  return [];
436
475
  }
437
- const orderedAt = coerceDate(options.orderedAt, "orderedAt");
438
476
  const from = coerceDate(options.from, "from");
439
- const { config } = options;
440
- if (!config || !config.timeZone) {
477
+ const orderedAt = options.orderedAt === undefined ? null : coerceDate(options.orderedAt, "orderedAt");
478
+ const baseTime = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
479
+ const providedConfig = options.config;
480
+ const timeZone = (_b = options.timeZone) !== null && _b !== void 0 ? _b : providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.timeZone;
481
+ if (!timeZone) {
441
482
  throw new Error("Configuration with a valid timeZone is required");
442
483
  }
443
- const timeZone = config.timeZone;
484
+ const eventClock = Object.assign(Object.assign({}, ((_c = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.eventClock) !== null && _c !== void 0 ? _c : {})), ((_d = options.eventClock) !== null && _d !== void 0 ? _d : {}));
485
+ const mealOffsets = Object.assign(Object.assign({}, ((_e = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.mealOffsets) !== null && _e !== void 0 ? _e : {})), ((_f = options.mealOffsets) !== null && _f !== void 0 ? _f : {}));
486
+ const frequencyDefaults = mergeFrequencyDefaults(providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.frequencyDefaults, options.frequencyDefaults);
487
+ const config = {
488
+ timeZone,
489
+ eventClock,
490
+ mealOffsets,
491
+ frequencyDefaults
492
+ };
444
493
  const timing = dosage.timing;
445
- const repeat = timing?.repeat;
494
+ const repeat = timing === null || timing === void 0 ? void 0 : timing.repeat;
446
495
  if (!timing || !repeat) {
447
496
  return [];
448
497
  }
449
- const baseline = computeBaseline(orderedAt, from);
450
498
  const results = [];
451
499
  const seen = new Set();
452
- const dayFilter = new Set((repeat.dayOfWeek ?? []).map((day) => day.toLowerCase()));
500
+ const dayFilter = new Set(((_g = repeat.dayOfWeek) !== null && _g !== void 0 ? _g : []).map((day) => day.toLowerCase()));
453
501
  const enforceDayFilter = dayFilter.size > 0;
454
- const whenCodes = repeat.when ?? [];
455
- const timeOfDayEntries = repeat.timeOfDay ?? [];
502
+ const whenCodes = (_h = repeat.when) !== null && _h !== void 0 ? _h : [];
503
+ const timeOfDayEntries = (_j = repeat.timeOfDay) !== null && _j !== void 0 ? _j : [];
456
504
  if (whenCodes.length > 0 || timeOfDayEntries.length > 0) {
457
505
  const expanded = expandWhenCodes(whenCodes, config, repeat);
458
506
  if (timeOfDayEntries.length > 0) {
@@ -466,11 +514,14 @@ export function nextDueDoses(dosage, options) {
466
514
  return a.time.localeCompare(b.time);
467
515
  });
468
516
  }
469
- const includesImmediate = whenCodes.includes(EventTiming.Immediate);
470
- if (includesImmediate && orderedAt >= baseline) {
471
- const instantIso = formatZonedIso(orderedAt, timeZone);
472
- results.push(instantIso);
473
- seen.add(instantIso);
517
+ const includesImmediate = (0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate);
518
+ if (includesImmediate) {
519
+ const immediateSource = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
520
+ if (!orderedAt || orderedAt >= from) {
521
+ const instantIso = formatZonedIso(immediateSource, timeZone);
522
+ results.push(instantIso);
523
+ seen.add(instantIso);
524
+ }
474
525
  }
475
526
  if (expanded.length === 0) {
476
527
  return results.slice(0, limit);
@@ -489,10 +540,10 @@ export function nextDueDoses(dosage, options) {
489
540
  if (!zoned) {
490
541
  continue;
491
542
  }
492
- if (zoned < baseline) {
543
+ if (zoned < from) {
493
544
  continue;
494
545
  }
495
- if (zoned < orderedAt) {
546
+ if (orderedAt && zoned < orderedAt) {
496
547
  continue;
497
548
  }
498
549
  const iso = formatZonedIso(zoned, timeZone);
@@ -518,7 +569,7 @@ export function nextDueDoses(dosage, options) {
518
569
  if (treatAsInterval) {
519
570
  // True interval schedules advance from the order start in fixed units. The
520
571
  // timing.code remains advisory so we only rely on the period/unit fields.
521
- const candidates = generateIntervalSeries(orderedAt, from, limit, repeat, timeZone, dayFilter, enforceDayFilter);
572
+ const candidates = generateIntervalSeries(baseTime, from, limit, repeat, timeZone, dayFilter, enforceDayFilter, orderedAt);
522
573
  return candidates;
523
574
  }
524
575
  if (repeat.frequency && repeat.period && repeat.periodUnit) {
@@ -540,7 +591,10 @@ export function nextDueDoses(dosage, options) {
540
591
  if (!zoned) {
541
592
  continue;
542
593
  }
543
- if (zoned < baseline || zoned < orderedAt) {
594
+ if (zoned < from) {
595
+ continue;
596
+ }
597
+ if (orderedAt && zoned < orderedAt) {
544
598
  continue;
545
599
  }
546
600
  const iso = formatZonedIso(zoned, timeZone);
@@ -561,21 +615,20 @@ export function nextDueDoses(dosage, options) {
561
615
  return [];
562
616
  }
563
617
  /**
564
- * Generates an interval-based series by stepping forward from orderedAt until
565
- * the requested number of timestamps have been produced.
618
+ * Generates an interval-based series by stepping forward from the base time
619
+ * until the requested number of timestamps have been produced.
566
620
  */
567
- function generateIntervalSeries(orderedAt, from, limit, repeat, timeZone, dayFilter, enforceDayFilter) {
621
+ function generateIntervalSeries(baseTime, from, limit, repeat, timeZone, dayFilter, enforceDayFilter, orderedAt) {
568
622
  const increment = createIntervalStepper(repeat, timeZone);
569
623
  if (!increment) {
570
624
  return [];
571
625
  }
572
626
  const results = [];
573
627
  const seen = new Set();
574
- let current = orderedAt;
575
- const baseline = computeBaseline(orderedAt, from);
628
+ let current = baseTime;
576
629
  let guard = 0;
577
630
  const maxIterations = limit * 1000;
578
- while (current < baseline && guard < maxIterations) {
631
+ while (current < from && guard < maxIterations) {
579
632
  const next = increment(current);
580
633
  if (!next || next.getTime() === current.getTime()) {
581
634
  break;
@@ -586,6 +639,25 @@ function generateIntervalSeries(orderedAt, from, limit, repeat, timeZone, dayFil
586
639
  while (results.length < limit && guard < maxIterations) {
587
640
  const weekday = getLocalWeekday(current, timeZone);
588
641
  if (!enforceDayFilter || dayFilter.has(weekday)) {
642
+ if (current < from) {
643
+ // Ensure the current candidate respects the evaluation window.
644
+ guard += 1;
645
+ const next = increment(current);
646
+ if (!next || next.getTime() === current.getTime()) {
647
+ break;
648
+ }
649
+ current = next;
650
+ continue;
651
+ }
652
+ if (orderedAt && current < orderedAt) {
653
+ guard += 1;
654
+ const next = increment(current);
655
+ if (!next || next.getTime() === current.getTime()) {
656
+ break;
657
+ }
658
+ current = next;
659
+ continue;
660
+ }
589
661
  const iso = formatZonedIso(current, timeZone);
590
662
  if (!seen.has(iso)) {
591
663
  seen.add(iso);