mod fixed;
pub use fixed::FixedOffset;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
pub use fixed::ArchivedFixedOffset;
mod local;
pub use local::Local;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
pub use local::ArchivedLocal;
mod utc;
pub use utc::Utc;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
pub use utc::ArchivedUtc;
use crate::datetime::DateTime;
use crate::naive::{NaiveDate, NaiveDateTime};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum MappedLocalTime<T> {
Single(T),
Ambiguous(T, T),
None,
}
pub type LocalResult<T> = MappedLocalTime<T>;
impl<T> MappedLocalTime<T> {
pub fn single(self) -> Option<T> {
match self {
MappedLocalTime::Single(t) => Some(t),
_ => None,
}
}
pub fn earliest(self) -> Option<T> {
match self {
MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(t, _) => Some(t),
_ => None,
}
}
pub fn latest(self) -> Option<T> {
match self {
MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(_, t) => Some(t),
_ => None,
}
}
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> MappedLocalTime<U> {
match self {
MappedLocalTime::None => MappedLocalTime::None,
MappedLocalTime::Single(v) => MappedLocalTime::Single(f(v)),
MappedLocalTime::Ambiguous(min, max) => MappedLocalTime::Ambiguous(f(min), f(max)),
}
}
pub(crate) fn and_then<U, F: FnMut(T) -> Option<U>>(self, mut f: F) -> MappedLocalTime<U> {
match self {
MappedLocalTime::None => MappedLocalTime::None,
MappedLocalTime::Single(v) => match f(v) {
Some(new) => MappedLocalTime::Single(new),
None => MappedLocalTime::None,
},
MappedLocalTime::Ambiguous(min, max) => match (f(min), f(max)) {
(Some(min), Some(max)) => MappedLocalTime::Ambiguous(min, max),
_ => MappedLocalTime::None,
},
}
}
}
impl<T: core::fmt::Debug> MappedLocalTime<T> {
#[track_caller]
pub fn unwrap(self) -> T {
match self {
MappedLocalTime::None => panic!("No such local time"),
MappedLocalTime::Single(t) => t,
MappedLocalTime::Ambiguous(t1, t2) => {
panic!("Ambiguous local time, ranging from {t1:?} to {t2:?}")
}
}
}
}
pub trait Offset: Sized + Clone + core::fmt::Debug {
fn fix(&self) -> FixedOffset;
}
pub trait TimeZone: Sized + Clone {
type Offset: Offset;
fn with_ymd_and_hms(
&self,
year: i32,
month: u32,
day: u32,
hour: u32,
min: u32,
sec: u32,
) -> MappedLocalTime<DateTime<Self>> {
match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec))
{
Some(dt) => self.from_local_datetime(&dt),
None => MappedLocalTime::None,
}
}
#[deprecated(note = "use `timestamp_opt()` instead")]
fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime<Self> {
self.timestamp_opt(secs, nsecs).unwrap()
}
fn timestamp_opt(&self, secs: i64, nsecs: u32) -> MappedLocalTime<DateTime<Self>> {
match DateTime::from_timestamp(secs, nsecs) {
Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())),
None => MappedLocalTime::None,
}
}
#[deprecated(note = "use `timestamp_millis_opt()` instead")]
fn timestamp_millis(&self, millis: i64) -> DateTime<Self> {
self.timestamp_millis_opt(millis).unwrap()
}
fn timestamp_millis_opt(&self, millis: i64) -> MappedLocalTime<DateTime<Self>> {
match DateTime::from_timestamp_millis(millis) {
Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())),
None => MappedLocalTime::None,
}
}
fn timestamp_nanos(&self, nanos: i64) -> DateTime<Self> {
self.from_utc_datetime(&DateTime::from_timestamp_nanos(nanos).naive_utc())
}
fn timestamp_micros(&self, micros: i64) -> MappedLocalTime<DateTime<Self>> {
match DateTime::from_timestamp_micros(micros) {
Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())),
None => MappedLocalTime::None,
}
}
fn from_offset(offset: &Self::Offset) -> Self;
fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<Self::Offset>;
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<Self::Offset>;
#[allow(clippy::wrong_self_convention)]
fn from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<DateTime<Self>> {
self.offset_from_local_datetime(local).and_then(|off| {
local
.checked_sub_offset(off.fix())
.map(|dt| DateTime::from_naive_utc_and_offset(dt, off))
})
}
fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset;
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset;
#[allow(clippy::wrong_self_convention)]
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Self> {
DateTime::from_naive_utc_and_offset(*utc, self.offset_from_utc_datetime(utc))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{Datelike, Timelike};
#[test]
fn single_earliest_latest_on_single_variant() {
let m = MappedLocalTime::Single(5);
assert_eq!(m.single(), Some(5));
assert_eq!(m.earliest(), Some(5));
assert_eq!(m.latest(), Some(5));
}
#[test]
fn single_earliest_latest_on_ambiguous_variant() {
let m = MappedLocalTime::Ambiguous(1, 2);
assert_eq!(m.single(), None);
assert_eq!(m.earliest(), Some(1));
assert_eq!(m.latest(), Some(2));
}
#[test]
fn single_earliest_latest_on_none_variant() {
let m: MappedLocalTime<i32> = MappedLocalTime::None;
assert_eq!(m.single(), None);
assert_eq!(m.earliest(), None);
assert_eq!(m.latest(), None);
}
#[test]
fn map_transforms_the_contained_values() {
assert_eq!(MappedLocalTime::Single(5).map(|x| x * 2), MappedLocalTime::Single(10));
assert_eq!(MappedLocalTime::Ambiguous(1, 2).map(|x| x * 2), MappedLocalTime::Ambiguous(2, 4));
let none: MappedLocalTime<i32> = MappedLocalTime::None;
assert_eq!(none.map(|x| x * 2), MappedLocalTime::None);
}
#[test]
fn and_then_propagates_failure() {
assert_eq!(MappedLocalTime::Single(5).and_then(|x| Some(x + 1)), MappedLocalTime::Single(6));
assert_eq!(MappedLocalTime::Single(5).and_then(|_: i32| None::<i32>), MappedLocalTime::None);
assert_eq!(
MappedLocalTime::Ambiguous(1, 2).and_then(|x| Some(x + 1)),
MappedLocalTime::Ambiguous(2, 3)
);
}
#[test]
fn unwrap_returns_the_single_value() {
assert_eq!(MappedLocalTime::Single(5).unwrap(), 5);
}
#[test]
#[should_panic]
fn unwrap_panics_on_none() {
let m: MappedLocalTime<i32> = MappedLocalTime::None;
m.unwrap();
}
#[test]
#[should_panic]
fn unwrap_panics_on_ambiguous() {
MappedLocalTime::Ambiguous(1, 2).unwrap();
}
#[test]
fn local_result_is_an_alias() {
let m: LocalResult<i32> = MappedLocalTime::Single(5);
assert_eq!(m, MappedLocalTime::Single(5));
}
#[test]
fn with_ymd_and_hms_builds_the_expected_datetime() {
let dt = Utc.with_ymd_and_hms(2023, 6, 15, 12, 30, 0).single().unwrap();
assert_eq!(dt.year(), 2023);
assert_eq!(dt.hour(), 12);
}
#[test]
fn with_ymd_and_hms_rejects_invalid_calendar_dates() {
assert_eq!(Utc.with_ymd_and_hms(2023, 2, 30, 0, 0, 0).single(), None);
}
#[test]
fn timestamp_opt_and_deprecated_timestamp_agree() {
let opt = Utc.timestamp_opt(1_700_000_000, 0).single().unwrap();
#[allow(deprecated)]
let panicking = Utc.timestamp(1_700_000_000, 0);
assert_eq!(opt, panicking);
}
#[test]
fn timestamp_millis_micros_and_nanos_agree_on_a_round_value() {
let from_secs = Utc.timestamp_opt(1_700_000_000, 0).single().unwrap();
let from_millis = Utc.timestamp_millis_opt(1_700_000_000_000).single().unwrap();
let from_micros = Utc.timestamp_micros(1_700_000_000_000_000).single().unwrap();
let from_nanos = Utc.timestamp_nanos(1_700_000_000_000_000_000);
assert_eq!(from_secs, from_millis);
assert_eq!(from_secs, from_micros);
assert_eq!(from_secs, from_nanos);
}
#[test]
fn from_local_datetime_and_from_utc_datetime_are_identity_for_utc() {
let naive = NaiveDate::from_ymd_opt(2023, 6, 15).unwrap().and_hms_opt(12, 30, 0).unwrap();
let from_local = Utc.from_local_datetime(&naive).single().unwrap();
let from_utc = Utc.from_utc_datetime(&naive);
assert_eq!(from_local, from_utc);
assert_eq!(from_utc.naive_utc(), naive);
}
}