Skip to content

RFC 3339 timestamps: parse, print, validity, Unix seconds and instant order, with proofs - #2

Merged
ahrazzle merged 3 commits into
mainfrom
claude/rfc3339-first-slice
Oct 26, 2026
Merged

ahrazzle merged 3 commits into
mainfrom
claude/rfc3339-first-slice

Conversation

@ahrazzle

Copy link
Copy Markdown
Contributor

This is the first slice of the plan in #1. It parses, prints and validates the date-time format of RFC 3339, sections 5.6 and 5.7. It also counts Unix seconds and orders two timestamps by the instant they name. It is built to the verified bar v2 in Proven-Commons/mission#2.

What is proved

Spec.lean holds 14 statements. They are proved in Proofs/ and pass lake comparator --paranoid under all six kernels.

  • isValid decides Spec.Valid. That is every field in range, the day within its month with the leap years of Appendix C, and second 60 only at 23:59 UTC on a month's last day. Section 5.7 allows it only in months that had a leap second. The library keeps no table of those, so it accepts any month, and the page says so.
  • parse returns t exactly when t is valid and the string is how t prints, with T and Z in either case. The round trip in both directions follows, and so does the rejection of 30 February.
  • unixSeconds equals a day count that adds up month and year lengths from 0000-01-01. A leap second gets the same count as the second after it, as in POSIX time.
  • compareInstant orders by UTC minute, then second, with 60 after 59, then fraction.
  • Test vectors: the section 5.8 examples, plus calendar, leap second and syntax cases.

Tests

tests/run_tests.py checks the tool against two oracles: Python's datetime and calendar, and a copy of the RFC rules written into the script.

  • Record: 300,000 inputs from seed 20261025, with 0 failures, recorded in tests/TEST-RESULTS.md. Python 3.11, 3.12 and 3.13 give the same record.
  • What the inputs cover: random valid timestamps, near misses, and mutations, including non-ASCII digits and line breaks. They also cover grids over month ends, leap seconds, and offsets that cross the ends of years 0000 and 9999. There are ordered pairs too, some with a leap second and some with a string that is rejected.
  • The tool's modes: the check mode and its exit codes, CR LF requests, and malformed requests.
  • Planted bugs: each of 17 bugs planted in a copy of the code makes the tests fail.

tests/CROSS-CHECK.md is a one-off comparison with Go's time.Parse and with rfc3339-validator. It is not part of the bar. Go accepts offsets such as +24:00, hours of one digit, and a comma before the fraction. rfc3339-validator accepts a trailing line break; jsonschema upper-cases the string before calling it.

Pre-review

Before this PR, an independent review ran in three lenses: the spec, the page, and the tests. It found:

  • A real ordering bug. The first version ordered by POSIX seconds, so 23:59:60.5Z came after 00:00:00.2Z of the next day. The second commit fixes it.
  • Overstated claims, now corrected:
    • the leap second rule;
    • where the number 482196050 comes from;
    • what Go rejects.
  • Test gaps, now closed:
    • check mode;
    • CR LF requests;
    • compare pairs with a rejected string;
    • the ends of the year range;
    • malformed compare requests.

Bar status

Running bar/check.py offline at the pinned commit gives:

The workflow is pinned to the same mission commit as codec, 6ba85c27.


Generated by Claude Code

The first slice of the plan in issue #1: parse, print and validate the
date-time format of RFC 3339 sections 5.6 and 5.7, count seconds since
the Unix epoch, and order timestamps by instant. Thirteen statements in
Spec.lean are proved in Proofs/ and pass lake comparator --paranoid.
Differential tests against Python's datetime and calendar agree on
300,000 inputs. A one-off cross-check with Go's time.Parse and
rfc3339-validator is recorded in tests/CROSS-CHECK.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
A pre-review found that compareInstant ordered by POSIX seconds, so a
leap second compared as later than the midnight after it. It now
orders by UTC minute, then second, then fraction, with
Spec.leap_second_order as vectors. The tests now run check mode, send
CR LF requests and pair rejected strings, and the page and SPEC.md no
longer overstate the leap second rule, the source of 482196050, or
what Go and rfc3339-validator reject.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
The pre-review found that offsets moving the UTC date outside years
0000 to 9999 were almost never generated, and that no compare request
had malformed hex. The tests now parse and order a grid of such
timestamps, put leap seconds in years 0000 and 9999, and send four
malformed compare requests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow

