From 5db253117b4f6223ef62954141b0a42eabc1d171 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 07:25:49 +0200 Subject: [PATCH 1/6] Add `date!` macro --- src/lib.rs | 2 ++ src/macros.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/macros.rs diff --git a/src/lib.rs b/src/lib.rs index 18b9226e40..7331616d19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -521,6 +521,8 @@ pub use time_delta::TimeDelta; /// Alias of [`TimeDelta`]. pub type Duration = TimeDelta; +mod macros; + use core::fmt; /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000000..970186c142 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,57 @@ +//! Macro's for easy initialization of date and time values. + +/// Create a [`NaiveDate`](crate::naive::NaiveDate) with a statically known value. +/// +/// Supported formats are 'year-month-day' and 'year-ordinal'. +/// +/// The input is checked at compile time. +/// +/// Note: rustfmt wants to add spaces around `-` in this macro. +/// For nice formatting use `#[rustfmt::skip::macros(date)]`, or use as `date! {2023-09-08}` +/// +/// # Examples +/// ``` +/// use chrono::date; +/// +/// assert_eq!(date!(2023-09-08), date!(2023-251)); +/// ``` +#[macro_export] +macro_rules! date { + ($y:literal-$m:literal-$d:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_ymd_opt($y, $m, $d) { + Some(d) => d, + None => panic!("invalid calendar date"), + }; + DATE + } + }}; + ($y:literal-$o:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_yo_opt($y, $o) { + Some(d) => d, + None => panic!("invalid ordinal date"), + }; + DATE + } + }}; +} + +#[cfg(test)] +#[rustfmt::skip::macros(date)] +mod tests { + use crate::NaiveDate; + + #[test] + fn init_macros() { + assert_eq!(date!(2023-09-08), NaiveDate::from_ymd_opt(2023, 9, 8).unwrap()); + assert_eq!(date!(2023-253), NaiveDate::from_yo_opt(2023, 253).unwrap()); + } + + #[test] + fn macros_are_const() { + const DATE: NaiveDate = date!(2023-09-08); + } +} From 31ce22341d0552ccd18dbf5be74fc38c5a11698b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 24 Sep 2023 08:37:56 +0200 Subject: [PATCH 2/6] Add `time!` macro --- src/macros.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 970186c142..c1165785f0 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -39,19 +39,66 @@ macro_rules! date { }}; } +/// Create a [`NaiveTime`](crate::naive::NaiveTime) with a statically known value. +/// +/// Supported formats are 'hour:minute' and 'hour:minute:second'. +/// +/// The input is checked at compile time. +/// +/// # Examples +/// ``` +/// use chrono::time; +/// # use chrono::Timelike; +/// +/// assert_eq!(time!(7:03), time!(7:03:00)); +/// let leap_second = time!(23:59:60); +/// # assert!(leap_second.second() == 59 && leap_second.nanosecond() == 1_000_000_000); +/// ``` +#[macro_export] +macro_rules! time { + ($h:literal:$m:literal:$s:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const SECS_NANOS: (u32, u32) = match $s { + 60u32 => (59, 1_000_000_000), + s => (s, 0), + }; + const TIME: $crate::NaiveTime = + match $crate::NaiveTime::from_hms_nano_opt($h, $m, SECS_NANOS.0, SECS_NANOS.1) { + Some(t) => t, + None => panic!("invalid time"), + }; + TIME + } + }}; + ($h:literal:$m:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const TIME: $crate::NaiveTime = match $crate::NaiveTime::from_hms_opt($h, $m, 0) { + Some(t) => t, + None => panic!("invalid time"), + }; + TIME + } + }}; +} + #[cfg(test)] #[rustfmt::skip::macros(date)] mod tests { - use crate::NaiveDate; + use crate::{NaiveDate, NaiveTime}; #[test] fn init_macros() { assert_eq!(date!(2023-09-08), NaiveDate::from_ymd_opt(2023, 9, 8).unwrap()); assert_eq!(date!(2023-253), NaiveDate::from_yo_opt(2023, 253).unwrap()); + assert_eq!(time!(7:03), NaiveTime::from_hms_opt(7, 3, 0).unwrap()); + assert_eq!(time!(7:03:25), NaiveTime::from_hms_opt(7, 3, 25).unwrap()); } #[test] fn macros_are_const() { const DATE: NaiveDate = date!(2023-09-08); + const TIME: NaiveTime = time!(7:03:25); } } From 248baf6ba29e1f74a47a4b5d9c516c201a9751b4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 24 Sep 2023 08:39:46 +0200 Subject: [PATCH 3/6] Add `datetime!` macro --- src/macros.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index c1165785f0..62aa83bcb3 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -83,10 +83,58 @@ macro_rules! time { }}; } +/// Create a [`NaiveDateTime`](crate::naive::NaiveDateTime) with a statically known value. +/// +/// The input is checked at compile time. +/// +/// # Examples +/// ``` +/// use chrono::datetime; +/// +/// let _ = datetime!(2023-09-08 7:03); +/// let _ = datetime!(2023-09-08 7:03:25); +/// ``` +#[macro_export] +macro_rules! datetime { + ($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_ymd_opt($y, $m, $d) { + Some(d) => d, + None => panic!("invalid calendar date"), + }; + const SECS_NANOS: (u32, u32) = match $s { + 60u32 => (59, 1_000_000_000), + s => (s, 0), + }; + const TIME: $crate::NaiveTime = + match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) { + Some(t) => t, + None => panic!("invalid time"), + }; + DATE.and_time(TIME) + } + }}; + ($y:literal-$m:literal-$d:literal $h:literal:$min:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_ymd_opt($y, $m, $d) { + Some(d) => d, + None => panic!("invalid calendar date"), + }; + const TIME: $crate::NaiveTime = match $crate::NaiveTime::from_hms_opt($h, $min, 0) { + Some(t) => t, + None => panic!("invalid time"), + }; + DATE.and_time(TIME) + } + }}; +} + #[cfg(test)] #[rustfmt::skip::macros(date)] mod tests { - use crate::{NaiveDate, NaiveTime}; + use crate::{NaiveDate, NaiveDateTime, NaiveTime}; #[test] fn init_macros() { @@ -94,11 +142,25 @@ mod tests { assert_eq!(date!(2023-253), NaiveDate::from_yo_opt(2023, 253).unwrap()); assert_eq!(time!(7:03), NaiveTime::from_hms_opt(7, 3, 0).unwrap()); assert_eq!(time!(7:03:25), NaiveTime::from_hms_opt(7, 3, 25).unwrap()); + assert_eq!( + time!(23:59:60), + NaiveTime::from_hms_nano_opt(23, 59, 59, 1_000_000_000).unwrap() + ); + assert_eq!( + datetime!(2023-09-08 7:03), + NaiveDate::from_ymd_opt(2023, 9, 8).unwrap().and_hms_opt(7, 3, 0).unwrap(), + ); + assert_eq!( + datetime!(2023-09-08 7:03:25), + NaiveDate::from_ymd_opt(2023, 9, 8).unwrap().and_hms_opt(7, 3, 25).unwrap(), + ); } #[test] fn macros_are_const() { const DATE: NaiveDate = date!(2023-09-08); const TIME: NaiveTime = time!(7:03:25); + const NAIVEDATETIME: NaiveDateTime = datetime!(2023-09-08 7:03:25); + assert_eq!(DATE.and_time(TIME), NAIVEDATETIME); } } From 327b520ed585f254f58fdb338da71dc6d6a2d9cd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 24 Sep 2023 08:41:39 +0200 Subject: [PATCH 4/6] Add `offset!` macro --- src/macros.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 62aa83bcb3..b401913dd4 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -131,10 +131,78 @@ macro_rules! datetime { }}; } +/// Create a [`FixedOffset`](crate::FixedOffset) with a statically known value. +/// +/// Supported formats are '(+|-)hour:minute' and '(+|-)hour:minute:second'. +/// +/// We don't allow an hour-only format such as `+01`. That would also make the macro parse `+0001` +/// as the same value, which should mean the same as `+00:01`. +/// +/// The input is checked at compile time. +/// +/// # Examples +/// ``` +/// use chrono::offset; +/// +/// assert_eq!(offset!(+01:30), offset!(+01:30:00)); +/// assert_eq!(offset!(-5:00), offset!(-5:00:00)); +/// ``` +#[macro_export] +macro_rules! offset { + (+$h:literal:$m:literal:$s:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + assert!($h < 24 || $m < 60 || $s < 60, "invalid offset"); + const OFFSET: $crate::FixedOffset = + match $crate::FixedOffset::east_opt($h * 3600 + $m * 60 + $s) { + Some(t) => t, + None => panic!("invalid offset"), + }; + OFFSET + } + }}; + (-$h:literal:$m:literal:$s:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + assert!($h < 24 || $m < 60 || $s < 60, "invalid offset"); + const OFFSET: $crate::FixedOffset = + match $crate::FixedOffset::west_opt($h * 3600 + $m * 60 + $s) { + Some(t) => t, + None => panic!("invalid offset"), + }; + OFFSET + } + }}; + (+$h:literal:$m:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + assert!($h < 24 || $m < 60, "invalid offset"); + const OFFSET: $crate::FixedOffset = + match $crate::FixedOffset::east_opt($h * 3600 + $m * 60) { + Some(t) => t, + None => panic!("invalid offset"), + }; + OFFSET + } + }}; + (-$h:literal:$m:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + assert!($h < 24 || $m < 60, "invalid offset"); + const OFFSET: $crate::FixedOffset = + match $crate::FixedOffset::west_opt($h * 3600 + $m * 60) { + Some(t) => t, + None => panic!("invalid offset"), + }; + OFFSET + } + }}; +} + #[cfg(test)] #[rustfmt::skip::macros(date)] mod tests { - use crate::{NaiveDate, NaiveDateTime, NaiveTime}; + use crate::{FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; #[test] fn init_macros() { @@ -154,6 +222,10 @@ mod tests { datetime!(2023-09-08 7:03:25), NaiveDate::from_ymd_opt(2023, 9, 8).unwrap().and_hms_opt(7, 3, 25).unwrap(), ); + assert_eq!(offset!(+05:43), FixedOffset::east_opt(20_580).unwrap()); + assert_eq!(offset!(-05:43), FixedOffset::east_opt(-20_580).unwrap()); + assert_eq!(offset!(+05:43:21), FixedOffset::east_opt(20_601).unwrap()); + assert_eq!(offset!(-05:43:21), FixedOffset::east_opt(-20_601).unwrap()); } #[test] @@ -162,5 +234,8 @@ mod tests { const TIME: NaiveTime = time!(7:03:25); const NAIVEDATETIME: NaiveDateTime = datetime!(2023-09-08 7:03:25); assert_eq!(DATE.and_time(TIME), NAIVEDATETIME); + + const OFFSET_1: FixedOffset = offset!(+02:00); + const OFFSET_2: FixedOffset = offset!(-02:00); } } From 99a70d631fd4a82134fa4684f49326b3e3f9decf Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 24 Sep 2023 08:43:19 +0200 Subject: [PATCH 5/6] Extend `datetime!` macro to also create `DateTime` --- src/macros.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index b401913dd4..3347ac1278 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -83,19 +83,84 @@ macro_rules! time { }}; } -/// Create a [`NaiveDateTime`](crate::naive::NaiveDateTime) with a statically known value. +/// Create a [`NaiveDateTime`] or [`DateTime`] with a statically known value. /// /// The input is checked at compile time. /// +/// [`NaiveDateTime`]: crate::naive::NaiveDateTime +/// [`DateTime`]: crate::DateTime +/// /// # Examples /// ``` /// use chrono::datetime; /// +/// // NaiveDateTime /// let _ = datetime!(2023-09-08 7:03); /// let _ = datetime!(2023-09-08 7:03:25); +/// // DateTime +/// let _ = datetime!(2023-09-08 7:03:25+02:00); +/// let _ = datetime!(2023-09-08 7:03:25-02:00); /// ``` #[macro_export] macro_rules! datetime { + ($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal+$hh:literal:$mm:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_ymd_opt($y, $m, $d) { + Some(d) => d, + None => panic!("invalid calendar date"), + }; + const SECS_NANOS: (u32, u32) = match $s { + 60u32 => (59, 1_000_000_000), + s => (s, 0), + }; + const TIME: $crate::NaiveTime = + match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) { + Some(t) => t, + None => panic!("invalid time"), + }; + assert!($hh < 24u32 || $mm < 60, "invalid offset"); + const OFFSET: $crate::FixedOffset = + match $crate::FixedOffset::east_opt(($hh * 3600 + $mm * 60) as i32) { + Some(o) => o, + None => panic!("invalid offset"), + }; + const DT: $crate::NaiveDateTime = match DATE.and_time(TIME).checked_sub_offset(OFFSET) { + Some(o) => o, + None => panic!("datetime out of range"), + }; + $crate::DateTime::<$crate::FixedOffset>::from_naive_utc_and_offset(DT, OFFSET) + } + }}; + ($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal-$hh:literal:$mm:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_ymd_opt($y, $m, $d) { + Some(d) => d, + None => panic!("invalid calendar date"), + }; + const SECS_NANOS: (u32, u32) = match $s { + 60u32 => (59, 1_000_000_000), + s => (s, 0), + }; + const TIME: $crate::NaiveTime = + match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) { + Some(t) => t, + None => panic!("invalid time"), + }; + assert!($hh < 24u32 || $mm < 60, "invalid offset"); + const OFFSET: $crate::FixedOffset = + match $crate::FixedOffset::west_opt(($hh * 3600 + $mm * 60) as i32) { + Some(o) => o, + None => panic!("invalid offset"), + }; + const DT: $crate::NaiveDateTime = match DATE.and_time(TIME).checked_sub_offset(OFFSET) { + Some(o) => o, + None => panic!("datetime out of range"), + }; + $crate::DateTime::<$crate::FixedOffset>::from_naive_utc_and_offset(DT, OFFSET) + } + }}; ($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal) => {{ #[allow(clippy::zero_prefixed_literal)] { @@ -202,7 +267,7 @@ macro_rules! offset { #[cfg(test)] #[rustfmt::skip::macros(date)] mod tests { - use crate::{FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; + use crate::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone}; #[test] fn init_macros() { @@ -222,6 +287,14 @@ mod tests { datetime!(2023-09-08 7:03:25), NaiveDate::from_ymd_opt(2023, 9, 8).unwrap().and_hms_opt(7, 3, 25).unwrap(), ); + assert_eq!( + datetime!(2023-09-08 7:03:25+02:00), + FixedOffset::east_opt(7200).unwrap().with_ymd_and_hms(2023, 9, 8, 7, 3, 25).unwrap(), + ); + assert_eq!( + datetime!(2023-09-08 7:03:25-02:00), + FixedOffset::east_opt(-7200).unwrap().with_ymd_and_hms(2023, 9, 8, 7, 3, 25).unwrap(), + ); assert_eq!(offset!(+05:43), FixedOffset::east_opt(20_580).unwrap()); assert_eq!(offset!(-05:43), FixedOffset::east_opt(-20_580).unwrap()); assert_eq!(offset!(+05:43:21), FixedOffset::east_opt(20_601).unwrap()); @@ -236,6 +309,11 @@ mod tests { assert_eq!(DATE.and_time(TIME), NAIVEDATETIME); const OFFSET_1: FixedOffset = offset!(+02:00); + const DATETIME_1: DateTime = datetime!(2023-09-08 7:03:25+02:00); + assert_eq!(OFFSET_1.from_local_datetime(&NAIVEDATETIME).unwrap(), DATETIME_1); + const OFFSET_2: FixedOffset = offset!(-02:00); + const DATETIME_2: DateTime = datetime!(2023-09-08 7:03:25-02:00); + assert_eq!(OFFSET_2.from_local_datetime(&NAIVEDATETIME).unwrap(), DATETIME_2); } } From 2d52c4b6de1bd38a6e319b90c5d3ab5e130bb34d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 27 Mar 2024 22:04:16 +0100 Subject: [PATCH 6/6] Support parsing fractional seconds --- src/lib.rs | 3 +- src/macros.rs | 96 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7331616d19..437f809b78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -521,7 +521,8 @@ pub use time_delta::TimeDelta; /// Alias of [`TimeDelta`]. pub type Duration = TimeDelta; -mod macros; +#[doc(hidden)] +pub mod macros; use core::fmt; diff --git a/src/macros.rs b/src/macros.rs index 3347ac1278..a2a8131c94 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -41,7 +41,7 @@ macro_rules! date { /// Create a [`NaiveTime`](crate::naive::NaiveTime) with a statically known value. /// -/// Supported formats are 'hour:minute' and 'hour:minute:second'. +/// Supported formats are 'hour:minute', 'hour:minute:second' and 'hour:minute:second.fraction'. /// /// The input is checked at compile time. /// @@ -51,11 +51,24 @@ macro_rules! date { /// # use chrono::Timelike; /// /// assert_eq!(time!(7:03), time!(7:03:00)); +/// assert_eq!(time!(12:34:56.789), time!(12:34:56.789000)); /// let leap_second = time!(23:59:60); /// # assert!(leap_second.second() == 59 && leap_second.nanosecond() == 1_000_000_000); /// ``` #[macro_export] macro_rules! time { + ($h:literal:$m:literal:$s:literal) => {{ + #[allow(clippy::zero_prefixed_literal)] + { + const SECS_NANOS: (u32, u32) = $crate::macros::parse_sec_and_nano(stringify!($s)); + const TIME: $crate::NaiveTime = + match $crate::NaiveTime::from_hms_nano_opt($h, $m, SECS_NANOS.0, SECS_NANOS.1) { + Some(t) => t, + None => panic!("invalid time"), + }; + TIME + } + }}; ($h:literal:$m:literal:$s:literal) => {{ #[allow(clippy::zero_prefixed_literal)] { @@ -97,6 +110,7 @@ macro_rules! time { /// // NaiveDateTime /// let _ = datetime!(2023-09-08 7:03); /// let _ = datetime!(2023-09-08 7:03:25); +/// let _ = datetime!(2023-09-08 7:03:25.01234); /// // DateTime /// let _ = datetime!(2023-09-08 7:03:25+02:00); /// let _ = datetime!(2023-09-08 7:03:25-02:00); @@ -110,10 +124,7 @@ macro_rules! datetime { Some(d) => d, None => panic!("invalid calendar date"), }; - const SECS_NANOS: (u32, u32) = match $s { - 60u32 => (59, 1_000_000_000), - s => (s, 0), - }; + const SECS_NANOS: (u32, u32) = $crate::macros::parse_sec_and_nano(stringify!($s)); const TIME: $crate::NaiveTime = match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) { Some(t) => t, @@ -139,10 +150,7 @@ macro_rules! datetime { Some(d) => d, None => panic!("invalid calendar date"), }; - const SECS_NANOS: (u32, u32) = match $s { - 60u32 => (59, 1_000_000_000), - s => (s, 0), - }; + const SECS_NANOS: (u32, u32) = $crate::macros::parse_sec_and_nano(stringify!($s)); const TIME: $crate::NaiveTime = match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) { Some(t) => t, @@ -168,10 +176,7 @@ macro_rules! datetime { Some(d) => d, None => panic!("invalid calendar date"), }; - const SECS_NANOS: (u32, u32) = match $s { - 60u32 => (59, 1_000_000_000), - s => (s, 0), - }; + const SECS_NANOS: (u32, u32) = $crate::macros::parse_sec_and_nano(stringify!($s)); const TIME: $crate::NaiveTime = match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) { Some(t) => t, @@ -264,10 +269,55 @@ macro_rules! offset { }}; } +/// Helper method that allows our macro's to parse a second and optional fractional second. +/// +/// This makes use of the fact that a `literal` macro argument can accept multiple types, such as an +/// integer or a floating point value. So a macro accepts both `12` and `12.34` as valid inputs (and +/// other literals we don't care about). However we don't know the type of the literal until use. +/// +/// With `stringify!()` it is possible to get back the original string argument to the macro. This +/// `parse_sec_and_nano` is a function to parse the value in const context. +#[doc(hidden)] +pub const fn parse_sec_and_nano(s: &str) -> (u32, u32) { + const fn digit(d: u8) -> u32 { + if d < b'0' || d > b'9' { + panic!("not a digit"); + } + (d - b'0') as u32 + } + const fn digit_opt(s: &[u8], index: usize) -> u32 { + match index < s.len() { + true => digit(s[index]), + false => 0, + } + } + let s = s.as_bytes(); + let second = digit(s[0]) * 10 + digit(s[1]); + let nano = if s.len() >= 4 && s[2] == b'.' && s.len() <= 12 { + digit_opt(s, 3) * 100_000_000 + + digit_opt(s, 4) * 10_000_000 + + digit_opt(s, 5) * 1_000_000 + + digit_opt(s, 6) * 100_000 + + digit_opt(s, 7) * 10_000 + + digit_opt(s, 8) * 1000 + + digit_opt(s, 9) * 100 + + digit_opt(s, 10) * 10 + + digit_opt(s, 11) + } else if s.len() != 2 { + panic!("invalid time"); + } else { + 0 + }; + match second { + 60 => (59, 1_000_000_000 + nano), + _ => (second, nano), + } +} + #[cfg(test)] #[rustfmt::skip::macros(date)] mod tests { - use crate::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone}; + use crate::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; #[test] fn init_macros() { @@ -275,6 +325,11 @@ mod tests { assert_eq!(date!(2023-253), NaiveDate::from_yo_opt(2023, 253).unwrap()); assert_eq!(time!(7:03), NaiveTime::from_hms_opt(7, 3, 0).unwrap()); assert_eq!(time!(7:03:25), NaiveTime::from_hms_opt(7, 3, 25).unwrap()); + assert_eq!(time!(7:03:25.01), NaiveTime::from_hms_milli_opt(7, 3, 25, 10).unwrap()); + assert_eq!( + time!(7:03:25.123456789), + NaiveTime::from_hms_nano_opt(7, 3, 25, 123_456_789).unwrap() + ); assert_eq!( time!(23:59:60), NaiveTime::from_hms_nano_opt(23, 59, 59, 1_000_000_000).unwrap() @@ -287,10 +342,23 @@ mod tests { datetime!(2023-09-08 7:03:25), NaiveDate::from_ymd_opt(2023, 9, 8).unwrap().and_hms_opt(7, 3, 25).unwrap(), ); + assert_eq!( + datetime!(2023-09-08 7:03:25.01), + NaiveDate::from_ymd_opt(2023, 9, 8).unwrap().and_hms_milli_opt(7, 3, 25, 10).unwrap(), + ); assert_eq!( datetime!(2023-09-08 7:03:25+02:00), FixedOffset::east_opt(7200).unwrap().with_ymd_and_hms(2023, 9, 8, 7, 3, 25).unwrap(), ); + assert_eq!( + datetime!(2023-09-08 7:03:25.01+02:00), + FixedOffset::east_opt(7200) + .unwrap() + .with_ymd_and_hms(2023, 9, 8, 7, 3, 25) + .unwrap() + .with_nanosecond(10_000_000) + .unwrap(), + ); assert_eq!( datetime!(2023-09-08 7:03:25-02:00), FixedOffset::east_opt(-7200).unwrap().with_ymd_and_hms(2023, 9, 8, 7, 3, 25).unwrap(),