Skip to content

Add %-.f to print the shortest exact fraction - #1814

Closed
KannarFr wants to merge 1 commit into
chronotope:mainfrom
KannarFr:nanosecond-trimmed
Closed

Add %-.f to print the shortest exact fraction#1814
KannarFr wants to merge 1 commit into
chronotope:mainfrom
KannarFr:nanosecond-trimmed

Conversation

@KannarFr

Copy link
Copy Markdown

Fixes #165.

%.f rounds a subsecond value up to a whole group of 3, 6 or 9 digits, so half a second prints as .500. to_rfc3339_opts(SecondsFormat::AutoSi, _) and a derived Serialize on DateTime do the same. There is currently no way to print .5.

Several ISO 8601 profiles require the shortest form. The one that motivated this is Java's DateTimeFormatter.ISO_OFFSET_DATE_TIME (and circe's Encoder.encodeOffsetDateTime, which delegates to it): a Rust service replacing a JVM one cannot reproduce the bytes its peer emits, which matters when two implementations' payloads are compared field-by-field during a migration. #165 opens with the same need from X.690/ASN.1.

The specifier

The thread stalled on spelling, between %-.f and %#.f. This implements %-.f, on the reasoning that trailing zeros in a fraction are padding, so the - modifier the numeric specifiers already carry means here exactly what it means everywhere else in chrono. # in C and in std::fmt selects a more decorated form ({:#x}, {:#?}), which is the opposite of what is wanted. If the maintainers prefer %#.f, the change is one match arm in StrftimeItems::next plus an entry in HAVE_ALTERNATES — happy to switch.

Behaviour

input %.f %-.f
0 ns (nothing) (nothing)
500_000_000 ns .500 .5
123_000_000 ns .123 .123
10 ns .000000010 .00000001
1 ns .000000001 .000000001

%0.f, %_.f, %-.3f and %-.9f remain errors, exactly as before — only Pad::None on a bare %.f is given a meaning, and the test pins that.

Compatibility

  • Fixed is already #[non_exhaustive], so Fixed::NanosecondTrimmed is semver-compatible; this targets main.
  • No existing format string changes meaning: %-.f was a hard error before this commit.
  • Parsing is untouched. NanosecondTrimmed joins the Nanosecond arm in parse.rs because trimming is a property of printing only, and a parser cannot be "trimmed" — so a trimmed fraction and its padded spelling read back to the same instant. The test asserts a print/parse round-trip over the interesting nanosecond values.

Not addressed here

SecondsFormat has no trimmed variant, so to_rfc3339_opts still cannot emit .5. That looked like it belonged with #1736 (Fixed::RFC3339Secs(SecondsFormat)) rather than in this change; say the word and I'll add it.

Checks

cargo test with --features "default unstable-locales rkyv-32 rkyv-validation serde arbitrary", plus --no-default-features with each of {}, alloc, unstable-locales, alloc,unstable-locales, now; cargo clippy --all-targets -- -D warnings; cargo fmt --check; RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps; and cargo +1.62.0 check --lib for the MSRV.

`%.f` rounds a subsecond value up to a whole group of 3, 6 or 9 digits, so
half a second prints as `.500`. `to_rfc3339_opts(SecondsFormat::AutoSi, _)`
and a derived `Serialize` do the same. There is currently no way to print
`.5`, which several ISO 8601 profiles require -- among them Java's
`DateTimeFormatter.ISO_OFFSET_DATE_TIME`, so a Rust service cannot reproduce
the bytes a JVM peer emits.

Trailing zeros in a fraction are padding, so this reuses the `-` modifier the
numeric specifiers already take: `%-.f` prints the shortest fraction that is
still exact, and nothing at all when the value is zero. `%0.f`, `%_.f` and
`%-.3f` remain errors, as before.

Parsing is untouched: `Fixed::NanosecondTrimmed` reads exactly like
`Fixed::Nanosecond`, since trimming is a property of printing only.

Fixes chronotope#165.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

Provide more ways to format the fractional portion of a second (required for X.690/ASN.1)

1 participant