Skip to content

Commit

Permalink
Extend datetime! macro to also create DateTime<FixedOffset>
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Sep 25, 2023
1 parent 277feb1 commit 972a08f
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 2 deletions.
13 changes: 13 additions & 0 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,19 @@ impl DateTime<FixedOffset> {
let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?;
parsed.to_datetime().map(|d| (d, remainder))
}

// FIXME: remove when our MSRV is 1.61+
// This method is used by the `datetime!` macro because `DateTime::from_naive_utc_and_offset`
// can't be made const yet.
// Trait bounds in const function / implementation blocks were not supported until 1.61.
#[allow(unused)] // only used when we invoke the `datetime!` macro
#[doc(hidden)]
pub const fn __from_naive_utc_and_fixed_offset(
datetime: NaiveDateTime,
offset: FixedOffset,
) -> Self {
DateTime { datetime, offset }
}
}

impl<Tz: TimeZone> DateTime<Tz>
Expand Down
79 changes: 77 additions & 2 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,81 @@ macro_rules! time {
}};
}

/// Create a `NaiveDateTime` with a statically known value.
/// Create a `NaiveDateTime` or `DateTime<FixedOffset>` with a statically known value.
///
/// The input is checked at compile time.
///
/// # Examples
/// ```
/// use chrono::datetime;
///
/// // NaiveDateTime
/// let _ = datetime!(2023-09-08 7:03);
/// let _ = datetime!(2023-09-08 7:03:25);
/// // DateTime<FixedOffset>
/// 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_fixed_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_fixed_offset(DT, OFFSET)
}
}};
($y:literal-$m:literal-$d:literal $h:literal:$min:literal:$s:literal) => {{
#[allow(clippy::zero_prefixed_literal)]
{
Expand Down Expand Up @@ -202,7 +264,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() {
Expand All @@ -222,6 +284,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());
Expand All @@ -236,6 +306,11 @@ mod tests {
assert_eq!(DATE.and_time(TIME), NAIVEDATETIME);

const OFFSET_1: FixedOffset = offset!(+02:00);
const DATETIME_1: DateTime<FixedOffset> = 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<FixedOffset> = datetime!(2023-09-08 7:03:25-02:00);
assert_eq!(OFFSET_2.from_local_datetime(&NAIVEDATETIME).unwrap(), DATETIME_2);
}
}

0 comments on commit 972a08f

Please sign in to comment.