Skip to content

Commit 972a08f

Browse files
committed
Extend datetime! macro to also create DateTime<FixedOffset>
1 parent 277feb1 commit 972a08f

2 files changed

Lines changed: 90 additions & 2 deletions

File tree

src/datetime/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,19 @@ impl DateTime<FixedOffset> {
852852
let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?;
853853
parsed.to_datetime().map(|d| (d, remainder))
854854
}
855+
856+
// FIXME: remove when our MSRV is 1.61+
857+
// This method is used by the `datetime!` macro because `DateTime::from_naive_utc_and_offset`
858+
// can't be made const yet.
859+
// Trait bounds in const function / implementation blocks were not supported until 1.61.
860+
#[allow(unused)] // only used when we invoke the `datetime!` macro
861+
#[doc(hidden)]
862+
pub const fn __from_naive_utc_and_fixed_offset(
863+
datetime: NaiveDateTime,
864+
offset: FixedOffset,
865+
) -> Self {
866+
DateTime { datetime, offset }
867+
}
855868
}
856869

857870
impl<Tz: TimeZone> DateTime<Tz>

src/macros.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,81 @@ macro_rules! time {
8383
}};
8484
}
8585

86-
/// Create a `NaiveDateTime` with a statically known value.
86+
/// Create a `NaiveDateTime` or `DateTime<FixedOffset>` with a statically known value.
8787
///
8888
/// The input is checked at compile time.
8989
///
9090
/// # Examples
9191
/// ```
9292
/// use chrono::datetime;
9393
///
94+
/// // NaiveDateTime
9495
/// let _ = datetime!(2023-09-08 7:03);
9596
/// let _ = datetime!(2023-09-08 7:03:25);
97+
/// // DateTime<FixedOffset>
98+
/// let _ = datetime!(2023-09-08 7:03:25+02:00);
99+
/// let _ = datetime!(2023-09-08 7:03:25-02:00);
96100
/// ```
97101
#[macro_export]
98102
macro_rules! datetime {
103+
($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal+$hh:literal:$mm:literal) => {{
104+
#[allow(clippy::zero_prefixed_literal)]
105+
{
106+
const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_ymd_opt($y, $m, $d) {
107+
Some(d) => d,
108+
None => panic!("invalid calendar date"),
109+
};
110+
const SECS_NANOS: (u32, u32) = match $s {
111+
60u32 => (59, 1_000_000_000),
112+
s => (s, 0),
113+
};
114+
const TIME: $crate::NaiveTime =
115+
match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) {
116+
Some(t) => t,
117+
None => panic!("invalid time"),
118+
};
119+
assert!($hh < 24u32 || $mm < 60, "invalid offset");
120+
const OFFSET: $crate::FixedOffset =
121+
match $crate::FixedOffset::east_opt(($hh * 3600 + $mm * 60) as i32) {
122+
Some(o) => o,
123+
None => panic!("invalid offset"),
124+
};
125+
const DT: $crate::NaiveDateTime = match DATE.and_time(TIME).checked_sub_offset(OFFSET) {
126+
Some(o) => o,
127+
None => panic!("datetime out of range"),
128+
};
129+
$crate::DateTime::<$crate::FixedOffset>::__from_naive_utc_and_fixed_offset(DT, OFFSET)
130+
}
131+
}};
132+
($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal-$hh:literal:$mm:literal) => {{
133+
#[allow(clippy::zero_prefixed_literal)]
134+
{
135+
const DATE: $crate::NaiveDate = match $crate::NaiveDate::from_ymd_opt($y, $m, $d) {
136+
Some(d) => d,
137+
None => panic!("invalid calendar date"),
138+
};
139+
const SECS_NANOS: (u32, u32) = match $s {
140+
60u32 => (59, 1_000_000_000),
141+
s => (s, 0),
142+
};
143+
const TIME: $crate::NaiveTime =
144+
match $crate::NaiveTime::from_hms_nano_opt($h, $min, SECS_NANOS.0, SECS_NANOS.1) {
145+
Some(t) => t,
146+
None => panic!("invalid time"),
147+
};
148+
assert!($hh < 24u32 || $mm < 60, "invalid offset");
149+
const OFFSET: $crate::FixedOffset =
150+
match $crate::FixedOffset::west_opt(($hh * 3600 + $mm * 60) as i32) {
151+
Some(o) => o,
152+
None => panic!("invalid offset"),
153+
};
154+
const DT: $crate::NaiveDateTime = match DATE.and_time(TIME).checked_sub_offset(OFFSET) {
155+
Some(o) => o,
156+
None => panic!("datetime out of range"),
157+
};
158+
$crate::DateTime::<$crate::FixedOffset>::__from_naive_utc_and_fixed_offset(DT, OFFSET)
159+
}
160+
}};
99161
($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal) => {{
100162
#[allow(clippy::zero_prefixed_literal)]
101163
{
@@ -202,7 +264,7 @@ macro_rules! offset {
202264
#[cfg(test)]
203265
#[rustfmt::skip::macros(date)]
204266
mod tests {
205-
use crate::{FixedOffset, NaiveDate, NaiveDateTime, NaiveTime};
267+
use crate::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};
206268

207269
#[test]
208270
fn init_macros() {
@@ -222,6 +284,14 @@ mod tests {
222284
datetime!(2023-09-08 7:03:25),
223285
NaiveDate::from_ymd_opt(2023, 9, 8).unwrap().and_hms_opt(7, 3, 25).unwrap(),
224286
);
287+
assert_eq!(
288+
datetime!(2023-09-08 7:03:25+02:00),
289+
FixedOffset::east_opt(7200).unwrap().with_ymd_and_hms(2023, 9, 8, 7, 3, 25).unwrap(),
290+
);
291+
assert_eq!(
292+
datetime!(2023-09-08 7:03:25-02:00),
293+
FixedOffset::east_opt(-7200).unwrap().with_ymd_and_hms(2023, 9, 8, 7, 3, 25).unwrap(),
294+
);
225295
assert_eq!(offset!(+05:43), FixedOffset::east_opt(20_580).unwrap());
226296
assert_eq!(offset!(-05:43), FixedOffset::east_opt(-20_580).unwrap());
227297
assert_eq!(offset!(+05:43:21), FixedOffset::east_opt(20_601).unwrap());
@@ -236,6 +306,11 @@ mod tests {
236306
assert_eq!(DATE.and_time(TIME), NAIVEDATETIME);
237307

238308
const OFFSET_1: FixedOffset = offset!(+02:00);
309+
const DATETIME_1: DateTime<FixedOffset> = datetime!(2023-09-08 7:03:25+02:00);
310+
assert_eq!(OFFSET_1.from_local_datetime(&NAIVEDATETIME).unwrap(), DATETIME_1);
311+
239312
const OFFSET_2: FixedOffset = offset!(-02:00);
313+
const DATETIME_2: DateTime<FixedOffset> = datetime!(2023-09-08 7:03:25-02:00);
314+
assert_eq!(OFFSET_2.from_local_datetime(&NAIVEDATETIME).unwrap(), DATETIME_2);
240315
}
241316
}

0 commit comments

Comments
 (0)