Skip to content

Commit 852d32d

Browse files
committed
Support parsing fractional seconds
1 parent 3126c6b commit 852d32d

File tree

2 files changed

+84
-15
lines changed

2 files changed

+84
-15
lines changed

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,8 @@ pub use duration::Duration;
489489
#[doc(no_inline)]
490490
pub use duration::OutOfRangeError;
491491

492-
mod macros;
492+
#[doc(hidden)]
493+
pub mod macros;
493494

494495
use core::fmt;
495496

src/macros.rs

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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]
5859
macro_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)]
269319
mod 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

Comments
 (0)