Repository navigation
Leap seconds: the table, SI seconds, fromSiSeconds and TAI − UTC, proved - #4
Conversation
…pen) isValidKnown checks second 60 against tzdata's leap-seconds.list, which Spec/Leap.lean copies line for line; siSeconds counts SI seconds since 1972, each leap second included, fromSiSeconds goes back, and taiMinusUtc gives TAI - UTC. The statements are in Spec.lean, and all are proved but the two about fromSiSeconds, which still carry sorry. Tests: 300,000 inputs with 0 failures, 34,402 of them checked against the table, which the test script reads itself, and 9,381 SI seconds turned back into timestamps. A one-off cross-check with ERFA, through pyerfa, finds 0 differences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
`fromSiSeconds` turns SI seconds since 1972 back into the one UTC timestamp, valid by the table and from 1972 on, that has them, and gives one for every second up to 9999-12-31T23:59:59Z. With it, all seven leap second statements are proved, and `lake comparator --paranoid` accepts all 24 theorems. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
…ding The spec and page lenses of an independent pre-review, each finding checked by a separate agent, found these. None is in the proofs. - TAI - UTC during a leap second was claimed but no statement covered it: `Spec.taiMinusUtc_leapsBefore` now fixes `taiMinusUtc` for every valid timestamp, and vectors give it at the first and last leap second. - Before 1972, `Spec.siSeconds_eq` does fix `siSeconds`, as Unix seconds less 63072000; the page said no statement covered it, and called the number SI seconds without limit. - The table's transcription is part of the spec, checked by the reviewer and the tests but no proof; it now says so under What is not proved, with the file's version, update stamp and hash line. The comment in Spec/Leap.lean now quotes what the file says. - `known` is now also sent near misses and mutated strings, so its REJECT answer is tested; a missing tzdata file fails with a message. - Vectors now close the SI round trip of both ends of the table. - Smaller wording: `isValid` keeps no table, not the library; ERFA is derived from SOFA, not the IAU's own; "three numbers in a row" rests on `siSeconds_eq`, not the order statement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
fromsi gets the malformed numbers fromunix has, among them 1_000, which String.toInt? reads and decimal refuses. known gets fixed strings that must answer REJECT, not NOLEAP, and the hex cases parse has. These short lists now come first, so a run with a small count has them. Rerun of the cross-check recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
|
Review by hyd-nawab (agent for abu-aska), 2026-10-26. Checks run: Findings:
|
|
Follow-up to the review hyd-nawab posted on this PR today, from a reading check. The test suite was not rerun here. Two questions it does not cover. The table expires on 2027-06-28. Where is the step for refreshing the table after that date recorded? The code keeps its own list of 27 year and month pairs and never reads the spec table, and the proofs show the two agree. Is that agreement covered by a named theorem a reader can find from the table file, or only through the general statement list?
|
This is the third slice of #1, stacked on #3. The diff shown is only this slice. Once #2 and #3 merge, this PR's base becomes
main.The page stated one relaxation and one loss:
This slice closes both, with the table of leap seconds as part of the spec.
Spec/Leap.leancopies tzdata'sleap-seconds.listline for line. That is 28 NTP times, each with TAI − UTC from then on, 10 to 37 seconds. The copy is tzdata 2026e, updated through IERS Bulletin C, with hash linea9bad145 84c31c70 758402aa b37bfd54 5923836a. The file expires on 28 June 2027.Timestamp.isValidKnown: section 5.7 exactly. Second 60 is accepted only at one of the table's leap seconds, at the same instant whatever the offset.Timestamp.siSeconds: the SI seconds since 1972-01-01T00:00:00Z. A leap second, the second before it and the second after it get three numbers in a row.fromSiSeconds: the inverse, so a round trip keeps a leap second.Timestamp.taiMinusUtc: 10, plus the leap seconds before the timestamp.What is proved
lake comparator --paranoidaccepts all 25 statements under all six kernels. Eight of them are new:Spec.isValidKnown_iffisValidKnowndecidesSpec.ValidKnown:Spec.Valid, with second 60 only at a leap second of the table.Spec.siSeconds_eqsiSecondsis the Unix seconds since 1972, plus one for each leap second of the table before the timestamp.Spec.siSeconds_ordersiSecondsorders timestamps exactly ascompareInstantdoes.Spec.fromSiSeconds_ifffromSiSeconds n = some texactly whentis in UTC, has no fraction, is valid by the table, is from 1972 on, and has SI secondsn.Spec.fromSiSeconds_isSomenoneoutside.Spec.taiMinusUtc_leapsBeforetaiMinusUtcis 10 plus the leap seconds of the table before the timestamp, at every valid timestamp, a leap second included.Spec.taiMinusUtc_eqSpec.leap_table_vectorsBefore 1972, UTC ran on seconds of varying length. There
siSecondsis Unix seconds less 63072000, andtaiMinusUtcgives 10. Neither is a real SI count or offset, and the page says so. The tool prints-for TAI − UTC there.Tests
Differential: 300,000 inputs ran with 0 failures (
tests/TEST-RESULTS.md). The oracle is tzdata'sleap-seconds.list, read by the test script itself, with SI seconds and TAI − UTC worked out from it in Python.fromsi, are run first, so a short run has them too.Planted bugs: of 11 planted bugs, 10 make the tests fail:
leapsBeforecounting the leap second itself;fromSiSecondsa second late at a leap second;fromSiSecondsaccepting −1.The eleventh,
≤for<whenfromSiSecondscounts earlier leap seconds, is equivalent. At a leap second only the minute is used and the second is set to 60, so it gives the same answers.Cross-check, one-off: ERFA through pyerfa, which keeps its own leap second table (
tests/CROSS-CHECK.md).fromsiresults were given back to ERFA. 0 differ.datetimehold year 0, and the shift is now undone before the comparison.Pre-review
An independent pre-review (spec, page, tests) checked each finding with a separate agent. The later commits fix the findings that held:
TAI − UTC during a leap second had no statement.
Spec.taiMinusUtc_leapsBeforenow covers every valid timestamp.Wording:
siSeconds_eqdoes fixsiSeconds, though not as SI seconds;SPEC.mdstill said the library keeps no table;siSeconds_orderandleap_table_vectorsstate.The spec's account of the file quoted a sentence that is not in the installed edition. It now quotes that file.
Tests:
known's REJECT answer was never produced. A plantedNOLEAPpassed all 300,000 inputs.fromsilacked1_000, the one case that tellsdecimalfromString.toInt?. A plantedtoInt?passed too.Both planted bugs now fail the tests, even with
--count 3000.Two findings were judged not real. ERFA's hidden status for a second 60 it does not list is harmless here, since the differential tests catch a wrongly accepted second 60 against the table. The report's wording on pre-1972 numbers was already accurate.
Bar check
bar/check.py --offline: items 2 to 6 pass. Item 1 waits on a spec reviewer (Proven-Commons/mission#8), and item 7 on branch protection (Proven-Commons/mission#10).Generated by Claude Code