Skip to content

ADR 0024 stub semantics

Karthik P edited this page Nov 1, 2026 · 2 revisions

ADR-0024: stub semantics for fixed-date years with week-based periods

A fixed-date year with week-based periods states its week grid with an explicit stub.anchor that ends the first period, states with a required stub.absorb that the first and last periods take the partial weeks, and keeps a week pattern that sums to 52.

Last reviewed: 1 Nov 2026

Status

Accepted on 1 Nov 2026 under the maintainer's delegation; may be revisited. The research proposal left this open ("Exact semantics of stub for fixed-date years with week-based periods, checked against Vishay's reported quarter ends"), and the maintainer asked for the open Phase 0 decisions to be completed without them (issue #11). Not built: in 0.1.0 subdivision.stub is rejected with UNSUPPORTED_FEATURE, and week_pattern inside a fixed_date year is rejected with UNSUPPORTED_COMBINATION. This decision is implemented in 0.2.

Context

Vishay's 10-Q describes "13-week periods beginning on a Sunday and ending on a Saturday, except for the first fiscal quarter, which always begins on January 1, and the fourth fiscal quarter, which always ends on December 31". The quote matched the filing in an excerpt check; sec.gov refused a raw fetch, so it is not raw-verified (Evidence). The year is fixed (1 January to 31 December) but the quarters are built from weeks, so the first and last quarters absorb partial weeks.

The proposal's Vishay definition anchors the week grid so that Q1 ends on the Saturday nearest 31 March. The filing does not name that anchor, so the definition is illustrative. Vishay's reported Q1 ends, 1 Apr 2023 and 30 Mar 2024, match "Saturday nearest 31 March" (Evidence); two years are not proof of the rule.

Contradictions

  • D1 (resolved by this ADR). The proposal's block table documents stub: first_and_last, a value saying the first and last periods absorb partial weeks. Its Vishay example instead writes stub: { "anchor": { "month": 3, "day": "last", "rule": "nearest" } }, an object that places the week grid. The two cannot both be the format.
  • D4 (resolved by this ADR). WEEKS_SUM_MISMATCH requires a week_pattern to sum to 52 weeks only inside a week_based year. What a pattern must sum to inside a fixed_date year is unspecified. Vishay's [13, 13, 13, 13] sums to 52 weeks, but a fixed-date year has 365 or 366 days, so one or two days must go somewhere.
  • Pattern length (found while deciding; not one of D1 to D12). The proposal's block table gives periods 12 or 13 entries, and spec/definition-v1.md rejects any other length with INVALID_VALUE. The proposal's own Vishay example has four entries, one per quarter.

Decision drivers

  • Reproduce Vishay's reported quarter ends exactly.
  • Express the grid anchor explicitly, not as an inferred default.
  • Keep stub meaningful only where partial weeks exist (STUB_WITHOUT_FIXED_YEAR in the proposal).

Considered options

Option 1: stub: "first_and_last" only (the block table)

Pros:

  • Simple to state.

Cons:

  • Does not say where the week grid starts, so it cannot reproduce Vishay's Q1 end without another rule, which would be an implicit default.

Option 2: stub: { anchor } (the Vishay example)

Pros:

  • States the grid anchor with the same month, day and rule vocabulary as week-based year anchors.

Cons:

  • Leaves implicit which periods absorb the partial weeks; the example assumes Q1 and Q4.

Option 3: An object with both the anchor and which periods absorb the stub

Pros:

  • Fully explicit; covers calendars that put the stub elsewhere.

Cons:

  • More fields to validate; no real calendar in the research needs a non-default stub placement yet.

For D4, the options are: require the pattern to sum to 52 and let the stub periods absorb the remaining days; require it to describe only the whole-week interior periods; or drop the sum rule for fixed-date years and rely on tiling validation.

Decision

Option 3, with one allowed placement in spec version 1. For D4, the pattern sums to 52 and the two stub periods absorb the difference.

Shape. In 0.2, subdivision.stub is an object with two required fields:

  • anchor: {month, day, rule}, with month 1–12, day 1–31 or "last", and rule last, first or nearest, the same vocabulary and meanings as a week_based year's anchor. It has no edge.
  • absorb: "first_and_last", the only value in spec version 1.

The proposal's Vishay example becomes valid by adding "absorb": "first_and_last":

"stub": {
  "anchor": { "month": 3, "day": "last", "rule": "nearest" },
  "absorb": "first_and_last"
}

The anchor is still the proposal's illustrative one.

