Explorar el Código

fix(schedule): keep low-year LMT handoff safe

pku-xht hace 1 mes
padre
commit
b37877d80b

+ 5 - 1
packages/schedule/tool-schedule/src/domain.ts

@@ -815,12 +815,15 @@ function ownedLowYearCronInstant(
 function nextCronInstant(rule: ParsedCronRule, timeZone: string, after: number): number | undefined {
   if (!rule.hasMatchingDate) return undefined
   let cursor = after
+  const formatter = cronLocalFormatter(timeZone)
   if (new Date(after).getUTCFullYear() <= CRONER_LOW_YEAR_CUTOFF) {
     const lower = ownedLowYearCronInstant(rule, timeZone, after, 1)
     if (lower !== undefined) return lower
+    if (localProjection(formatter, after).offset % 60_000 !== 0) {
+      cursor = cursorBeforeNextOffsetTransition(formatter, after)
+    }
   }
   const evaluator = cronEvaluator(rule, timeZone)
-  const formatter = cronLocalFormatter(timeZone)
   let gapCorrections = 0
   while (cursor < MAX_FOUR_DIGIT_YEAR_MS) {
     const candidate = evaluator.nextRun(new Date(cursor))
@@ -840,6 +843,7 @@ function nextCronInstant(rule: ParsedCronRule, timeZone: string, after: number):
     }
     gapCorrections = 0
     if (epoch > MAX_FOUR_DIGIT_YEAR_MS) return undefined
+    /* v8 ignore next 3 -- current IANA data leaves sub-minute LMT at its first transition. */
     if (epoch % 60_000 !== 0) {
       cursor = cursorBeforeNextOffsetTransition(formatter, epoch)
       continue

+ 11 - 3
packages/schedule/tool-schedule/tests/cron.spec.ts

@@ -246,15 +246,23 @@ describe('Croner calendar adapter', () => {
   })
 
   it('skips a sub-minute local-mean-time era before iterating dense safe-year matches', () => {
-    const record = createCronScheduleRecord(
+    const yearOne = createCronScheduleRecord(
+      ScheduleId('schedule-sub-minute-offset-year-one'),
+      'standard-time handoff',
+      '*/5 * * * *',
+      'Europe/Amsterdam',
+      Date.parse('0001-01-01T00:00:00.000Z'),
+    )
+    const yearOneHundred = createCronScheduleRecord(
       ScheduleId('schedule-sub-minute-offset'),
       'standard-time handoff',
       '*/5 * * * *',
       'Europe/Amsterdam',
       Date.parse('0100-01-01T00:00:00.000Z'),
     )
-    expect(new Date(record.scheduledAt).getUTCFullYear()).toBeGreaterThan(109)
-    expect(Math.abs(Date.parse(record.scheduledAt) % 60_000)).toBe(0)
+    expect(yearOne.scheduledAt).toBe(yearOneHundred.scheduledAt)
+    expect(new Date(yearOne.scheduledAt).getUTCFullYear()).toBeGreaterThan(109)
+    expect(Math.abs(Date.parse(yearOne.scheduledAt) % 60_000)).toBe(0)
   }, 1_000)
 
   it('selects the latest current match after a persisted baseline', () => {