1use crate::{Encoder, Error, ErrorKind, Result, Tag};
9use core::{fmt, str::FromStr, time::Duration};
10
11#[cfg(feature = "std")]
12use std::time::{SystemTime, UNIX_EPOCH};
13
14#[cfg(feature = "time")]
15use time::PrimitiveDateTime;
16
17const MIN_YEAR: u16 = 1970;
19
20const MAX_UNIX_DURATION: Duration = Duration::from_secs(253_402_300_799);
25
26#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
33pub struct DateTime {
34 year: u16,
38
39 month: u8,
41
42 day: u8,
44
45 hour: u8,
47
48 minutes: u8,
50
51 seconds: u8,
53
54 unix_duration: Duration,
56}
57
58impl DateTime {
59 pub fn new(year: u16, month: u8, day: u8, hour: u8, minutes: u8, seconds: u8) -> Result<Self> {
61 if year < MIN_YEAR
63 || !(1..=12).contains(&month)
64 || !(1..=31).contains(&day)
65 || !(0..=23).contains(&hour)
66 || !(0..=59).contains(&minutes)
67 || !(0..=59).contains(&seconds)
68 {
69 return Err(ErrorKind::DateTime.into());
70 }
71
72 let leap_years =
73 ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
74
75 let is_leap_year = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
76
77 let (mut ydays, mdays): (u16, u8) = match month {
78 1 => (0, 31),
79 2 if is_leap_year => (31, 29),
80 2 => (31, 28),
81 3 => (59, 31),
82 4 => (90, 30),
83 5 => (120, 31),
84 6 => (151, 30),
85 7 => (181, 31),
86 8 => (212, 31),
87 9 => (243, 30),
88 10 => (273, 31),
89 11 => (304, 30),
90 12 => (334, 31),
91 _ => return Err(ErrorKind::DateTime.into()),
92 };
93
94 if day > mdays || day == 0 {
95 return Err(ErrorKind::DateTime.into());
96 }
97
98 ydays += day as u16 - 1;
99
100 if is_leap_year && month > 2 {
101 ydays += 1;
102 }
103
104 let days = (year - 1970) as u64 * 365 + leap_years as u64 + ydays as u64;
105 let time = seconds as u64 + (minutes as u64 * 60) + (hour as u64 * 3600);
106 let unix_duration = Duration::from_secs(time + days * 86400);
107
108 if unix_duration > MAX_UNIX_DURATION {
109 return Err(ErrorKind::DateTime.into());
110 }
111
112 Ok(Self {
113 year,
114 month,
115 day,
116 hour,
117 minutes,
118 seconds,
119 unix_duration,
120 })
121 }
122
123 pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
127 if unix_duration > MAX_UNIX_DURATION {
128 return Err(ErrorKind::DateTime.into());
129 }
130
131 let secs_since_epoch = unix_duration.as_secs();
132
133 const LEAPOCH: i64 = 11017;
135 const DAYS_PER_400Y: i64 = 365 * 400 + 97;
136 const DAYS_PER_100Y: i64 = 365 * 100 + 24;
137 const DAYS_PER_4Y: i64 = 365 * 4 + 1;
138
139 let days = (secs_since_epoch / 86400) as i64 - LEAPOCH;
140 let secs_of_day = secs_since_epoch % 86400;
141
142 let mut qc_cycles = days / DAYS_PER_400Y;
143 let mut remdays = days % DAYS_PER_400Y;
144
145 if remdays < 0 {
146 remdays += DAYS_PER_400Y;
147 qc_cycles -= 1;
148 }
149
150 let mut c_cycles = remdays / DAYS_PER_100Y;
151 if c_cycles == 4 {
152 c_cycles -= 1;
153 }
154 remdays -= c_cycles * DAYS_PER_100Y;
155
156 let mut q_cycles = remdays / DAYS_PER_4Y;
157 if q_cycles == 25 {
158 q_cycles -= 1;
159 }
160 remdays -= q_cycles * DAYS_PER_4Y;
161
162 let mut remyears = remdays / 365;
163 if remyears == 4 {
164 remyears -= 1;
165 }
166 remdays -= remyears * 365;
167
168 let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
169
170 let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
171 let mut mon = 0;
172 for mon_len in months.iter() {
173 mon += 1;
174 if remdays < *mon_len {
175 break;
176 }
177 remdays -= *mon_len;
178 }
179 let mday = remdays + 1;
180 let mon = if mon + 2 > 12 {
181 year += 1;
182 mon - 10
183 } else {
184 mon + 2
185 };
186
187 let second = secs_of_day % 60;
188 let mins_of_day = secs_of_day / 60;
189 let minute = mins_of_day % 60;
190 let hour = mins_of_day / 60;
191
192 Self::new(
193 year as u16,
194 mon,
195 mday as u8,
196 hour as u8,
197 minute as u8,
198 second as u8,
199 )
200 }
201
202 pub fn year(&self) -> u16 {
204 self.year
205 }
206
207 pub fn month(&self) -> u8 {
209 self.month
210 }
211
212 pub fn day(&self) -> u8 {
214 self.day
215 }
216
217 pub fn hour(&self) -> u8 {
219 self.hour
220 }
221
222 pub fn minutes(&self) -> u8 {
224 self.minutes
225 }
226
227 pub fn seconds(&self) -> u8 {
229 self.seconds
230 }
231
232 pub fn unix_duration(&self) -> Duration {
234 self.unix_duration
235 }
236
237 #[cfg(feature = "std")]
239 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
240 pub fn from_system_time(time: SystemTime) -> Result<Self> {
241 time.duration_since(UNIX_EPOCH)
242 .map_err(|_| ErrorKind::DateTime.into())
243 .and_then(Self::from_unix_duration)
244 }
245
246 #[cfg(feature = "std")]
248 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
249 pub fn to_system_time(&self) -> SystemTime {
250 UNIX_EPOCH + self.unix_duration()
251 }
252}
253
254impl FromStr for DateTime {
255 type Err = Error;
256
257 fn from_str(s: &str) -> Result<Self> {
258 match *s.as_bytes() {
259 [year1, year2, year3, year4, b'-', month1, month2, b'-', day1, day2, b'T', hour1, hour2, b':', min1, min2, b':', sec1, sec2, b'Z'] =>
260 {
261 let tag = Tag::GeneralizedTime;
262 let year = decode_decimal(tag, year1, year2).map_err(|_| ErrorKind::DateTime)?
263 as u16
264 * 100
265 + decode_decimal(tag, year3, year4).map_err(|_| ErrorKind::DateTime)? as u16;
266 let month = decode_decimal(tag, month1, month2).map_err(|_| ErrorKind::DateTime)?;
267 let day = decode_decimal(tag, day1, day2).map_err(|_| ErrorKind::DateTime)?;
268 let hour = decode_decimal(tag, hour1, hour2).map_err(|_| ErrorKind::DateTime)?;
269 let minutes = decode_decimal(tag, min1, min2).map_err(|_| ErrorKind::DateTime)?;
270 let seconds = decode_decimal(tag, sec1, sec2).map_err(|_| ErrorKind::DateTime)?;
271 Self::new(year, month, day, hour, minutes, seconds)
272 }
273 _ => Err(ErrorKind::DateTime.into()),
274 }
275 }
276}
277
278impl fmt::Display for DateTime {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 write!(
281 f,
282 "{:02}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
283 self.year, self.month, self.day, self.hour, self.minutes, self.seconds
284 )
285 }
286}
287
288#[cfg(feature = "std")]
289#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
290impl From<DateTime> for SystemTime {
291 fn from(time: DateTime) -> SystemTime {
292 time.to_system_time()
293 }
294}
295
296#[cfg(feature = "std")]
297#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
298impl From<&DateTime> for SystemTime {
299 fn from(time: &DateTime) -> SystemTime {
300 time.to_system_time()
301 }
302}
303
304#[cfg(feature = "std")]
305#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
306impl TryFrom<SystemTime> for DateTime {
307 type Error = Error;
308
309 fn try_from(time: SystemTime) -> Result<DateTime> {
310 DateTime::from_system_time(time)
311 }
312}
313
314#[cfg(feature = "std")]
315#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
316impl TryFrom<&SystemTime> for DateTime {
317 type Error = Error;
318
319 fn try_from(time: &SystemTime) -> Result<DateTime> {
320 DateTime::from_system_time(*time)
321 }
322}
323
324#[cfg(feature = "time")]
325#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
326impl TryFrom<DateTime> for PrimitiveDateTime {
327 type Error = Error;
328
329 fn try_from(time: DateTime) -> Result<PrimitiveDateTime> {
330 let month = (time.month() as u8).try_into()?;
331 let date = time::Date::from_calendar_date(time.year() as i32, month, time.day())?;
332 let time = time::Time::from_hms(time.hour(), time.minutes(), time.seconds())?;
333
334 Ok(PrimitiveDateTime::new(date, time))
335 }
336}
337
338#[cfg(feature = "time")]
339#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
340impl TryFrom<PrimitiveDateTime> for DateTime {
341 type Error = Error;
342
343 fn try_from(time: PrimitiveDateTime) -> Result<DateTime> {
344 DateTime::new(
345 time.year() as u16,
346 time.month().into(),
347 time.day(),
348 time.hour(),
349 time.minute(),
350 time.second(),
351 )
352 }
353}
354
355pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8> {
357 if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) {
358 Ok((hi - b'0') * 10 + (lo - b'0'))
359 } else {
360 Err(tag.value_error())
361 }
362}
363
364pub(crate) fn encode_decimal(encoder: &mut Encoder<'_>, tag: Tag, value: u8) -> Result<()> {
366 let hi_val = value / 10;
367
368 if hi_val >= 10 {
369 return Err(tag.value_error());
370 }
371
372 encoder.byte(hi_val + b'0')?;
373 encoder.byte((value % 10) + b'0')
374}
375
376#[cfg(test)]
377mod tests {
378 use super::DateTime;
379
380 fn is_date_valid(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> bool {
382 DateTime::new(year, month, day, hour, minute, second).is_ok()
383 }
384
385 #[test]
386 fn feb_leap_year_handling() {
387 assert!(is_date_valid(2000, 2, 29, 0, 0, 0));
388 assert!(!is_date_valid(2001, 2, 29, 0, 0, 0));
389 assert!(!is_date_valid(2100, 2, 29, 0, 0, 0));
390 }
391
392 #[test]
393 fn from_str() {
394 let datetime = "2001-01-02T12:13:14Z".parse::<DateTime>().unwrap();
395 assert_eq!(datetime.year(), 2001);
396 assert_eq!(datetime.month(), 1);
397 assert_eq!(datetime.day(), 2);
398 assert_eq!(datetime.hour(), 12);
399 assert_eq!(datetime.minutes(), 13);
400 assert_eq!(datetime.seconds(), 14);
401 }
402
403 #[cfg(feature = "alloc")]
404 #[test]
405 fn display() {
406 use alloc::string::ToString;
407 let datetime = DateTime::new(2001, 01, 02, 12, 13, 14).unwrap();
408 assert_eq!(&datetime.to_string(), "2001-01-02T12:13:14Z");
409 }
410}