irox_time/
datetime.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3
4//!
5//! Contains [`UTCDateTime`] and associated elements to represent a [`Date`] and [`Time`] in UTC
6//!
7
8extern crate alloc;
9use crate::epoch::{UnixTimestamp, UNIX_EPOCH};
10use crate::format::iso8601::{BASIC_DATE_TIME_OF_DAY, ISO8601_DATE_TIME};
11use crate::format::{Format, FormatError, FormatParser};
12use crate::gregorian::Date;
13use crate::julian::JulianDate;
14use crate::Time;
15pub use alloc::string::String;
16use core::fmt::{Display, Formatter};
17use core::ops::{Add, AddAssign, Sub};
18use irox_units::bounds::GreaterThanEqualToValueError;
19use irox_units::units::duration::Duration;
20
21///
22/// Represents a Gregorian Date and Time in UTC
23#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
24pub struct UTCDateTime {
25    pub(crate) date: Date,
26    pub(crate) time: Time,
27}
28
29impl Display for UTCDateTime {
30    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
31        f.write_fmt(format_args!("{}", self.format(&BASIC_DATE_TIME_OF_DAY)))
32    }
33}
34
35impl UTCDateTime {
36    ///
37    /// New UTC Date and Time
38    #[must_use]
39    pub fn new(date: Date, time: Time) -> UTCDateTime {
40        UTCDateTime { date, time }
41    }
42
43    ///
44    /// New UTC Date and Time from the specified values
45    pub fn try_from_values(
46        year: i32,
47        month: u8,
48        day: u8,
49        hour: u8,
50        minute: u8,
51        seconds: u8,
52    ) -> Result<UTCDateTime, GreaterThanEqualToValueError<u8>> {
53        let date = Date::try_from_values(year, month, day)?;
54        let time = Time::from_hms(hour, minute, seconds)?;
55        Ok(UTCDateTime::new(date, time))
56    }
57
58    ///
59    /// New UTC date and Time from the specified values (fractional seconds)
60    pub fn try_from_values_f64(
61        year: i32,
62        month: u8,
63        day: u8,
64        hour: u8,
65        minute: u8,
66        seconds: f64,
67    ) -> Result<UTCDateTime, GreaterThanEqualToValueError<f64>> {
68        let date = Date::try_from_values(year, month, day)?;
69        let time = Time::from_hms_f64(hour, minute, seconds)?;
70        Ok(UTCDateTime::new(date, time))
71    }
72
73    ///
74    /// Returns the Gregorian Date portion of this UTCDateTime
75    #[must_use]
76    pub fn get_date(&self) -> Date {
77        self.date
78    }
79
80    ///
81    /// Returns the Time portion of this UTCDateTime
82    #[must_use]
83    pub fn get_time(&self) -> Time {
84        self.time
85    }
86
87    ///
88    /// Returns the current instant in time as reported by the local system
89    /// clock.
90    #[must_use]
91    #[cfg(feature = "std")]
92    pub fn now() -> UTCDateTime {
93        UnixTimestamp::now().into()
94    }
95
96    #[must_use]
97    pub fn format<T: Format<UTCDateTime>>(&self, format: &T) -> String {
98        format.format(self)
99    }
100
101    /// Formats this date as a extended ISO8601 Date & Time, `2023-12-31T05:10:25Z`
102    #[must_use]
103    pub fn format_iso8601_extended(&self) -> String {
104        ISO8601_DATE_TIME.format(self)
105    }
106    /// Formats this date as a basic ISO8601 Date & Time, `20231231T051025Z`, suitable for filenames
107    #[must_use]
108    pub fn format_iso8601_basic(&self) -> String {
109        BASIC_DATE_TIME_OF_DAY.format(self)
110    }
111    /// Attempts to parse the provided string as either a [`crate::format::iso8601::BasicDateTimeOfDay`] or a [`crate::format::iso8601::ExtendedDateTimeFormat`]
112    pub fn try_from_iso8601(val: &str) -> Result<Self, FormatError> {
113        ISO8601_DATE_TIME.try_from(val)
114    }
115}
116
117impl From<&UnixTimestamp> for UTCDateTime {
118    fn from(value: &UnixTimestamp) -> Self {
119        let date = value.as_date();
120        let remaining_seconds = value.get_offset().as_seconds_f64()
121            - date.as_unix_timestamp().get_offset().as_seconds_f64();
122
123        let time = Time::from_seconds_f64(remaining_seconds).unwrap_or_default();
124
125        UTCDateTime { date, time }
126    }
127}
128impl From<UnixTimestamp> for UTCDateTime {
129    fn from(value: UnixTimestamp) -> Self {
130        let date = value.as_date();
131        let remaining_seconds = value.get_offset().as_seconds_f64()
132            - date.as_unix_timestamp().get_offset().as_seconds_f64();
133
134        let time = Time::from_seconds_f64(remaining_seconds).unwrap_or_default();
135
136        UTCDateTime { date, time }
137    }
138}
139
140impl From<UTCDateTime> for UnixTimestamp {
141    fn from(value: UTCDateTime) -> Self {
142        let mut date_dur = value.date - UNIX_EPOCH.get_gregorian_date();
143        date_dur += Into::<Duration>::into(value.time);
144        Self::from_offset(date_dur)
145    }
146}
147impl From<&UTCDateTime> for UnixTimestamp {
148    fn from(value: &UTCDateTime) -> Self {
149        let mut date_dur = value.date - UNIX_EPOCH.get_gregorian_date();
150        date_dur += Into::<Duration>::into(value.time);
151        Self::from_offset(date_dur)
152    }
153}
154
155impl From<UTCDateTime> for JulianDate {
156    fn from(value: UTCDateTime) -> Self {
157        let mut date: JulianDate = value.date.into();
158        let time: Duration = value.time.into();
159        date += time;
160        date
161    }
162}
163
164impl From<&UTCDateTime> for JulianDate {
165    fn from(value: &UTCDateTime) -> Self {
166        let mut date: JulianDate = value.date.into();
167        let time: Duration = value.time.into();
168        date += time;
169        date
170    }
171}
172
173impl Sub<Self> for UTCDateTime {
174    type Output = Duration;
175
176    fn sub(self, rhs: Self) -> Self::Output {
177        let ts1: JulianDate = self.into();
178        let ts2: JulianDate = rhs.into();
179
180        ts1 - ts2
181    }
182}
183impl Sub<&Self> for UTCDateTime {
184    type Output = Duration;
185
186    fn sub(self, rhs: &Self) -> Self::Output {
187        let ts1: JulianDate = self.into();
188        let ts2: JulianDate = rhs.into();
189
190        ts1 - ts2
191    }
192}
193
194impl Add<Duration> for UTCDateTime {
195    type Output = UTCDateTime;
196
197    fn add(self, rhs: Duration) -> Self::Output {
198        let (time, excess) = self.time.wrapping_add(rhs);
199        let date = self.date + excess;
200        UTCDateTime { date, time }
201    }
202}
203impl Add<&Duration> for UTCDateTime {
204    type Output = UTCDateTime;
205
206    fn add(self, rhs: &Duration) -> Self::Output {
207        let (time, excess) = self.time.wrapping_add(*rhs);
208        let date = self.date + excess;
209        UTCDateTime { date, time }
210    }
211}
212
213impl AddAssign<Duration> for UTCDateTime {
214    fn add_assign(&mut self, rhs: Duration) {
215        let (time, excess) = self.time.wrapping_add(rhs);
216        self.time = time;
217        self.date += excess;
218    }
219}
220impl AddAssign<&Duration> for UTCDateTime {
221    fn add_assign(&mut self, rhs: &Duration) {
222        let (time, excess) = self.time.wrapping_add(*rhs);
223        self.time = time;
224        self.date += excess;
225    }
226}