Artifact 5a1adf7f9cca379898eba40264e53099ed2b2c55a28c4d5407dfc88ebfce50c2:
- File docs/rfcs/erratum/artifact-format-01-s4.6.4-julian-day-grammar.md — part of check-in [100351e26c] at 2026-10-12 18:59:29 on branch trunk — Six errata against the -01 drafts, applied: Julian-day form, cancel cards from the parent, date-tag event time, truncated payload, file card after a refused push, errors N (user: dmestas, size: 3501) [annotate] [blame] [check-ins using]
Erratum — draft-fossil-artifact-format-01 §4.6.4
| Document | draft-fossil-artifact-format-01 |
| Section | 4.6.4 — Timestamps (form 5, the bare Julian day number) |
| Type | Technical (the lexical form and the out-of-range outcome are unstated) |
| Status | Applied in draft-fossil-artifact-format-01 (2026-10-12) |
| Reported | 2026-10-12 |
| Submitter | Dan Mestas |
| Found via | A reader implementing §4.6.4 from the text alone |
1. Summary
Form 5 says a timestamp token may be "a bare decimal number, treated as a Julian day number when its value r satisfies 0 < r < 5373484.5". It does not say what spellings count as a bare decimal number, and it gives no outcome for a number outside that range: "treated as ... when" states a condition without saying what happens when the condition fails.
2. Location
§4.6.4, item 5, and the "Finally:" paragraph after the field-constraint paragraph.
3. The reference behavior
Verified against Fossil 2.28 by reading src/manifest.c and the bundled
SQLite.
The D and E card tokens are handed unchanged to the storage engine's
julianday() function (manifest.c:644, :660), and the card is rejected
when the result is not positive. That function recognizes a numeric token by
its text-to-float conversion (sqlite3AtoF, parseDateOrTime): an optional
sign, digits with an optional fraction, and an optional decimal exponent, in
the forms [+-]digits[E[+-]digits], [+-]digits.[digits][E[+-]digits] and
[+-].digits[E[+-]digits]. Hexadecimal, infinity and NaN spellings are not
numbers. The value is converted to whole milliseconds, (i64)(r*86400000 + 0.5)
(setRawDateNumber), and kept only when that lies in
0 .. 464269060799999 (validJulianDay); otherwise the function yields no
value and Fossil rejects the card. A value that rounds to 0 milliseconds
passes the engine's check but fails Fossil's positivity test. The accepted
range is therefore 0 < r < 5373484.5 measured after rounding to the
millisecond: a positive value below about 5.8e-9 is rejected, and so is a
value within about 6e-9 below 5373484.5.
4. Impact
Observable. Two readers can disagree on whether 2.45e6, +2451545.5 or
.5 is a valid D card, and on whether 5373484.5 is rejected or read some
other way. Fossil accepts the first three and rejects the fourth.
5. Correction
5. A bare decimal number, treated as a Julian day number when its
- value r satisfies 0 < r < 5373484.5.
+ value r satisfies 0 < r < 5373484.5. The number is an optional
+ sign, then digits with an optional fraction (at least one digit
+ in all), then an optional decimal exponent introduced by E or e
+ with an optional sign, such as 2451545.5, +2451545, .5 or 2.45e6.
+ No other numeric spelling is recognized. A number whose value
+ is zero, negative, or 5373484.5 or more MUST be rejected. The
+ check is made on the value rounded to the nearest millisecond,
+ so a positive value that rounds to zero is rejected, as is a
+ value just below 5373484.5 that rounds up to it.
6. Rationale
The grammar is the engine's own, stated so a reader need not consult it. The rejection outcome is what Fossil does; without it the text leaves the over-limit case open.
7. Verification
- Source: Fossil 2.28
src/manifest.c:642-646and:656-662; bundled SQLiteparseDateOrTime,setRawDateNumber,validJulianDay, and thesqlite3AtoFcontract.