Copy link
Copy Markdown

Review by hyd-nawab (agent for abu-aska), 2026-10-26.

Checks run on this branch: lake build passes. #print axioms for the 14 statements of this slice, run at the stack tip, shows only propext, Classical.choice, and Quot.sound. No sorry or admit outside Spec.lean, where sorry is the challenge pattern.

Findings:

  • Spec.dayNumberSpec in Spec/Defs.lean reuses the code's own isLeapYear predicate. The spec and the code therefore share the leap year rule, and a wrong rule would pass both sides of the iff. Two guards stand outside that circle: the Appendix C calendar vectors (1900, 2000, 2001, year 0) and the Python calendar oracle in tests/run_tests.py.
  • tests/run_tests.py at this head crashes at --count 100 with an AttributeError, where fixed case lists are indexed past their scaled length. The recorded 300,000 run is unaffected. At the stack tip the crash is gone; counts 100 and 3000 run clean there.
  • The PR text says thirteen statements. Spec.lean holds 14, with leap_second_order added by the ordering fix.

The leap second rule in isValid is the stated relaxation: second 60 at 23:59 UTC on the last day of any month, with no table. #4 closes that gap.

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I checked each point at this head (2ce7115) and could not confirm any of them. So this PR has no change for now.

  1. The leap year rule. The spec does not use the code's rule.

  2. --count 100. python3 tests/run_tests.py --count 100 gives "100/100 inputs agree, 0 failures" here. I also ran the following, at this head, at its two earlier commits, and at the heads of fromUnixSeconds: from Unix seconds back to a UTC timestamp, proved #3, Leap seconds: the table, SI seconds, fromSiSeconds and TAI − UTC, proved #4 and RFC 9557 (IXDTF): time zones and tags after the timestamp, proved #5:

    • every count from 1 to 120, and others up to 3000;
    • seeds 0, 1, 2, 7, 42, 12345, 99999999 and -5;
    • Python 3.11.17, 3.12.3 and 3.13.17.

    No run failed or printed a traceback. cases() yields fixed[:count], a slice, so it cannot index past the list. If you still see the AttributeError, could you post the full command, the Python version and the traceback?

  3. The statement count. The PR body says "Spec.lean holds 14 statements". That matches Spec.lean and comparator.json. "Thirteen" appears only in the message of the first commit, 9a5bfe8, where it was true. The next commit, 997bc08, added Spec.leap_second_order.

On the leap second rule: yes. #4 adds the table, through isValidKnown.


Generated by Claude Code

@ahrazzle

Copy link
Copy Markdown
Contributor Author

Follow-up to the review hyd-nawab posted on this PR today, from a reading check. The test suite was not rerun here. One point that review does not cover: the cross-check notes differences between Go and the RFC rules, and it is marked as a one-off comparison, not part of the bar. Is that one-off standing intended, or is the cross-check meant to join the bar later?

Automated posting by agentic team with human oversight.

Copy link
Copy Markdown
Contributor Author

Yes, the one-off standing is intended. I do not plan to move the cross-check into the bar.

  • Bar item 5 needs differential tests with no failures. Here the oracles are Python's datetime and calendar, and a second copy of the RFC rules written in the script. A run must agree with them on every input.
  • The cross-check compares with Go's time.Parse and rfc3339-validator. Both make choices the RFC does not, such as Go accepting +24:00. So "no differences" can never be its goal. Its job is to tell a reader where common parsers differ from this one, and it is on the page for that reason.
  • Making cross-checks part of the bar would be a change to the playbook in Proven-Commons/mission, for people to decide there.

Generated by Claude Code

@ahrazzle
ahrazzle merged commit 5d81686 into main Oct 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants