diff --git a/internal/schedule/caps.go b/internal/schedule/caps.go index a4782043e..6c4da365b 100644 --- a/internal/schedule/caps.go +++ b/internal/schedule/caps.go @@ -18,8 +18,9 @@ type Limits struct { // candidates it produces (TASK-4100 H1, r2): the count restarts at every // candidate, so it ends a no-match stretch and nothing else. A stall is a // CapStall only when an answer could lie past the examined part (r3). - // MaxDuration is checked between yields, bounding a call to within one - // period expansion, limited by maxPeriodCandidates (TASK-4100 final round H1). + // MaxDuration is checked between yields, so a call can overrun it by one + // period expansion (limited by maxPeriodCandidates) or one no-match stretch of + // MaxSteps steps; MaxSteps has no ceiling (TASK-4100 final round H1, 3a H-L1). // MaxScan additionally refuses a wall-clock query far from Start, except // for COUNT rules and rules whose UNTIL is already spent (TASK-4100 r4). MaxSteps int64 @@ -106,9 +107,8 @@ func (c *Compiled) checkScan(loc *time.Location, base, q time.Time, lim Limits) // guard runs fn in the calling goroutine, so nothing can be abandoned or leak. // A panic in the library becomes a MalformedError instead of crashing the caller. -// MaxDuration is checked between yields, bounding a call to within one period -// expansion, limited by maxPeriodCandidates (TASK-4100 final round H1). -// MaxSteps ends a no-match stretch inside the vendored generator (r2, r4). +// MaxDuration is checked between yields and MaxSteps ends a no-match stretch inside the +// vendored generator (r2, r4); Limits.MaxSteps states what the two bound together. func guard(fn func() error) (err error) { defer func() { if recover() != nil { diff --git a/internal/schedule/doc.go b/internal/schedule/doc.go index 64e7b5dda..427bd6dfa 100644 --- a/internal/schedule/doc.go +++ b/internal/schedule/doc.go @@ -35,11 +35,12 @@ // EXDATE matched on the wall reading // UNTIL compared on the wall; a Z UNTIL and elapsed rules: on the instant // -// RULES. ParseRule is strict. It refuses what RFC 5545 forbids (COUNT with -// UNTIL, BYWEEKNO off YEARLY, an ordinal BYDAY off MONTHLY and YEARLY, -// BYSETPOS alone, BYMONTHDAY on WEEKLY, BYYEARDAY on DAILY, WEEKLY or MONTHLY), -// rules that can never fire (Feb 30), FREQ=SECONDLY (the tick is 30 s), and -// rules over 512 bytes, INTERVAL over 1,000,000 or years outside 1970..2200. +// RULES. ParseRule refuses what RFC 5545 forbids (COUNT with UNTIL, BYWEEKNO off YEARLY, +// an ordinal BYDAY off MONTHLY and YEARLY, BYSETPOS alone, BYMONTHDAY on WEEKLY, BYYEARDAY +// on DAILY, WEEKLY or MONTHLY), except that an UNTIL whose type differs from DTSTART's is +// accepted as Until defines it (untilbounds_test.go). It also refuses rules that never fire +// (Feb 30), FREQ=SECONDLY (the tick is 30 s), over 10,000 candidates per FREQ period +// (density.go), over 512 bytes, INTERVAL over 1,000,000 or years outside 1970..2200. // A DTSTART the rule does not produce is not emitted (dateutil semantics, ruling // Q1). Every refusal is a *MalformedError carrying a Code and the offending Part; // no third-party error text ever reaches a caller.