Meaning. In each fiscal year:

  1. The target date is the first occurrence of (anchor.month, anchor.day) on or after the fiscal year's first day.
  2. The anchor date is the qualifying weekday chosen from the target by rule. The qualifying weekday is the day before week.starts_on, as for edge: "end" in ADR-0028. So every period after the first starts on week.starts_on, and the period grid lines up with the week grid.
  3. Period 1 runs from the fiscal year's first day to the anchor date, inclusive.
  4. Periods 2 to n − 1 are exactly periods[k] weeks each.
  5. Period n runs from the next day to the fiscal year's last day.

D4. periods must sum to 52 inside a fixed_date year too, and WEEKS_SUM_MISMATCH applies there. periods[0] and periods[n − 1] are the nominal lengths of the two stub periods. Validation then checks every fiscal year in the supported range and rejects the definition if either stub period differs from its nominal length by seven days or more, or would be empty. For the illustrative Vishay anchor, a hand calculation over years 1 to 9999 gives Q1 between four days shorter and three days longer than 13 weeks, and Q4 between one day shorter and five days longer; this calculation is not yet a committed check. extra_week is not allowed in a stub year, because there is no 53rd week: the last period absorbs the extra days.

Where stub is valid. Only with subdivision.type: "week_pattern" inside a fixed_date year. Inside a week_based year it is STUB_WITHOUT_FIXED_YEAR, the proposal's code. A week_pattern inside a fixed_date year without stub is MISSING_FIELD at subdivision.stub. The proposal's code also allows stub in a table year; that combination is not decided here (ADR-0022 leaves it open), so 0.2 rejects it with UNSUPPORTED_COMBINATION.

Pattern length. In 0.2, periods may have 4 entries (one period per quarter) as well as 12 or 13, in any year type, so that the proposal's Vishay example can be written as it stands. The rule does not depend on the year type, which keeps the blocks independent (ADR-0005).

What decided it:

  • Option 1 needs a hidden default. Without an anchor, the grid position would have to be inferred, which the decision drivers and AGENTS.md design rule 10 rule out.
  • Option 2 alone leaves the placement implicit. Adding a placement field later as optional would create a silent default, and adding it as required would break every definition written for 0.2. A required field with one allowed value costs one line now, and a later value is an addition, not a breaking change. This is a judgement, not evidence: the research has no calendar with another placement.
  • Option 3 resolves D1 without discarding either source. The block table's first_and_last becomes the value of absorb, and the example's object becomes anchor.
  • The 52-week sum keeps a useful check (D4). Describing only the interior periods would make quarters and extra_week.period count different things in different year types. Dropping the sum would stop catching a mistyped pattern such as [13, 13, 13, 12]. With the sum and the seven-day bound, the pattern's first and last entries keep a meaning that validation enforces.
  • The anchor shape does not depend on Vishay's real anchor. The anchor is data in each definition. Whether Vishay's own definition uses "Saturday nearest 31 March" is a separate, open question about the illustrative example.

Consequences

Good:

  • D1 and D4 are resolved, and a Vishay-style calendar can be written without an implicit rule once 0.2 ships.
  • Every period boundary after the first falls on a week boundary.

Bad:

  • Two required fields where the proposal's example has one. Definitions copied from the proposal fail validation with MISSING_FIELD until absorb is added; the hint can name it.
  • A fixed-date year with the stub at only one end cannot be written until a new absorb value is added by a later ADR.

What 0.2 must implement:

  • The shape, meaning and validation above, in spec/definition-v1.md and the core, with the reserved-list entry for subdivision.stub removed.
  • STUB_WITHOUT_FIXED_YEAR, a code for a stub period outside the seven-day bound or empty, and the widened descriptions of WEEKS_SUM_MISMATCH and INVALID_VALUE for periods, all in the catalogue in spec/api-v1.md in the same pull request. The new code's name is fixed in that change.
  • A committed check in research/verification/ that reproduces the seven-day bound for the illustrative anchor, computed independently of fiscalclock.
  • A cited Vishay conformance vector, which Gate C on the Roadmap requires. Its expected quarter ends must come from Vishay's filings for several years, not from the illustrative anchor and not from running fiscalclock. Until then the Vishay definition stays illustrative.
  • The Definition-Format page updated to the decided shape.

Not decided here:

  • Whether Vishay's real anchor is the Saturday nearest 31 March.
  • stub inside a table year.
  • Any absorb value other than first_and_last.

Links

Clone this wiki locally