diff --git a/CHANGELOG.md b/CHANGELOG.md index 13ea8078..4fa144af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- [#789]: `defmt`: Add support for new time-related display hints + +[#789]: https://github.com/knurling-rs/defmt/pull/789 + ## defmt-decoder v0.3.9, defmt-print v0.3.10 - 2023-10-04 - [#784]: `defmt-decoder`: Prepare `defmt-decoder v0.3.9` release diff --git a/book/src/hints.md b/book/src/hints.md index 79717574..97a67781 100644 --- a/book/src/hints.md +++ b/book/src/hints.md @@ -8,14 +8,18 @@ The hint follows the syntax `:Display` and must come after the type within the b The following display hints are currently supported: -| hint | name | -| :---- | :--------------------------------------------- | -| `:x` | lowercase hexadecimal | -| `:X` | uppercase hexadecimal | -| `:?` | `core::fmt::Debug`-like | -| `:b` | binary | -| `:a` | ASCII | -| `:us` | microseconds (formats integers as time stamps) | +| hint | name | +| :----- | :------------------------------------------------------- | +| `:x` | lowercase hexadecimal | +| `:X` | uppercase hexadecimal | +| `:?` | `core::fmt::Debug`-like | +| `:b` | binary | +| `:a` | ASCII | +| `:ms` | timestamp in seconds (input in milliseconds) | +| `:us` | timestamp in seconds (input in microseconds) | +| `:ts` | timestamp in human-readable time (input in seconds) | +| `:tms` | timestamp in human-readable time (input in milliseconds) | +| `:tus` | timestamp in human-readable time (input in microseconds) | The first 4 display hints resemble what's supported in `core::fmt`, for example: diff --git a/decoder/src/frame.rs b/decoder/src/frame.rs index 12901944..bcc9b72a 100644 --- a/decoder/src/frame.rs +++ b/decoder/src/frame.rs @@ -240,11 +240,25 @@ impl<'t> Frame<'t> { (true, false) => write!(buf, "{x:#0zero_pad$x}")?, (true, true) => write!(buf, "{x:#0zero_pad$X}")?, }, - Some(DisplayHint::Microseconds) => { + Some(DisplayHint::Seconds(TimePrecision::Micros)) => { let seconds = x / 1_000_000; let micros = x % 1_000_000; write!(buf, "{seconds}.{micros:06}")?; } + Some(DisplayHint::Seconds(TimePrecision::Millis)) => { + let seconds = x / 1_000; + let millis = x % 1_000; + write!(buf, "{seconds}.{millis:03}")?; + } + Some(DisplayHint::Time(TimePrecision::Micros)) => { + self.format_time(x, &TimePrecision::Micros, buf)?; + } + Some(DisplayHint::Time(TimePrecision::Millis)) => { + self.format_time(x, &TimePrecision::Millis, buf)?; + } + Some(DisplayHint::Time(TimePrecision::Seconds)) => { + self.format_time(x, &TimePrecision::Seconds, buf)?; + } Some(DisplayHint::Bitflags { name, package, @@ -388,6 +402,54 @@ impl<'t> Frame<'t> { Ok(()) } + fn format_time( + &self, + timestamp: u128, + precision: &TimePrecision, + buf: &mut String, + ) -> Result<(), fmt::Error> { + let div_rem = |x, y| (x / y, x % y); + + let (timestamp, decimals) = match precision { + TimePrecision::Micros => div_rem(timestamp, 1_000_000), + TimePrecision::Millis => div_rem(timestamp, 1_000), + TimePrecision::Seconds => (timestamp, 0), + }; + + let (timestamp, seconds) = div_rem(timestamp, 60); + let (timestamp, minutes) = div_rem(timestamp, 60); + let (timestamp, hours) = div_rem(timestamp, 24); + let days = timestamp; + + if days == 0 { + match precision { + TimePrecision::Micros => write!( + buf, + "{hours:0>2}:{minutes:0>2}:{seconds:0>2}.{decimals:0>6}" + ), + TimePrecision::Millis => write!( + buf, + "{hours:0>2}:{minutes:0>2}:{seconds:0>2}.{decimals:0>3}" + ), + TimePrecision::Seconds => write!(buf, "{hours:0>2}:{minutes:0>2}:{seconds:0>2}"), + } + } else { + match precision { + TimePrecision::Micros => write!( + buf, + "{days}:{hours:0>2}:{minutes:0>2}:{seconds:0>2}.{decimals:0>6}" + ), + TimePrecision::Millis => write!( + buf, + "{days}:{hours:0>2}:{minutes:0>2}:{seconds:0>2}.{decimals:0>3}" + ), + TimePrecision::Seconds => { + write!(buf, "{days}:{hours:0>2}:{minutes:0>2}:{seconds:0>2}") + } + } + } + } + fn format_iso8601( &self, timestamp: u64, @@ -395,6 +457,9 @@ impl<'t> Frame<'t> { buf: &mut String, ) -> Result<(), fmt::Error> { let format = match precision { + TimePrecision::Micros => format_description!( + "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z" + ), TimePrecision::Millis => format_description!( "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z" ), @@ -403,6 +468,7 @@ impl<'t> Frame<'t> { } }; let date_time = OffsetDateTime::from_unix_timestamp_nanos(match precision { + TimePrecision::Micros => timestamp as i128 * 1_000, TimePrecision::Millis => timestamp as i128 * 1_000_000, TimePrecision::Seconds => timestamp as i128 * 1_000_000_000, }) diff --git a/firmware/qemu/src/bin/hints.out b/firmware/qemu/src/bin/hints.out index 0716bdca..735b9457 100644 --- a/firmware/qemu/src/bin/hints.out +++ b/firmware/qemu/src/bin/hints.out @@ -59,3 +59,8 @@ 0.000058 INFO Debug 10 0.000059 INFO ISO8601 2021-04-20T09:23:44.804Z 0.000060 INFO ISO8601 +53271-03-27T11:46:44Z +0.000061 INFO Sec ms 1234.567 +0.000062 INFO Sec us 1.234567 +0.000063 INFO Time s 14:06:56:07 +0.000064 INFO Time ms 00:20:34.567 +0.000065 INFO Time us 00:00:01.234567 diff --git a/firmware/qemu/src/bin/hints.rs b/firmware/qemu/src/bin/hints.rs index 1a9d9748..50f13f19 100644 --- a/firmware/qemu/src/bin/hints.rs +++ b/firmware/qemu/src/bin/hints.rs @@ -127,6 +127,15 @@ fn main() -> ! { defmt::info!("ISO8601 {:iso8601ms}", 1618910624804_u64); defmt::info!("ISO8601 {:iso8601s}", 1618910624804_u64); + // Timestamps with seconds + defmt::info!("Sec ms {:ms}", 1_234_567_u64); + defmt::info!("Sec us {:us}", 1_234_567_u64); + + // Timestamps with time format + defmt::info!("Time s {:ts}", 1_234_567_u64); + defmt::info!("Time ms {:tms}", 1_234_567_u64); + defmt::info!("Time us {:tus}", 1_234_567_u64); + loop { debug::exit(debug::EXIT_SUCCESS) } diff --git a/parser/src/display_hint.rs b/parser/src/display_hint.rs index dd599c69..9e7fa583 100644 --- a/parser/src/display_hint.rs +++ b/parser/src/display_hint.rs @@ -21,8 +21,10 @@ pub enum DisplayHint { Ascii, /// `:?` Debug, - /// `:us`, formats integers as timestamps in microseconds - Microseconds, + /// `:us` `:ms`, formats integers as timestamps in seconds + Seconds(TimePrecision), + /// `:tus` `:tms` `:ts`, formats integers as human-readable time + Time(TimePrecision), /// `:iso8601{ms,s}`, formats integers as timestamp in ISO8601 date time format ISO8601(TimePrecision), /// `__internal_bitflags_NAME` instructs the decoder to print the flags that are set, instead of @@ -75,7 +77,11 @@ impl DisplayHint { Some(match s { "" => DisplayHint::NoHint { zero_pad }, - "us" => DisplayHint::Microseconds, + "us" => DisplayHint::Seconds(TimePrecision::Micros), + "ms" => DisplayHint::Seconds(TimePrecision::Millis), + "tus" => DisplayHint::Time(TimePrecision::Micros), + "tms" => DisplayHint::Time(TimePrecision::Millis), + "ts" => DisplayHint::Time(TimePrecision::Seconds), "a" => DisplayHint::Ascii, "b" => DisplayHint::Binary { alternate, @@ -99,9 +105,10 @@ impl DisplayHint { } } -/// Precision of ISO8601 datetime +/// Precision of timestamp #[derive(Clone, Debug, Eq, PartialEq)] pub enum TimePrecision { + Micros, Millis, Seconds, } diff --git a/parser/src/tests.rs b/parser/src/tests.rs index 97662407..4fc7735c 100644 --- a/parser/src/tests.rs +++ b/parser/src/tests.rs @@ -31,6 +31,11 @@ fn all_parse_param_cases( #[case(":#x", DisplayHint::Hexadecimal { alternate: true, uppercase: false, zero_pad: 0 })] #[case(":X", DisplayHint::Hexadecimal { alternate: false, uppercase: true, zero_pad: 0 })] #[case(":#X", DisplayHint::Hexadecimal { alternate: true, uppercase: true, zero_pad: 0 })] +#[case(":ms", DisplayHint::Seconds(TimePrecision::Millis))] +#[case(":us", DisplayHint::Seconds(TimePrecision::Micros))] +#[case(":ts", DisplayHint::Time(TimePrecision::Seconds))] +#[case(":tms", DisplayHint::Time(TimePrecision::Millis))] +#[case(":tus", DisplayHint::Time(TimePrecision::Micros))] #[case(":iso8601ms", DisplayHint::ISO8601(TimePrecision::Millis))] #[case(":iso8601s", DisplayHint::ISO8601(TimePrecision::Seconds))] #[case(":?", DisplayHint::Debug)]