diff --git a/CHANGELOG.md b/CHANGELOG.md index 69c503a4..c8b8fabe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Bug fixes: * [#155](https://github.com/BurntSushi/jiff/issues/155): Relax `strftime` format strings from ASCII-only to all of UTF-8. +* [#188](https://github.com/BurntSushi/jiff/issues/188): +`Span` and `SignedDuration` now use uppercase unit designator labels in their +default ISO 8601 `Display` implementation. 0.1.18 (2024-12-31) diff --git a/COMPARE.md b/COMPARE.md index 6ebf29c3..40543858 100644 --- a/COMPARE.md +++ b/COMPARE.md @@ -328,7 +328,7 @@ use jiff::{Span, ToSpan}; fn main() -> anyhow::Result<()> { let span = 5.years().months(2).days(1).hours(20); let json = serde_json::to_string_pretty(&span)?; - assert_eq!(json, "\"P5y2m1dT20h\""); + assert_eq!(json, "\"P5Y2M1DT20H\""); let got: Span = serde_json::from_str(&json)?; assert_eq!(got, span); diff --git a/src/civil/date.rs b/src/civil/date.rs index 5374edf2..165fb8e3 100644 --- a/src/civil/date.rs +++ b/src/civil/date.rs @@ -1635,11 +1635,11 @@ impl Date { /// /// // The default limits durations to using "days" as the biggest unit. /// let span = d1.until(d2)?; - /// assert_eq!(span.to_string(), "P8456d"); + /// assert_eq!(span.to_string(), "P8456D"); /// /// // But we can ask for units all the way up to years. /// let span = d1.until((Unit::Year, d2))?; - /// assert_eq!(span.to_string(), "P23y1m24d"); + /// assert_eq!(span.to_string(), "P23Y1M24D"); /// /// # Ok::<(), Box>(()) /// ``` diff --git a/src/civil/datetime.rs b/src/civil/datetime.rs index 046e0811..8e736a19 100644 --- a/src/civil/datetime.rs +++ b/src/civil/datetime.rs @@ -1775,11 +1775,11 @@ impl DateTime { /// /// // The default limits durations to using "days" as the biggest unit. /// let span = dt1.until(dt2)?; - /// assert_eq!(span.to_string(), "P8456dT12h5m29.9999965s"); + /// assert_eq!(span.to_string(), "P8456DT12H5M29.9999965S"); /// /// // But we can ask for units all the way up to years. /// let span = dt1.until((Unit::Year, dt2))?; - /// assert_eq!(span.to_string(), "P23y1m24dT12h5m29.9999965s"); + /// assert_eq!(span.to_string(), "P23Y1M24DT12H5M29.9999965S"); /// # Ok::<(), Box>(()) /// ``` /// diff --git a/src/civil/time.rs b/src/civil/time.rs index f085cdec..15ee4c51 100644 --- a/src/civil/time.rs +++ b/src/civil/time.rs @@ -1198,12 +1198,12 @@ impl Time { /// /// // The default limits spans to using "hours" as the biggest unit. /// let span = t1.until(t2)?; - /// assert_eq!(span.to_string(), "PT12h5m29.9999965s"); + /// assert_eq!(span.to_string(), "PT12H5M29.9999965S"); /// /// // But we can ask for smaller units, like capping the biggest unit /// // to minutes instead of hours. /// let span = t1.until((Unit::Minute, t2))?; - /// assert_eq!(span.to_string(), "PT725m29.9999965s"); + /// assert_eq!(span.to_string(), "PT725M29.9999965S"); /// /// # Ok::<(), Box>(()) /// ``` diff --git a/src/fmt/friendly/mod.rs b/src/fmt/friendly/mod.rs index cb36b741..b47a909b 100644 --- a/src/fmt/friendly/mod.rs +++ b/src/fmt/friendly/mod.rs @@ -77,11 +77,11 @@ format when using the `std::fmt::Display` trait implementation: use jiff::{SignedDuration, ToSpan}; let span = 2.months().days(35).hours(2).minutes(30); -assert_eq!(format!("{span}"), "P2m35dT2h30m"); // ISO 8601 +assert_eq!(format!("{span}"), "P2M35DT2H30M"); // ISO 8601 assert_eq!(format!("{span:#}"), "2mo 35d 2h 30m"); // "friendly" let sdur = SignedDuration::new(2 * 60 * 60 + 30 * 60, 123_456_789); -assert_eq!(format!("{sdur}"), "PT2h30m0.123456789s"); // ISO 8601 +assert_eq!(format!("{sdur}"), "PT2H30M0.123456789S"); // ISO 8601 assert_eq!(format!("{sdur:#}"), "2h 30m 123ms 456µs 789ns"); // "friendly" ``` @@ -467,10 +467,11 @@ P1Y2M3DT4H59M1.1S P1y2m3dT4h59m1.1S ``` -When all of the unit designators are capital letters in particular, everything -runs together and it's hard for the eye to distinguish where digits stop and -letters begin. Using lowercase letters for unit designators helps somewhat, -but this is an extension to ISO 8601 that isn't broadly supported. +When all of the unit designators are capital letters in particular (which +is the default), everything runs together and it's hard for the eye to +distinguish where digits stop and letters begin. Using lowercase letters for +unit designators helps somewhat, but this is an extension to ISO 8601 that +isn't broadly supported. The "friendly" format resolves both of these problems by permitting sub-second components and allowing the use of whitespace and longer unit designator labels diff --git a/src/fmt/friendly/parser.rs b/src/fmt/friendly/parser.rs index 60b6373a..e49c9056 100644 --- a/src/fmt/friendly/parser.rs +++ b/src/fmt/friendly/parser.rs @@ -975,78 +975,78 @@ mod tests { fn parse_span_basic() { let p = |s: &str| SpanParser::new().parse_span(s).unwrap(); - insta::assert_snapshot!(p("5 years"), @"P5y"); - insta::assert_snapshot!(p("5 years 4 months"), @"P5y4m"); - insta::assert_snapshot!(p("5 years 4 months 3 hours"), @"P5y4mT3h"); - insta::assert_snapshot!(p("5 years, 4 months, 3 hours"), @"P5y4mT3h"); + insta::assert_snapshot!(p("5 years"), @"P5Y"); + insta::assert_snapshot!(p("5 years 4 months"), @"P5Y4M"); + insta::assert_snapshot!(p("5 years 4 months 3 hours"), @"P5Y4MT3H"); + insta::assert_snapshot!(p("5 years, 4 months, 3 hours"), @"P5Y4MT3H"); - insta::assert_snapshot!(p("01:02:03"), @"PT1h2m3s"); - insta::assert_snapshot!(p("5 days 01:02:03"), @"P5dT1h2m3s"); + insta::assert_snapshot!(p("01:02:03"), @"PT1H2M3S"); + insta::assert_snapshot!(p("5 days 01:02:03"), @"P5DT1H2M3S"); // This is Python's `str(timedelta)` format! - insta::assert_snapshot!(p("5 days, 01:02:03"), @"P5dT1h2m3s"); - insta::assert_snapshot!(p("3yrs 5 days 01:02:03"), @"P3y5dT1h2m3s"); - insta::assert_snapshot!(p("3yrs 5 days, 01:02:03"), @"P3y5dT1h2m3s"); + insta::assert_snapshot!(p("5 days, 01:02:03"), @"P5DT1H2M3S"); + insta::assert_snapshot!(p("3yrs 5 days 01:02:03"), @"P3Y5DT1H2M3S"); + insta::assert_snapshot!(p("3yrs 5 days, 01:02:03"), @"P3Y5DT1H2M3S"); insta::assert_snapshot!( p("3yrs 5 days, 01:02:03.123456789"), - @"P3y5dT1h2m3.123456789s", + @"P3Y5DT1H2M3.123456789S", ); - insta::assert_snapshot!(p("999:999:999"), @"PT999h999m999s"); + insta::assert_snapshot!(p("999:999:999"), @"PT999H999M999S"); } #[test] fn parse_span_fractional() { let p = |s: &str| SpanParser::new().parse_span(s).unwrap(); - insta::assert_snapshot!(p("1.5hrs"), @"PT1h30m"); - insta::assert_snapshot!(p("1.5mins"), @"PT1m30s"); - insta::assert_snapshot!(p("1.5secs"), @"PT1.5s"); - insta::assert_snapshot!(p("1.5msecs"), @"PT0.0015s"); - insta::assert_snapshot!(p("1.5µsecs"), @"PT0.0000015s"); + insta::assert_snapshot!(p("1.5hrs"), @"PT1H30M"); + insta::assert_snapshot!(p("1.5mins"), @"PT1M30S"); + insta::assert_snapshot!(p("1.5secs"), @"PT1.5S"); + insta::assert_snapshot!(p("1.5msecs"), @"PT0.0015S"); + insta::assert_snapshot!(p("1.5µsecs"), @"PT0.0000015S"); - insta::assert_snapshot!(p("1d 1.5hrs"), @"P1dT1h30m"); - insta::assert_snapshot!(p("1h 1.5mins"), @"PT1h1m30s"); - insta::assert_snapshot!(p("1m 1.5secs"), @"PT1m1.5s"); - insta::assert_snapshot!(p("1s 1.5msecs"), @"PT1.0015s"); - insta::assert_snapshot!(p("1ms 1.5µsecs"), @"PT0.0010015s"); + insta::assert_snapshot!(p("1d 1.5hrs"), @"P1DT1H30M"); + insta::assert_snapshot!(p("1h 1.5mins"), @"PT1H1M30S"); + insta::assert_snapshot!(p("1m 1.5secs"), @"PT1M1.5S"); + insta::assert_snapshot!(p("1s 1.5msecs"), @"PT1.0015S"); + insta::assert_snapshot!(p("1ms 1.5µsecs"), @"PT0.0010015S"); - insta::assert_snapshot!(p("1s2000ms"), @"PT3s"); + insta::assert_snapshot!(p("1s2000ms"), @"PT3S"); } #[test] fn parse_span_boundaries() { let p = |s: &str| SpanParser::new().parse_span(s).unwrap(); - insta::assert_snapshot!(p("19998 years"), @"P19998y"); - insta::assert_snapshot!(p("19998 years ago"), @"-P19998y"); - insta::assert_snapshot!(p("239976 months"), @"P239976m"); - insta::assert_snapshot!(p("239976 months ago"), @"-P239976m"); - insta::assert_snapshot!(p("1043497 weeks"), @"P1043497w"); - insta::assert_snapshot!(p("1043497 weeks ago"), @"-P1043497w"); - insta::assert_snapshot!(p("7304484 days"), @"P7304484d"); - insta::assert_snapshot!(p("7304484 days ago"), @"-P7304484d"); - insta::assert_snapshot!(p("175307616 hours"), @"PT175307616h"); - insta::assert_snapshot!(p("175307616 hours ago"), @"-PT175307616h"); - insta::assert_snapshot!(p("10518456960 minutes"), @"PT10518456960m"); - insta::assert_snapshot!(p("10518456960 minutes ago"), @"-PT10518456960m"); - insta::assert_snapshot!(p("631107417600 seconds"), @"PT631107417600s"); - insta::assert_snapshot!(p("631107417600 seconds ago"), @"-PT631107417600s"); - insta::assert_snapshot!(p("631107417600000 milliseconds"), @"PT631107417600s"); - insta::assert_snapshot!(p("631107417600000 milliseconds ago"), @"-PT631107417600s"); - insta::assert_snapshot!(p("631107417600000000 microseconds"), @"PT631107417600s"); - insta::assert_snapshot!(p("631107417600000000 microseconds ago"), @"-PT631107417600s"); - insta::assert_snapshot!(p("9223372036854775807 nanoseconds"), @"PT9223372036.854775807s"); - insta::assert_snapshot!(p("9223372036854775807 nanoseconds ago"), @"-PT9223372036.854775807s"); - - insta::assert_snapshot!(p("175307617 hours"), @"PT175307616h60m"); - insta::assert_snapshot!(p("175307617 hours ago"), @"-PT175307616h60m"); - insta::assert_snapshot!(p("10518456961 minutes"), @"PT10518456960m60s"); - insta::assert_snapshot!(p("10518456961 minutes ago"), @"-PT10518456960m60s"); - insta::assert_snapshot!(p("631107417601 seconds"), @"PT631107417601s"); - insta::assert_snapshot!(p("631107417601 seconds ago"), @"-PT631107417601s"); - insta::assert_snapshot!(p("631107417600001 milliseconds"), @"PT631107417600.001s"); - insta::assert_snapshot!(p("631107417600001 milliseconds ago"), @"-PT631107417600.001s"); - insta::assert_snapshot!(p("631107417600000001 microseconds"), @"PT631107417600.000001s"); - insta::assert_snapshot!(p("631107417600000001 microseconds ago"), @"-PT631107417600.000001s"); + insta::assert_snapshot!(p("19998 years"), @"P19998Y"); + insta::assert_snapshot!(p("19998 years ago"), @"-P19998Y"); + insta::assert_snapshot!(p("239976 months"), @"P239976M"); + insta::assert_snapshot!(p("239976 months ago"), @"-P239976M"); + insta::assert_snapshot!(p("1043497 weeks"), @"P1043497W"); + insta::assert_snapshot!(p("1043497 weeks ago"), @"-P1043497W"); + insta::assert_snapshot!(p("7304484 days"), @"P7304484D"); + insta::assert_snapshot!(p("7304484 days ago"), @"-P7304484D"); + insta::assert_snapshot!(p("175307616 hours"), @"PT175307616H"); + insta::assert_snapshot!(p("175307616 hours ago"), @"-PT175307616H"); + insta::assert_snapshot!(p("10518456960 minutes"), @"PT10518456960M"); + insta::assert_snapshot!(p("10518456960 minutes ago"), @"-PT10518456960M"); + insta::assert_snapshot!(p("631107417600 seconds"), @"PT631107417600S"); + insta::assert_snapshot!(p("631107417600 seconds ago"), @"-PT631107417600S"); + insta::assert_snapshot!(p("631107417600000 milliseconds"), @"PT631107417600S"); + insta::assert_snapshot!(p("631107417600000 milliseconds ago"), @"-PT631107417600S"); + insta::assert_snapshot!(p("631107417600000000 microseconds"), @"PT631107417600S"); + insta::assert_snapshot!(p("631107417600000000 microseconds ago"), @"-PT631107417600S"); + insta::assert_snapshot!(p("9223372036854775807 nanoseconds"), @"PT9223372036.854775807S"); + insta::assert_snapshot!(p("9223372036854775807 nanoseconds ago"), @"-PT9223372036.854775807S"); + + insta::assert_snapshot!(p("175307617 hours"), @"PT175307616H60M"); + insta::assert_snapshot!(p("175307617 hours ago"), @"-PT175307616H60M"); + insta::assert_snapshot!(p("10518456961 minutes"), @"PT10518456960M60S"); + insta::assert_snapshot!(p("10518456961 minutes ago"), @"-PT10518456960M60S"); + insta::assert_snapshot!(p("631107417601 seconds"), @"PT631107417601S"); + insta::assert_snapshot!(p("631107417601 seconds ago"), @"-PT631107417601S"); + insta::assert_snapshot!(p("631107417600001 milliseconds"), @"PT631107417600.001S"); + insta::assert_snapshot!(p("631107417600001 milliseconds ago"), @"-PT631107417600.001S"); + insta::assert_snapshot!(p("631107417600000001 microseconds"), @"PT631107417600.000001S"); + insta::assert_snapshot!(p("631107417600000001 microseconds ago"), @"-PT631107417600.000001S"); // We don't include nanoseconds here, because that will fail to // parse due to overflowing i64. } @@ -1073,7 +1073,7 @@ mod tests { ); insta::assert_snapshot!( p("1 year 1 mont"), - @r###"failed to parse "1 year 1 mont" in the "friendly" format: parsed value 'P1y1m', but unparsed input "nt" remains (expected no unparsed input)"###, + @r###"failed to parse "1 year 1 mont" in the "friendly" format: parsed value 'P1Y1M', but unparsed input "nt" remains (expected no unparsed input)"###, ); insta::assert_snapshot!( p("2 months,"), @@ -1085,7 +1085,7 @@ mod tests { ); insta::assert_snapshot!( p("2 months ,"), - @r###"failed to parse "2 months ," in the "friendly" format: parsed value 'P2m', but unparsed input "," remains (expected no unparsed input)"###, + @r###"failed to parse "2 months ," in the "friendly" format: parsed value 'P2M', but unparsed input "," remains (expected no unparsed input)"###, ); } @@ -1095,11 +1095,11 @@ mod tests { insta::assert_snapshot!( p("1yago"), - @r###"failed to parse "1yago" in the "friendly" format: parsed value 'P1y', but unparsed input "ago" remains (expected no unparsed input)"###, + @r###"failed to parse "1yago" in the "friendly" format: parsed value 'P1Y', but unparsed input "ago" remains (expected no unparsed input)"###, ); insta::assert_snapshot!( p("1 year 1 monthago"), - @r###"failed to parse "1 year 1 monthago" in the "friendly" format: parsed value 'P1y1m', but unparsed input "ago" remains (expected no unparsed input)"###, + @r###"failed to parse "1 year 1 monthago" in the "friendly" format: parsed value 'P1Y1M', but unparsed input "ago" remains (expected no unparsed input)"###, ); insta::assert_snapshot!( p("+1 year 1 month ago"), @@ -1126,7 +1126,7 @@ mod tests { // one fewer is okay insta::assert_snapshot!( p("640330789636854775 micros"), - @"PT640330789636.854775s" + @"PT640330789636.854775S" ); insta::assert_snapshot!( @@ -1139,7 +1139,7 @@ mod tests { // one fewer is okay insta::assert_snapshot!( p("640330789636854775.807 micros"), - @"PT640330789636.854775807s" + @"PT640330789636.854775807S" ); } @@ -1233,9 +1233,9 @@ mod tests { fn parse_duration_basic() { let p = |s: &str| SpanParser::new().parse_duration(s).unwrap(); - insta::assert_snapshot!(p("1 hour, 2 minutes, 3 seconds"), @"PT1h2m3s"); - insta::assert_snapshot!(p("01:02:03"), @"PT1h2m3s"); - insta::assert_snapshot!(p("999:999:999"), @"PT1015h55m39s"); + insta::assert_snapshot!(p("1 hour, 2 minutes, 3 seconds"), @"PT1H2M3S"); + insta::assert_snapshot!(p("01:02:03"), @"PT1H2M3S"); + insta::assert_snapshot!(p("999:999:999"), @"PT1015H55M39S"); } #[test] @@ -1245,7 +1245,7 @@ mod tests { insta::assert_snapshot!( p("9223372036854775807s"), - @"PT2562047788015215h30m7s", + @"PT2562047788015215H30M7S", ); insta::assert_snapshot!( perr("9223372036854775808s"), @@ -1269,18 +1269,18 @@ mod tests { fn parse_duration_fractional() { let p = |s: &str| SpanParser::new().parse_duration(s).unwrap(); - insta::assert_snapshot!(p("1.5hrs"), @"PT1h30m"); - insta::assert_snapshot!(p("1.5mins"), @"PT1m30s"); - insta::assert_snapshot!(p("1.5secs"), @"PT1.5s"); - insta::assert_snapshot!(p("1.5msecs"), @"PT0.0015s"); - insta::assert_snapshot!(p("1.5µsecs"), @"PT0.0000015s"); + insta::assert_snapshot!(p("1.5hrs"), @"PT1H30M"); + insta::assert_snapshot!(p("1.5mins"), @"PT1M30S"); + insta::assert_snapshot!(p("1.5secs"), @"PT1.5S"); + insta::assert_snapshot!(p("1.5msecs"), @"PT0.0015S"); + insta::assert_snapshot!(p("1.5µsecs"), @"PT0.0000015S"); - insta::assert_snapshot!(p("1h 1.5mins"), @"PT1h1m30s"); - insta::assert_snapshot!(p("1m 1.5secs"), @"PT1m1.5s"); - insta::assert_snapshot!(p("1s 1.5msecs"), @"PT1.0015s"); - insta::assert_snapshot!(p("1ms 1.5µsecs"), @"PT0.0010015s"); + insta::assert_snapshot!(p("1h 1.5mins"), @"PT1H1M30S"); + insta::assert_snapshot!(p("1m 1.5secs"), @"PT1M1.5S"); + insta::assert_snapshot!(p("1s 1.5msecs"), @"PT1.0015S"); + insta::assert_snapshot!(p("1ms 1.5µsecs"), @"PT0.0010015S"); - insta::assert_snapshot!(p("1s2000ms"), @"PT3s"); + insta::assert_snapshot!(p("1s2000ms"), @"PT3S"); } #[test] @@ -1288,51 +1288,51 @@ mod tests { let p = |s: &str| SpanParser::new().parse_duration(s).unwrap(); let pe = |s: &str| SpanParser::new().parse_duration(s).unwrap_err(); - insta::assert_snapshot!(p("175307616 hours"), @"PT175307616h"); - insta::assert_snapshot!(p("175307616 hours ago"), @"-PT175307616h"); - insta::assert_snapshot!(p("10518456960 minutes"), @"PT175307616h"); - insta::assert_snapshot!(p("10518456960 minutes ago"), @"-PT175307616h"); - insta::assert_snapshot!(p("631107417600 seconds"), @"PT175307616h"); - insta::assert_snapshot!(p("631107417600 seconds ago"), @"-PT175307616h"); - insta::assert_snapshot!(p("631107417600000 milliseconds"), @"PT175307616h"); - insta::assert_snapshot!(p("631107417600000 milliseconds ago"), @"-PT175307616h"); - insta::assert_snapshot!(p("631107417600000000 microseconds"), @"PT175307616h"); - insta::assert_snapshot!(p("631107417600000000 microseconds ago"), @"-PT175307616h"); - insta::assert_snapshot!(p("9223372036854775807 nanoseconds"), @"PT2562047h47m16.854775807s"); - insta::assert_snapshot!(p("9223372036854775807 nanoseconds ago"), @"-PT2562047h47m16.854775807s"); - - insta::assert_snapshot!(p("175307617 hours"), @"PT175307617h"); - insta::assert_snapshot!(p("175307617 hours ago"), @"-PT175307617h"); - insta::assert_snapshot!(p("10518456961 minutes"), @"PT175307616h1m"); - insta::assert_snapshot!(p("10518456961 minutes ago"), @"-PT175307616h1m"); - insta::assert_snapshot!(p("631107417601 seconds"), @"PT175307616h1s"); - insta::assert_snapshot!(p("631107417601 seconds ago"), @"-PT175307616h1s"); - insta::assert_snapshot!(p("631107417600001 milliseconds"), @"PT175307616h0.001s"); - insta::assert_snapshot!(p("631107417600001 milliseconds ago"), @"-PT175307616h0.001s"); - insta::assert_snapshot!(p("631107417600000001 microseconds"), @"PT175307616h0.000001s"); - insta::assert_snapshot!(p("631107417600000001 microseconds ago"), @"-PT175307616h0.000001s"); + insta::assert_snapshot!(p("175307616 hours"), @"PT175307616H"); + insta::assert_snapshot!(p("175307616 hours ago"), @"-PT175307616H"); + insta::assert_snapshot!(p("10518456960 minutes"), @"PT175307616H"); + insta::assert_snapshot!(p("10518456960 minutes ago"), @"-PT175307616H"); + insta::assert_snapshot!(p("631107417600 seconds"), @"PT175307616H"); + insta::assert_snapshot!(p("631107417600 seconds ago"), @"-PT175307616H"); + insta::assert_snapshot!(p("631107417600000 milliseconds"), @"PT175307616H"); + insta::assert_snapshot!(p("631107417600000 milliseconds ago"), @"-PT175307616H"); + insta::assert_snapshot!(p("631107417600000000 microseconds"), @"PT175307616H"); + insta::assert_snapshot!(p("631107417600000000 microseconds ago"), @"-PT175307616H"); + insta::assert_snapshot!(p("9223372036854775807 nanoseconds"), @"PT2562047H47M16.854775807S"); + insta::assert_snapshot!(p("9223372036854775807 nanoseconds ago"), @"-PT2562047H47M16.854775807S"); + + insta::assert_snapshot!(p("175307617 hours"), @"PT175307617H"); + insta::assert_snapshot!(p("175307617 hours ago"), @"-PT175307617H"); + insta::assert_snapshot!(p("10518456961 minutes"), @"PT175307616H1M"); + insta::assert_snapshot!(p("10518456961 minutes ago"), @"-PT175307616H1M"); + insta::assert_snapshot!(p("631107417601 seconds"), @"PT175307616H1S"); + insta::assert_snapshot!(p("631107417601 seconds ago"), @"-PT175307616H1S"); + insta::assert_snapshot!(p("631107417600001 milliseconds"), @"PT175307616H0.001S"); + insta::assert_snapshot!(p("631107417600001 milliseconds ago"), @"-PT175307616H0.001S"); + insta::assert_snapshot!(p("631107417600000001 microseconds"), @"PT175307616H0.000001S"); + insta::assert_snapshot!(p("631107417600000001 microseconds ago"), @"-PT175307616H0.000001S"); // We don't include nanoseconds here, because that will fail to // parse due to overflowing i64. // The above were copied from the corresponding `Span` test, which has // tighter limits on components. But a `SignedDuration` supports the // full range of `i64` seconds. - insta::assert_snapshot!(p("2562047788015215hours"), @"PT2562047788015215h"); - insta::assert_snapshot!(p("-2562047788015215hours"), @"-PT2562047788015215h"); + insta::assert_snapshot!(p("2562047788015215hours"), @"PT2562047788015215H"); + insta::assert_snapshot!(p("-2562047788015215hours"), @"-PT2562047788015215H"); insta::assert_snapshot!( pe("2562047788015216hrs"), @r###"failed to parse "2562047788015216hrs" in the "friendly" format: converting 2562047788015216 hours to seconds overflows i64"###, ); - insta::assert_snapshot!(p("153722867280912930minutes"), @"PT2562047788015215h30m"); - insta::assert_snapshot!(p("153722867280912930minutes ago"), @"-PT2562047788015215h30m"); + insta::assert_snapshot!(p("153722867280912930minutes"), @"PT2562047788015215H30M"); + insta::assert_snapshot!(p("153722867280912930minutes ago"), @"-PT2562047788015215H30M"); insta::assert_snapshot!( pe("153722867280912931mins"), @r###"failed to parse "153722867280912931mins" in the "friendly" format: parameter 'minutes-to-seconds' with value 60 is not in the required range of -9223372036854775808..=9223372036854775807"###, ); - insta::assert_snapshot!(p("9223372036854775807seconds"), @"PT2562047788015215h30m7s"); - insta::assert_snapshot!(p("-9223372036854775807seconds"), @"-PT2562047788015215h30m7s"); + insta::assert_snapshot!(p("9223372036854775807seconds"), @"PT2562047788015215H30M7S"); + insta::assert_snapshot!(p("-9223372036854775807seconds"), @"-PT2562047788015215H30M7S"); insta::assert_snapshot!( pe("9223372036854775808s"), @r###"failed to parse "9223372036854775808s" in the "friendly" format: number '9223372036854775808' too big to parse into 64-bit integer"###, @@ -1369,7 +1369,7 @@ mod tests { ); insta::assert_snapshot!( p("1 hour 1 minut"), - @r###"failed to parse "1 hour 1 minut" in the "friendly" format: parsed value 'PT1h1m', but unparsed input "ut" remains (expected no unparsed input)"###, + @r###"failed to parse "1 hour 1 minut" in the "friendly" format: parsed value 'PT1H1M', but unparsed input "ut" remains (expected no unparsed input)"###, ); insta::assert_snapshot!( p("2 minutes,"), @@ -1381,7 +1381,7 @@ mod tests { ); insta::assert_snapshot!( p("2 minutes ,"), - @r###"failed to parse "2 minutes ," in the "friendly" format: parsed value 'PT2m', but unparsed input "," remains (expected no unparsed input)"###, + @r###"failed to parse "2 minutes ," in the "friendly" format: parsed value 'PT2M', but unparsed input "," remains (expected no unparsed input)"###, ); } @@ -1391,11 +1391,11 @@ mod tests { insta::assert_snapshot!( p("1hago"), - @r###"failed to parse "1hago" in the "friendly" format: parsed value 'PT1h', but unparsed input "ago" remains (expected no unparsed input)"###, + @r###"failed to parse "1hago" in the "friendly" format: parsed value 'PT1H', but unparsed input "ago" remains (expected no unparsed input)"###, ); insta::assert_snapshot!( p("1 hour 1 minuteago"), - @r###"failed to parse "1 hour 1 minuteago" in the "friendly" format: parsed value 'PT1h1m', but unparsed input "ago" remains (expected no unparsed input)"###, + @r###"failed to parse "1 hour 1 minuteago" in the "friendly" format: parsed value 'PT1H1M', but unparsed input "ago" remains (expected no unparsed input)"###, ); insta::assert_snapshot!( p("+1 hour 1 minute ago"), @@ -1421,7 +1421,7 @@ mod tests { // one fewer is okay insta::assert_snapshot!( p("9223372036854775807 micros"), - @"PT2562047788h54.775807s" + @"PT2562047788H54.775807S" ); } diff --git a/src/fmt/temporal/mod.rs b/src/fmt/temporal/mod.rs index 95f898a4..a1bfdd42 100644 --- a/src/fmt/temporal/mod.rs +++ b/src/fmt/temporal/mod.rs @@ -68,8 +68,11 @@ But there are some details not easily captured by a simple regular expression: * At least one unit must be specified. To write a zero span, specify `0` for any unit. For example, `P0d` and `PT0s` are equivalent. -* The format is case insensitive. The printer will by default capitalize the -`P` and `T` designators, but lowercase the unit designators. +* The format is case insensitive. The printer will by default capitalize all +designators, but the unit designators can be configured to use lowercase with +[`SpanPrinter::lowercase`]. For example, `P3y1m10dT5h` instead of +`P3Y1M10DT5H`. You might prefer lowercase since you may find it easier to read. +However, it is an extension to ISO 8601 and isn't as broadly supported. * Hours, minutes or seconds may be fractional. And the only units that may be fractional are the lowest units. * A span like `P99999999999y` is invalid because it exceeds the allowable range @@ -1566,7 +1569,7 @@ impl SpanParser { /// let mut buf = vec![]; /// // Printing to a `Vec` can never fail. /// PRINTER.print_span(&span, &mut buf).unwrap(); -/// assert_eq!(buf, "PT48m".as_bytes()); +/// assert_eq!(buf, "PT48M".as_bytes()); /// ``` /// /// # Example: using adapters with `std::io::Write` and `std::fmt::Write` @@ -1605,6 +1608,30 @@ impl SpanPrinter { SpanPrinter { p: printer::SpanPrinter::new() } } + /// Use lowercase for unit designator labels. + /// + /// By default, unit designator labels are written in uppercase. + /// + /// # Example + /// + /// This shows the difference between the default (uppercase) and enabling + /// lowercase. Lowercase unit designator labels tend to be easier to read + /// (in this author's opinion), but they aren't as broadly supported since + /// they are an extension to ISO 8601. + /// + /// ``` + /// use jiff::{fmt::temporal::SpanPrinter, ToSpan}; + /// + /// let span = 5.years().days(10).hours(1); + /// let printer = SpanPrinter::new(); + /// assert_eq!(printer.span_to_string(&span), "P5Y10DT1H"); + /// assert_eq!(printer.lowercase(true).span_to_string(&span), "P5y10dT1h"); + /// ``` + #[inline] + pub const fn lowercase(self, yes: bool) -> SpanPrinter { + SpanPrinter { p: self.p.lowercase(yes) } + } + /// Format a `Span` into a string. /// /// This is a convenience routine for [`SpanPrinter::print_span`] with @@ -1618,7 +1645,7 @@ impl SpanPrinter { /// const PRINTER: SpanPrinter = SpanPrinter::new(); /// /// let span = 3.years().months(5); - /// assert_eq!(PRINTER.span_to_string(&span), "P3y5m"); + /// assert_eq!(PRINTER.span_to_string(&span), "P3Y5M"); /// /// # Ok::<(), Box>(()) /// ``` @@ -1646,8 +1673,8 @@ impl SpanPrinter { /// const PRINTER: SpanPrinter = SpanPrinter::new(); /// /// let dur = SignedDuration::new(86_525, 123_000_789); - /// assert_eq!(PRINTER.duration_to_string(&dur), "PT24h2m5.123000789s"); - /// assert_eq!(PRINTER.duration_to_string(&-dur), "-PT24h2m5.123000789s"); + /// assert_eq!(PRINTER.duration_to_string(&dur), "PT24H2M5.123000789S"); + /// assert_eq!(PRINTER.duration_to_string(&-dur), "-PT24H2M5.123000789S"); /// /// # Ok::<(), Box>(()) /// ``` @@ -1683,7 +1710,7 @@ impl SpanPrinter { /// let mut buf = String::new(); /// // Printing to a `String` can never fail. /// PRINTER.print_span(&span, &mut buf).unwrap(); - /// assert_eq!(buf, "P3y5m"); + /// assert_eq!(buf, "P3Y5M"); /// /// # Ok::<(), Box>(()) /// ``` @@ -1719,12 +1746,12 @@ impl SpanPrinter { /// let mut buf = String::new(); /// // Printing to a `String` can never fail. /// PRINTER.print_duration(&dur, &mut buf).unwrap(); - /// assert_eq!(buf, "PT24h2m5.123000789s"); + /// assert_eq!(buf, "PT24H2M5.123000789S"); /// /// // Negative durations are supported. /// buf.clear(); /// PRINTER.print_duration(&-dur, &mut buf).unwrap(); - /// assert_eq!(buf, "-PT24h2m5.123000789s"); + /// assert_eq!(buf, "-PT24H2M5.123000789S"); /// /// # Ok::<(), Box>(()) /// ``` diff --git a/src/fmt/temporal/printer.rs b/src/fmt/temporal/printer.rs index c65ce819..0597c3c9 100644 --- a/src/fmt/temporal/printer.rs +++ b/src/fmt/temporal/printer.rs @@ -220,14 +220,21 @@ impl Default for DateTimePrinter { /// Note that in Temporal, a "span" is called a "duration." #[derive(Debug)] pub(super) struct SpanPrinter { - /// There are currently no configuration options for this printer. - _priv: (), + /// Whether to use lowercase unit designators. + lowercase: bool, } impl SpanPrinter { /// Create a new Temporal span printer with the default configuration. pub(super) const fn new() -> SpanPrinter { - SpanPrinter { _priv: () } + SpanPrinter { lowercase: false } + } + + /// Use lowercase for unit designator labels. + /// + /// By default, unit designator labels are written in uppercase. + pub(super) const fn lowercase(self, yes: bool) -> SpanPrinter { + SpanPrinter { lowercase: yes } } /// Print the given span to the writer given. @@ -249,22 +256,22 @@ impl SpanPrinter { let mut non_zero_greater_than_second = false; if span.get_years_ranged() != 0 { wtr.write_int(&FMT_INT, span.get_years_ranged().get().abs())?; - wtr.write_str("y")?; + wtr.write_char(self.label('Y'))?; non_zero_greater_than_second = true; } if span.get_months_ranged() != 0 { wtr.write_int(&FMT_INT, span.get_months_ranged().get().abs())?; - wtr.write_str("m")?; + wtr.write_char(self.label('M'))?; non_zero_greater_than_second = true; } if span.get_weeks_ranged() != 0 { wtr.write_int(&FMT_INT, span.get_weeks_ranged().get().abs())?; - wtr.write_str("w")?; + wtr.write_char(self.label('W'))?; non_zero_greater_than_second = true; } if span.get_days_ranged() != 0 { wtr.write_int(&FMT_INT, span.get_days_ranged().get().abs())?; - wtr.write_str("d")?; + wtr.write_char(self.label('D'))?; non_zero_greater_than_second = true; } @@ -275,7 +282,7 @@ impl SpanPrinter { printed_time_prefix = true; } wtr.write_int(&FMT_INT, span.get_hours_ranged().get().abs())?; - wtr.write_str("h")?; + wtr.write_char(self.label('H'))?; non_zero_greater_than_second = true; } if span.get_minutes_ranged() != 0 { @@ -284,7 +291,7 @@ impl SpanPrinter { printed_time_prefix = true; } wtr.write_int(&FMT_INT, span.get_minutes_ranged().get().abs())?; - wtr.write_str("m")?; + wtr.write_char(self.label('M'))?; non_zero_greater_than_second = true; } @@ -307,7 +314,7 @@ impl SpanPrinter { wtr.write_str("T")?; } wtr.write_int(&FMT_INT, seconds.get())?; - wtr.write_str("s")?; + wtr.write_char(self.label('S'))?; } else if millis != 0 || micros != 0 || nanos != 0 { if !printed_time_prefix { wtr.write_str("T")?; @@ -336,7 +343,7 @@ impl SpanPrinter { wtr.write_str(".")?; wtr.write_fraction(&FMT_FRACTION, fraction_nano.get())?; } - wtr.write_str("s")?; + wtr.write_char(self.label('S'))?; } Ok(()) } @@ -370,25 +377,37 @@ impl SpanPrinter { secs = (secs % 60).abs(); if hours != 0 { wtr.write_int(&FMT_INT, hours)?; - wtr.write_str("h")?; + wtr.write_char(self.label('H'))?; non_zero_greater_than_second = true; } if minutes != 0 { wtr.write_int(&FMT_INT, minutes)?; - wtr.write_str("m")?; + wtr.write_char(self.label('M'))?; non_zero_greater_than_second = true; } if (secs != 0 || !non_zero_greater_than_second) && nanos == 0 { wtr.write_int(&FMT_INT, secs)?; - wtr.write_str("s")?; + wtr.write_char(self.label('S'))?; } else if nanos != 0 { wtr.write_int(&FMT_INT, secs)?; wtr.write_str(".")?; wtr.write_fraction(&FMT_FRACTION, nanos)?; - wtr.write_str("s")?; + wtr.write_char(self.label('S'))?; } Ok(()) } + + /// Converts the uppercase unit designator label to lowercase if this + /// printer is configured to use lowercase. Otherwise the label is returned + /// unchanged. + fn label(&self, upper: char) -> char { + debug_assert!(upper.is_ascii()); + if self.lowercase { + upper.to_ascii_lowercase() + } else { + upper + } + } } #[cfg(test)] @@ -450,25 +469,25 @@ mod tests { buf }; - insta::assert_snapshot!(p(Span::new()), @"PT0s"); - insta::assert_snapshot!(p(1.second()), @"PT1s"); - insta::assert_snapshot!(p(-1.second()), @"-PT1s"); + insta::assert_snapshot!(p(Span::new()), @"PT0S"); + insta::assert_snapshot!(p(1.second()), @"PT1S"); + insta::assert_snapshot!(p(-1.second()), @"-PT1S"); insta::assert_snapshot!(p( 1.second().milliseconds(1).microseconds(1).nanoseconds(1), - ), @"PT1.001001001s"); + ), @"PT1.001001001S"); insta::assert_snapshot!(p( 0.second().milliseconds(999).microseconds(999).nanoseconds(999), - ), @"PT0.999999999s"); + ), @"PT0.999999999S"); insta::assert_snapshot!(p( 1.year().months(1).weeks(1).days(1) .hours(1).minutes(1).seconds(1) .milliseconds(1).microseconds(1).nanoseconds(1), - ), @"P1y1m1w1dT1h1m1.001001001s"); + ), @"P1Y1M1W1DT1H1M1.001001001S"); insta::assert_snapshot!(p( -1.year().months(1).weeks(1).days(1) .hours(1).minutes(1).seconds(1) .milliseconds(1).microseconds(1).nanoseconds(1), - ), @"-P1y1m1w1dT1h1m1.001001001s"); + ), @"-P1Y1M1W1DT1H1M1.001001001S"); } #[test] @@ -482,52 +501,52 @@ mod tests { // These are all sub-second trickery tests. insta::assert_snapshot!(p( 0.second().milliseconds(1000).microseconds(1000).nanoseconds(1000), - ), @"PT1.001001s"); + ), @"PT1.001001S"); insta::assert_snapshot!(p( 1.second().milliseconds(1000).microseconds(1000).nanoseconds(1000), - ), @"PT2.001001s"); + ), @"PT2.001001S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MAX_REPR), - ), @"PT631107417600s"); + ), @"PT631107417600S"); insta::assert_snapshot!(p( 0.second() .microseconds(t::SpanMicroseconds::MAX_REPR), - ), @"PT631107417600s"); + ), @"PT631107417600S"); insta::assert_snapshot!(p( 0.second() .nanoseconds(t::SpanNanoseconds::MAX_REPR), - ), @"PT9223372036.854775807s"); + ), @"PT9223372036.854775807S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MAX_REPR) .microseconds(999_999), - ), @"PT631107417600.999999s"); + ), @"PT631107417600.999999S"); // This is 1 microsecond more than the maximum number of seconds // representable in a span. insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MAX_REPR) .microseconds(1_000_000), - ), @"PT631107417601s"); + ), @"PT631107417601S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MAX_REPR) .microseconds(1_000_001), - ), @"PT631107417601.000001s"); + ), @"PT631107417601.000001S"); // This is 1 nanosecond more than the maximum number of seconds // representable in a span. insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MAX_REPR) .nanoseconds(1_000_000_000), - ), @"PT631107417601s"); + ), @"PT631107417601S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MAX_REPR) .nanoseconds(1_000_000_001), - ), @"PT631107417601.000000001s"); + ), @"PT631107417601.000000001S"); // The max millis, micros and nanos, combined. insta::assert_snapshot!(p( @@ -535,7 +554,7 @@ mod tests { .milliseconds(t::SpanMilliseconds::MAX_REPR) .microseconds(t::SpanMicroseconds::MAX_REPR) .nanoseconds(t::SpanNanoseconds::MAX_REPR), - ), @"PT1271438207236.854775807s"); + ), @"PT1271438207236.854775807S"); // The max seconds, millis, micros and nanos, combined. insta::assert_snapshot!(p( Span::new() @@ -543,7 +562,7 @@ mod tests { .milliseconds(t::SpanMilliseconds::MAX_REPR) .microseconds(t::SpanMicroseconds::MAX_REPR) .nanoseconds(t::SpanNanoseconds::MAX_REPR), - ), @"PT1902545624836.854775807s"); + ), @"PT1902545624836.854775807S"); } #[test] @@ -557,52 +576,52 @@ mod tests { // These are all sub-second trickery tests. insta::assert_snapshot!(p( -0.second().milliseconds(1000).microseconds(1000).nanoseconds(1000), - ), @"-PT1.001001s"); + ), @"-PT1.001001S"); insta::assert_snapshot!(p( -1.second().milliseconds(1000).microseconds(1000).nanoseconds(1000), - ), @"-PT2.001001s"); + ), @"-PT2.001001S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MIN_REPR), - ), @"-PT631107417600s"); + ), @"-PT631107417600S"); insta::assert_snapshot!(p( 0.second() .microseconds(t::SpanMicroseconds::MIN_REPR), - ), @"-PT631107417600s"); + ), @"-PT631107417600S"); insta::assert_snapshot!(p( 0.second() .nanoseconds(t::SpanNanoseconds::MIN_REPR), - ), @"-PT9223372036.854775807s"); + ), @"-PT9223372036.854775807S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MIN_REPR) .microseconds(999_999), - ), @"-PT631107417600.999999s"); + ), @"-PT631107417600.999999S"); // This is 1 microsecond more than the maximum number of seconds // representable in a span. insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MIN_REPR) .microseconds(1_000_000), - ), @"-PT631107417601s"); + ), @"-PT631107417601S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MIN_REPR) .microseconds(1_000_001), - ), @"-PT631107417601.000001s"); + ), @"-PT631107417601.000001S"); // This is 1 nanosecond more than the maximum number of seconds // representable in a span. insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MIN_REPR) .nanoseconds(1_000_000_000), - ), @"-PT631107417601s"); + ), @"-PT631107417601S"); insta::assert_snapshot!(p( 0.second() .milliseconds(t::SpanMilliseconds::MIN_REPR) .nanoseconds(1_000_000_001), - ), @"-PT631107417601.000000001s"); + ), @"-PT631107417601.000000001S"); // The max millis, micros and nanos, combined. insta::assert_snapshot!(p( @@ -610,7 +629,7 @@ mod tests { .milliseconds(t::SpanMilliseconds::MIN_REPR) .microseconds(t::SpanMicroseconds::MIN_REPR) .nanoseconds(t::SpanNanoseconds::MIN_REPR), - ), @"-PT1271438207236.854775807s"); + ), @"-PT1271438207236.854775807S"); // The max seconds, millis, micros and nanos, combined. insta::assert_snapshot!(p( Span::new() @@ -618,7 +637,7 @@ mod tests { .milliseconds(t::SpanMilliseconds::MIN_REPR) .microseconds(t::SpanMicroseconds::MIN_REPR) .nanoseconds(t::SpanNanoseconds::MIN_REPR), - ), @"-PT1902545624836.854775807s"); + ), @"-PT1902545624836.854775807S"); } #[test] @@ -630,40 +649,40 @@ mod tests { buf }; - insta::assert_snapshot!(p(0, 0), @"PT0s"); - insta::assert_snapshot!(p(0, 1), @"PT0.000000001s"); - insta::assert_snapshot!(p(1, 0), @"PT1s"); - insta::assert_snapshot!(p(59, 0), @"PT59s"); - insta::assert_snapshot!(p(60, 0), @"PT1m"); - insta::assert_snapshot!(p(60, 1), @"PT1m0.000000001s"); - insta::assert_snapshot!(p(61, 1), @"PT1m1.000000001s"); - insta::assert_snapshot!(p(3_600, 0), @"PT1h"); - insta::assert_snapshot!(p(3_600, 1), @"PT1h0.000000001s"); - insta::assert_snapshot!(p(3_660, 0), @"PT1h1m"); - insta::assert_snapshot!(p(3_660, 1), @"PT1h1m0.000000001s"); - insta::assert_snapshot!(p(3_661, 0), @"PT1h1m1s"); - insta::assert_snapshot!(p(3_661, 1), @"PT1h1m1.000000001s"); - - insta::assert_snapshot!(p(0, -1), @"-PT0.000000001s"); - insta::assert_snapshot!(p(-1, 0), @"-PT1s"); - insta::assert_snapshot!(p(-59, 0), @"-PT59s"); - insta::assert_snapshot!(p(-60, 0), @"-PT1m"); - insta::assert_snapshot!(p(-60, -1), @"-PT1m0.000000001s"); - insta::assert_snapshot!(p(-61, -1), @"-PT1m1.000000001s"); - insta::assert_snapshot!(p(-3_600, 0), @"-PT1h"); - insta::assert_snapshot!(p(-3_600, -1), @"-PT1h0.000000001s"); - insta::assert_snapshot!(p(-3_660, 0), @"-PT1h1m"); - insta::assert_snapshot!(p(-3_660, -1), @"-PT1h1m0.000000001s"); - insta::assert_snapshot!(p(-3_661, 0), @"-PT1h1m1s"); - insta::assert_snapshot!(p(-3_661, -1), @"-PT1h1m1.000000001s"); + insta::assert_snapshot!(p(0, 0), @"PT0S"); + insta::assert_snapshot!(p(0, 1), @"PT0.000000001S"); + insta::assert_snapshot!(p(1, 0), @"PT1S"); + insta::assert_snapshot!(p(59, 0), @"PT59S"); + insta::assert_snapshot!(p(60, 0), @"PT1M"); + insta::assert_snapshot!(p(60, 1), @"PT1M0.000000001S"); + insta::assert_snapshot!(p(61, 1), @"PT1M1.000000001S"); + insta::assert_snapshot!(p(3_600, 0), @"PT1H"); + insta::assert_snapshot!(p(3_600, 1), @"PT1H0.000000001S"); + insta::assert_snapshot!(p(3_660, 0), @"PT1H1M"); + insta::assert_snapshot!(p(3_660, 1), @"PT1H1M0.000000001S"); + insta::assert_snapshot!(p(3_661, 0), @"PT1H1M1S"); + insta::assert_snapshot!(p(3_661, 1), @"PT1H1M1.000000001S"); + + insta::assert_snapshot!(p(0, -1), @"-PT0.000000001S"); + insta::assert_snapshot!(p(-1, 0), @"-PT1S"); + insta::assert_snapshot!(p(-59, 0), @"-PT59S"); + insta::assert_snapshot!(p(-60, 0), @"-PT1M"); + insta::assert_snapshot!(p(-60, -1), @"-PT1M0.000000001S"); + insta::assert_snapshot!(p(-61, -1), @"-PT1M1.000000001S"); + insta::assert_snapshot!(p(-3_600, 0), @"-PT1H"); + insta::assert_snapshot!(p(-3_600, -1), @"-PT1H0.000000001S"); + insta::assert_snapshot!(p(-3_660, 0), @"-PT1H1M"); + insta::assert_snapshot!(p(-3_660, -1), @"-PT1H1M0.000000001S"); + insta::assert_snapshot!(p(-3_661, 0), @"-PT1H1M1S"); + insta::assert_snapshot!(p(-3_661, -1), @"-PT1H1M1.000000001S"); insta::assert_snapshot!( p(i64::MIN, -999_999_999), - @"-PT2562047788015215h30m8.999999999s", + @"-PT2562047788015215H30M8.999999999S", ); insta::assert_snapshot!( p(i64::MAX, 999_999_999), - @"PT2562047788015215h30m7.999999999s", + @"PT2562047788015215H30M7.999999999S", ); } } diff --git a/src/lib.rs b/src/lib.rs index bea9dc3a..79b9392f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -368,7 +368,7 @@ use jiff::civil::date; let zdt1 = date(2020, 8, 26).at(6, 27, 0, 0).intz("America/New_York")?; let zdt2 = date(2023, 12, 31).at(18, 30, 0, 0).intz("America/New_York")?; let span = &zdt2 - &zdt1; -assert_eq!(span.to_string(), "PT29341h3m"); +assert_eq!(format!("{span:#}"), "29341h 3m"); # Ok::<(), Box>(()) ``` @@ -384,7 +384,7 @@ use jiff::{civil::date, Unit}; let zdt1 = date(2020, 8, 26).at(6, 27, 0, 0).intz("America/New_York")?; let zdt2 = date(2023, 12, 31).at(18, 30, 0, 0).intz("America/New_York")?; let span = zdt1.until((Unit::Year, &zdt2))?; -assert_eq!(span.to_string(), "P3y4m5dT12h3m"); +assert_eq!(format!("{span:#}"), "3y 4mo 5d 12h 3m"); # Ok::<(), Box>(()) ``` diff --git a/src/signed_duration.rs b/src/signed_duration.rs index c0cac5b2..b5681873 100644 --- a/src/signed_duration.rs +++ b/src/signed_duration.rs @@ -32,7 +32,7 @@ use crate::util::libm::Float; /// use jiff::SignedDuration; /// /// let duration: SignedDuration = "PT2h30m".parse()?; -/// assert_eq!(duration.to_string(), "PT2h30m"); +/// assert_eq!(duration.to_string(), "PT2H30M"); /// /// // Or use the "friendly" format by invoking the alternate: /// assert_eq!(format!("{duration:#}"), "2h 30m"); @@ -75,7 +75,7 @@ use crate::util::libm::Float; /// let duration = span.to_jiff_duration(&relative)?; /// // This example also motivates *why* a relative date /// // is required. Not all days are the same length! -/// assert_eq!(duration.to_string(), "PT25h"); +/// assert_eq!(duration.to_string(), "PT25H"); /// /// # Ok::<(), Box>(()) /// ``` @@ -2641,27 +2641,27 @@ mod tests { insta::assert_snapshot!( p("1 hour").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("+1 hour").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("-1 hour").unwrap(), - @"-PT1h", + @"-PT1H", ); insta::assert_snapshot!( p("PT1h").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("+PT1h").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("-PT1h").unwrap(), - @"-PT1h", + @"-PT1H", ); insta::assert_snapshot!( @@ -2686,27 +2686,27 @@ mod tests { insta::assert_snapshot!( p("1 hour").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("+1 hour").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("-1 hour").unwrap(), - @"-PT1h", + @"-PT1H", ); insta::assert_snapshot!( p("PT1h").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("+PT1h").unwrap(), - @"PT1h", + @"PT1H", ); insta::assert_snapshot!( p("-PT1h").unwrap(), - @"-PT1h", + @"-PT1H", ); insta::assert_snapshot!( diff --git a/src/span.rs b/src/span.rs index a901557c..e8c7b216 100644 --- a/src/span.rs +++ b/src/span.rs @@ -70,7 +70,7 @@ use crate::{ /// use jiff::Span; /// /// let span = Span::new().days(5).hours(8).minutes(1); -/// assert_eq!(span.to_string(), "P5dT8h1m"); +/// assert_eq!(span.to_string(), "P5DT8H1M"); /// ``` /// /// But Jiff provides a [`ToSpan`] trait that defines extension methods on @@ -80,10 +80,10 @@ use crate::{ /// use jiff::ToSpan; /// /// let span = 5.days().hours(8).minutes(1); -/// assert_eq!(span.to_string(), "P5dT8h1m"); +/// assert_eq!(span.to_string(), "P5DT8H1M"); /// // singular units on integers can be used too: /// let span = 1.day().hours(8).minutes(1); -/// assert_eq!(span.to_string(), "P1dT8h1m"); +/// assert_eq!(span.to_string(), "P1DT8H1M"); /// ``` /// /// # Negative spans @@ -99,25 +99,25 @@ use crate::{ /// use jiff::{Span, ToSpan}; /// /// let span = -Span::new().days(5); -/// assert_eq!(span.to_string(), "-P5d"); +/// assert_eq!(span.to_string(), "-P5D"); /// /// let span = Span::new().days(5).negate(); -/// assert_eq!(span.to_string(), "-P5d"); +/// assert_eq!(span.to_string(), "-P5D"); /// /// let span = Span::new().days(-5); -/// assert_eq!(span.to_string(), "-P5d"); +/// assert_eq!(span.to_string(), "-P5D"); /// /// let span = -Span::new().days(-5).negate(); -/// assert_eq!(span.to_string(), "-P5d"); +/// assert_eq!(span.to_string(), "-P5D"); /// /// let span = -5.days(); -/// assert_eq!(span.to_string(), "-P5d"); +/// assert_eq!(span.to_string(), "-P5D"); /// /// let span = (-5).days(); -/// assert_eq!(span.to_string(), "-P5d"); +/// assert_eq!(span.to_string(), "-P5D"); /// /// let span = -(5.days()); -/// assert_eq!(span.to_string(), "-P5d"); +/// assert_eq!(span.to_string(), "-P5D"); /// ``` /// /// The sign of a span applies to the entire span. When a span is negative, @@ -180,10 +180,12 @@ use crate::{ /// ``` /// use jiff::{Span, ToSpan}; /// -/// let span: Span = "P2M10DT2H30M".parse()?; -/// assert_eq!(span.to_string(), "P2m10dT2h30m"); +/// let span: Span = "P2m10dT2h30m".parse()?; +/// // By default, capital unit designator labels are used. +/// // This can be changed with `jiff::fmt::temporal::SpanPrinter::lowercase`. +/// assert_eq!(span.to_string(), "P2M10DT2H30M"); /// -/// // Or use the "friendly" format by invoking the alternate: +/// // Or use the "friendly" format by invoking the `Display` alternate: /// assert_eq!(format!("{span:#}"), "2mo 10d 2h 30m"); /// /// // Parsing automatically supports both the ISO 8601 and "friendly" formats: @@ -1227,9 +1229,9 @@ impl Span { /// use jiff::ToSpan; /// /// let span = -100.seconds(); - /// assert_eq!(span.to_string(), "-PT100s"); + /// assert_eq!(span.to_string(), "-PT100S"); /// let span = span.abs(); - /// assert_eq!(span.to_string(), "PT100s"); + /// assert_eq!(span.to_string(), "PT100S"); /// ``` #[inline] pub fn abs(self) -> Span { @@ -1251,9 +1253,9 @@ impl Span { /// use jiff::ToSpan; /// /// let span = 100.days(); - /// assert_eq!(span.to_string(), "P100d"); + /// assert_eq!(span.to_string(), "P100D"); /// let span = span.negate(); - /// assert_eq!(span.to_string(), "-P100d"); + /// assert_eq!(span.to_string(), "-P100D"); /// ``` /// /// # Example: available via the negation operator @@ -1264,9 +1266,9 @@ impl Span { /// use jiff::ToSpan; /// /// let span = 100.days(); - /// assert_eq!(span.to_string(), "P100d"); + /// assert_eq!(span.to_string(), "P100D"); /// let span = -span; - /// assert_eq!(span.to_string(), "-P100d"); + /// assert_eq!(span.to_string(), "-P100D"); /// ``` #[inline] pub fn negate(self) -> Span { @@ -3708,13 +3710,13 @@ impl quickcheck::Arbitrary for Span { /// ``` /// use jiff::ToSpan; /// -/// assert_eq!(5.days().to_string(), "P5d"); -/// assert_eq!(5.days().hours(10).to_string(), "P5dT10h"); +/// assert_eq!(5.days().to_string(), "P5D"); +/// assert_eq!(5.days().hours(10).to_string(), "P5DT10H"); /// /// // Negation works and it doesn't matter where the sign goes. It can be /// // applied to the span itself or to the integer. -/// assert_eq!((-5.days()).to_string(), "-P5d"); -/// assert_eq!((-5).days().to_string(), "-P5d"); +/// assert_eq!((-5.days()).to_string(), "-P5D"); +/// assert_eq!((-5).days().to_string(), "-P5D"); /// ``` /// /// # Example: alternative via span parsing @@ -3929,7 +3931,7 @@ impl_to_span!(i64); /// let zdt1: Zoned = "2024-07-06 17:40-04[America/New_York]".parse()?; /// let zdt2: Zoned = "2024-11-05 08:00-05[America/New_York]".parse()?; /// let span = zdt1.until((Unit::Year, &zdt2))?; -/// assert_eq!(span.to_string(), "P3m29dT14h20m"); +/// assert_eq!(format!("{span:#}"), "3mo 29d 14h 20m"); /// /// # Ok::<(), Box>(()) /// ``` @@ -6507,7 +6509,7 @@ mod tests { .nanoseconds(10); insta::assert_snapshot!( span, - @"P1y2m3w4dT5h6m7.00800901s", + @"P1Y2M3W4DT5H6M7.00800901S", ); insta::assert_snapshot!( alloc::format!("{span:#}"), @@ -6575,27 +6577,27 @@ mod tests { insta::assert_snapshot!( p("1 day").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("+1 day").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("-1 day").unwrap(), - @"-P1d", + @"-P1D", ); insta::assert_snapshot!( p("P1d").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("+P1d").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("-P1d").unwrap(), - @"-P1d", + @"-P1D", ); insta::assert_snapshot!( @@ -6620,27 +6622,27 @@ mod tests { insta::assert_snapshot!( p("1 day").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("+1 day").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("-1 day").unwrap(), - @"-P1d", + @"-P1D", ); insta::assert_snapshot!( p("P1d").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("+P1d").unwrap(), - @"P1d", + @"P1D", ); insta::assert_snapshot!( p("-P1d").unwrap(), - @"-P1d", + @"-P1D", ); insta::assert_snapshot!( diff --git a/src/timestamp.rs b/src/timestamp.rs index 3fb0f093..316ab33c 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -1521,11 +1521,11 @@ impl Timestamp { /// /// // The default limits durations to using "seconds" as the biggest unit. /// let span = ts1.until(ts2)?; - /// assert_eq!(span.to_string(), "PT730641929.9999965s"); + /// assert_eq!(span.to_string(), "PT730641929.9999965S"); /// /// // But we can ask for units all the way up to hours. /// let span = ts1.until((Unit::Hour, ts2))?; - /// assert_eq!(span.to_string(), "PT202956h5m29.9999965s"); + /// assert_eq!(span.to_string(), "PT202956H5M29.9999965S"); /// /// # Ok::<(), Box>(()) /// ``` diff --git a/src/zoned.rs b/src/zoned.rs index d17a62ee..2ae7fd27 100644 --- a/src/zoned.rs +++ b/src/zoned.rs @@ -2366,11 +2366,11 @@ impl Zoned { /// /// // The default limits durations to using "hours" as the biggest unit. /// let span = zdt1.until(&zdt2)?; - /// assert_eq!(span.to_string(), "PT202956h5m29.9999965s"); + /// assert_eq!(span.to_string(), "PT202956H5M29.9999965S"); /// /// // But we can ask for units all the way up to years. /// let span = zdt1.until((Unit::Year, &zdt2))?; - /// assert_eq!(span.to_string(), "P23y1m24dT12h5m29.9999965s"); + /// assert_eq!(span.to_string(), "P23Y1M24DT12H5M29.9999965S"); /// # Ok::<(), Box>(()) /// ``` /// diff --git a/tests/tc39_262/span/round.rs b/tests/tc39_262/span/round.rs index 949f383e..6b26063f 100644 --- a/tests/tc39_262/span/round.rs +++ b/tests/tc39_262/span/round.rs @@ -210,14 +210,14 @@ fn duration_out_of_range_added_to_relative() -> Result { let relative = SpanRound::new().relative(d); insta::assert_snapshot!( sp.round(relative.smallest(Unit::Year)).unwrap_err(), - @"failed to add P2000000dT170000000h to 2000-01-01T00:00:00: failed to add overflowing span, P7083333d, from adding PT170000000h to 00:00:00, to 7475-10-25: parameter 'days' with value 7083333 is not in the required range of -4371587..=2932896", + @"failed to add P2000000DT170000000H to 2000-01-01T00:00:00: failed to add overflowing span, P7083333D, from adding PT170000000H to 00:00:00, to 7475-10-25: parameter 'days' with value 7083333 is not in the required range of -4371587..=2932896", ); let sp = -2_000_000.days().hours(170_000_000); let relative = SpanRound::new().relative(d); insta::assert_snapshot!( sp.round(relative.smallest(Unit::Year)).unwrap_err(), - @"failed to add -P2000000dT170000000h to 2000-01-01T00:00:00: failed to add overflowing span, -P7083334d, from adding -PT170000000h to 00:00:00, to -003476-03-09: parameter 'days' with value -7083334 is not in the required range of -4371587..=2932896", + @"failed to add -P2000000DT170000000H to 2000-01-01T00:00:00: failed to add overflowing span, -P7083334D, from adding -PT170000000H to 00:00:00, to -003476-03-09: parameter 'days' with value -7083334 is not in the required range of -4371587..=2932896", ); Ok(()) @@ -611,7 +611,7 @@ fn out_of_range_when_adjusting_rounded_days() -> Result { insta::assert_snapshot!( sp.round(options).unwrap_err(), // Kind of a brutal error message... - @"failed to add P1dT631107331200.999999999s to 1970-01-01T00:00:00+00:00[UTC]: failed to add span PT631107331200.999999999s to timestamp 1970-01-02T00:00:00Z (which was created from 1970-01-02T00:00:00): adding PT631107331200.999999999s to 1970-01-02T00:00:00Z overflowed: parameter 'span' with value 631107331200999999999 is not in the required range of -377705023201000000000..=253402207200999999999", + @"failed to add P1DT631107331200.999999999S to 1970-01-01T00:00:00+00:00[UTC]: failed to add span PT631107331200.999999999S to timestamp 1970-01-02T00:00:00Z (which was created from 1970-01-02T00:00:00): adding PT631107331200.999999999S to 1970-01-02T00:00:00Z overflowed: parameter 'span' with value 631107331200999999999 is not in the required range of -377705023201000000000..=253402207200999999999", ); Ok(())