@@ -41,7 +41,7 @@ macro_rules! date {
4141
4242/// Create a [`NaiveTime`](crate::naive::NaiveTime) with a statically known value.
4343///
44- /// Supported formats are 'hour:minute' and 'hour:minute:second'.
44+ /// Supported formats are 'hour:minute', 'hour:minute:second' and 'hour:minute:second.fraction '.
4545///
4646/// The input is checked at compile time.
4747///
@@ -51,11 +51,24 @@ macro_rules! date {
5151/// # use chrono::Timelike;
5252///
5353/// assert_eq!(time!(7:03), time!(7:03:00));
54+ /// assert_eq!(time!(12:34:56.789), time!(12:34:56.789000));
5455/// let leap_second = time!(23:59:60);
5556/// # assert!(leap_second.second() == 59 && leap_second.nanosecond() == 1_000_000_000);
5657/// ```
5758#[ macro_export]
5859macro_rules! time {
60+ ( $h: literal: $m: literal: $s: literal) => { {
61+ #[ allow( clippy:: zero_prefixed_literal) ]
62+ {
63+ const SECS_NANOS : ( u32 , u32 ) = $crate:: macros:: parse_sec_and_nano( stringify!( $s) ) ;
64+ const TIME : $crate:: NaiveTime =
65+ match $crate:: NaiveTime :: from_hms_nano_opt( $h, $m, SECS_NANOS . 0 , SECS_NANOS . 1 ) {
66+ Some ( t) => t,
67+ None => panic!( "invalid time" ) ,
68+ } ;
69+ TIME
70+ }
71+ } } ;
5972 ( $h: literal: $m: literal: $s: literal) => { {
6073 #[ allow( clippy:: zero_prefixed_literal) ]
6174 {
@@ -97,6 +110,7 @@ macro_rules! time {
97110/// // NaiveDateTime
98111/// let _ = datetime!(2023-09-08 7:03);
99112/// let _ = datetime!(2023-09-08 7:03:25);
113+ /// let _ = datetime!(2023-09-08 7:03:25.01234);
100114/// // DateTime<FixedOffset>
101115/// let _ = datetime!(2023-09-08 7:03:25+02:00);
102116/// let _ = datetime!(2023-09-08 7:03:25-02:00);
@@ -110,10 +124,7 @@ macro_rules! datetime {
110124 Some ( d) => d,
111125 None => panic!( "invalid calendar date" ) ,
112126 } ;
113- const SECS_NANOS : ( u32 , u32 ) = match $s {
114- 60u32 => ( 59 , 1_000_000_000 ) ,
115- s => ( s, 0 ) ,
116- } ;
127+ const SECS_NANOS : ( u32 , u32 ) = $crate:: macros:: parse_sec_and_nano( stringify!( $s) ) ;
117128 const TIME : $crate:: NaiveTime =
118129 match $crate:: NaiveTime :: from_hms_nano_opt( $h, $min, SECS_NANOS . 0 , SECS_NANOS . 1 ) {
119130 Some ( t) => t,
@@ -139,10 +150,7 @@ macro_rules! datetime {
139150 Some ( d) => d,
140151 None => panic!( "invalid calendar date" ) ,
141152 } ;
142- const SECS_NANOS : ( u32 , u32 ) = match $s {
143- 60u32 => ( 59 , 1_000_000_000 ) ,
144- s => ( s, 0 ) ,
145- } ;
153+ const SECS_NANOS : ( u32 , u32 ) = $crate:: macros:: parse_sec_and_nano( stringify!( $s) ) ;
146154 const TIME : $crate:: NaiveTime =
147155 match $crate:: NaiveTime :: from_hms_nano_opt( $h, $min, SECS_NANOS . 0 , SECS_NANOS . 1 ) {
148156 Some ( t) => t,
@@ -168,10 +176,7 @@ macro_rules! datetime {
168176 Some ( d) => d,
169177 None => panic!( "invalid calendar date" ) ,
170178 } ;
171- const SECS_NANOS : ( u32 , u32 ) = match $s {
172- 60u32 => ( 59 , 1_000_000_000 ) ,
173- s => ( s, 0 ) ,
174- } ;
179+ const SECS_NANOS : ( u32 , u32 ) = $crate:: macros:: parse_sec_and_nano( stringify!( $s) ) ;
175180 const TIME : $crate:: NaiveTime =
176181 match $crate:: NaiveTime :: from_hms_nano_opt( $h, $min, SECS_NANOS . 0 , SECS_NANOS . 1 ) {
177182 Some ( t) => t,
@@ -264,17 +269,67 @@ macro_rules! offset {
264269 } } ;
265270}
266271
272+ /// Helper method that allows our macro's to parse a second and optional fractional second.
273+ ///
274+ /// This makes use of the fact that a `literal` macro argument can accept multiple types, such as an
275+ /// integer or a floating point value. So a macro accepts both `12` and `12.34` as valid inputs (and
276+ /// other literals we don't care about). However we don't know the type of the literal until use.
277+ ///
278+ /// With `stringify!()` it is possible to get back the original string argument to the macro. This
279+ /// `parse_sec_and_nano` is a function to parse the value in const context.
280+ #[ doc( hidden) ]
281+ pub const fn parse_sec_and_nano ( s : & str ) -> ( u32 , u32 ) {
282+ const fn digit ( d : u8 ) -> u32 {
283+ if d < b'0' && d > b'9' {
284+ panic ! ( "not a digit" ) ;
285+ }
286+ ( d - b'0' ) as u32
287+ }
288+ const fn digit_opt ( s : & [ u8 ] , index : usize ) -> u32 {
289+ match index < s. len ( ) {
290+ true => digit ( s[ index] ) ,
291+ false => 0 ,
292+ }
293+ }
294+ let s = s. as_bytes ( ) ;
295+ let second = digit ( s[ 0 ] ) * 10 + digit ( s[ 1 ] ) ;
296+ let nano = if s. len ( ) >= 4 && s[ 2 ] == b'.' && s. len ( ) <= 12 {
297+ digit_opt ( s, 3 ) * 100_000_000
298+ + digit_opt ( s, 4 ) * 10_000_000
299+ + digit_opt ( s, 5 ) * 1_000_000
300+ + digit_opt ( s, 6 ) * 100_000
301+ + digit_opt ( s, 7 ) * 10_000
302+ + digit_opt ( s, 8 ) * 1000
303+ + digit_opt ( s, 9 ) * 100
304+ + digit_opt ( s, 10 ) * 10
305+ + digit_opt ( s, 11 )
306+ } else if s. len ( ) != 2 {
307+ panic ! ( "invalid time" ) ;
308+ } else {
309+ 0
310+ } ;
311+ match second {
312+ 60 => ( 59 , 1_000_000_000 + nano) ,
313+ _ => ( second, nano) ,
314+ }
315+ }
316+
267317#[ cfg( test) ]
268318#[ rustfmt:: skip:: macros( date) ]
269319mod tests {
270- use crate :: { DateTime , FixedOffset , NaiveDate , NaiveDateTime , NaiveTime , TimeZone } ;
320+ use crate :: { DateTime , FixedOffset , NaiveDate , NaiveDateTime , NaiveTime , TimeZone , Timelike } ;
271321
272322 #[ test]
273323 fn init_macros ( ) {
274324 assert_eq ! ( date!( 2023 -09 -08 ) , NaiveDate :: from_ymd_opt( 2023 , 9 , 8 ) . unwrap( ) ) ;
275325 assert_eq ! ( date!( 2023 -253 ) , NaiveDate :: from_yo_opt( 2023 , 253 ) . unwrap( ) ) ;
276326 assert_eq ! ( time!( 7 : 03 ) , NaiveTime :: from_hms_opt( 7 , 3 , 0 ) . unwrap( ) ) ;
277327 assert_eq ! ( time!( 7 : 03 : 25 ) , NaiveTime :: from_hms_opt( 7 , 3 , 25 ) . unwrap( ) ) ;
328+ assert_eq ! ( time!( 7 : 03 : 25.01 ) , NaiveTime :: from_hms_milli_opt( 7 , 3 , 25 , 10 ) . unwrap( ) ) ;
329+ assert_eq ! (
330+ time!( 7 : 03 : 25.123456789 ) ,
331+ NaiveTime :: from_hms_nano_opt( 7 , 3 , 25 , 123_456_789 ) . unwrap( )
332+ ) ;
278333 assert_eq ! (
279334 time!( 23 : 59 : 60 ) ,
280335 NaiveTime :: from_hms_nano_opt( 23 , 59 , 59 , 1_000_000_000 ) . unwrap( )
@@ -287,10 +342,23 @@ mod tests {
287342 datetime!( 2023 -09 -08 7 : 03 : 25 ) ,
288343 NaiveDate :: from_ymd_opt( 2023 , 9 , 8 ) . unwrap( ) . and_hms_opt( 7 , 3 , 25 ) . unwrap( ) ,
289344 ) ;
345+ assert_eq ! (
346+ datetime!( 2023 -09 -08 7 : 03 : 25.01 ) ,
347+ NaiveDate :: from_ymd_opt( 2023 , 9 , 8 ) . unwrap( ) . and_hms_milli_opt( 7 , 3 , 25 , 10 ) . unwrap( ) ,
348+ ) ;
290349 assert_eq ! (
291350 datetime!( 2023 -09 -08 7 : 03 : 25 +02 : 00 ) ,
292351 FixedOffset :: east_opt( 7200 ) . unwrap( ) . with_ymd_and_hms( 2023 , 9 , 8 , 7 , 3 , 25 ) . unwrap( ) ,
293352 ) ;
353+ assert_eq ! (
354+ datetime!( 2023 -09 -08 7 : 03 : 25.01 +02 : 00 ) ,
355+ FixedOffset :: east_opt( 7200 )
356+ . unwrap( )
357+ . with_ymd_and_hms( 2023 , 9 , 8 , 7 , 3 , 25 )
358+ . unwrap( )
359+ . with_nanosecond( 10_000_000 )
360+ . unwrap( ) ,
361+ ) ;
294362 assert_eq ! (
295363 datetime!( 2023 -09 -08 7 : 03 : 25 -02 : 00 ) ,
296364 FixedOffset :: east_opt( -7200 ) . unwrap( ) . with_ymd_and_hms( 2023 , 9 , 8 , 7 , 3 , 25 ) . unwrap( ) ,
0 commit comments