dos_date_time/dos_date_time/
convert.rs

1// SPDX-FileCopyrightText: 2025 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Implementations of conversions between [`DateTime`] and other types.
6
7#[cfg(feature = "chrono")]
8use chrono::NaiveDateTime;
9#[cfg(feature = "jiff")]
10use jiff::civil;
11use time::PrimitiveDateTime;
12
13use super::DateTime;
14use crate::error::DateTimeRangeError;
15
16impl From<DateTime> for PrimitiveDateTime {
17    /// Converts a `DateTime` to a [`PrimitiveDateTime`].
18    ///
19    /// # Examples
20    ///
21    /// ```
22    /// # use dos_date_time::{
23    /// #     DateTime,
24    /// #     time::{PrimitiveDateTime, macros::datetime},
25    /// # };
26    /// #
27    /// assert_eq!(
28    ///     PrimitiveDateTime::from(DateTime::MIN),
29    ///     datetime!(1980-01-01 00:00:00)
30    /// );
31    /// assert_eq!(
32    ///     PrimitiveDateTime::from(DateTime::MAX),
33    ///     datetime!(2107-12-31 23:59:58)
34    /// );
35    /// ```
36    fn from(dt: DateTime) -> Self {
37        let (date, time) = (dt.date().into(), dt.time().into());
38        Self::new(date, time)
39    }
40}
41
42#[cfg(feature = "chrono")]
43impl From<DateTime> for NaiveDateTime {
44    /// Converts a `DateTime` to a [`NaiveDateTime`].
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// # use dos_date_time::{DateTime, chrono::NaiveDateTime};
50    /// #
51    /// assert_eq!(
52    ///     NaiveDateTime::from(DateTime::MIN),
53    ///     "1980-01-01T00:00:00".parse::<NaiveDateTime>().unwrap()
54    /// );
55    /// assert_eq!(
56    ///     NaiveDateTime::from(DateTime::MAX),
57    ///     "2107-12-31T23:59:58".parse::<NaiveDateTime>().unwrap()
58    /// );
59    /// ```
60    fn from(dt: DateTime) -> Self {
61        let (date, time) = (dt.date().into(), dt.time().into());
62        Self::new(date, time)
63    }
64}
65
66#[cfg(feature = "jiff")]
67impl From<DateTime> for civil::DateTime {
68    /// Converts a `DateTime` to a [`civil::DateTime`].
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// # use dos_date_time::{DateTime, jiff::civil};
74    /// #
75    /// assert_eq!(
76    ///     civil::DateTime::from(DateTime::MIN),
77    ///     civil::date(1980, 1, 1).at(0, 0, 0, 0)
78    /// );
79    /// assert_eq!(
80    ///     civil::DateTime::from(DateTime::MAX),
81    ///     civil::date(2107, 12, 31).at(23, 59, 58, 0)
82    /// );
83    /// ```
84    fn from(dt: DateTime) -> Self {
85        let (date, time) = (dt.date().into(), dt.time().into());
86        Self::from_parts(date, time)
87    }
88}
89
90impl TryFrom<PrimitiveDateTime> for DateTime {
91    type Error = DateTimeRangeError;
92
93    /// Converts a [`PrimitiveDateTime`] to a `DateTime`.
94    ///
95    /// <div class="warning">
96    ///
97    /// The resolution of MS-DOS date and time is 2 seconds. So this method
98    /// rounds towards zero, truncating any fractional part of the exact result
99    /// of dividing seconds by 2.
100    ///
101    /// </div>
102    ///
103    /// # Errors
104    ///
105    /// Returns [`Err`] if `dt` is out of range for MS-DOS date and time.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// # use dos_date_time::{DateTime, time::macros::datetime};
111    /// #
112    /// assert_eq!(
113    ///     DateTime::try_from(datetime!(1980-01-01 00:00:00)),
114    ///     Ok(DateTime::MIN)
115    /// );
116    /// assert_eq!(
117    ///     DateTime::try_from(datetime!(2107-12-31 23:59:58)),
118    ///     Ok(DateTime::MAX)
119    /// );
120    ///
121    /// // Before `1980-01-01 00:00:00`.
122    /// assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59)).is_err());
123    /// // After `2107-12-31 23:59:59`.
124    /// assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00)).is_err());
125    /// ```
126    fn try_from(dt: PrimitiveDateTime) -> Result<Self, Self::Error> {
127        let (date, time) = (dt.date(), dt.time());
128        Self::from_date_time(date, time)
129    }
130}
131
132#[cfg(feature = "chrono")]
133impl TryFrom<NaiveDateTime> for DateTime {
134    type Error = DateTimeRangeError;
135
136    /// Converts a [`NaiveDateTime`] to a `DateTime`.
137    ///
138    /// <div class="warning">
139    ///
140    /// The resolution of MS-DOS date and time is 2 seconds. So this method
141    /// rounds towards zero, truncating any fractional part of the exact result
142    /// of dividing seconds by 2.
143    ///
144    /// </div>
145    ///
146    /// # Errors
147    ///
148    /// Returns [`Err`] if `dt` is out of range for MS-DOS date and time.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// # use dos_date_time::{DateTime, chrono::NaiveDateTime};
154    /// #
155    /// assert_eq!(
156    ///     DateTime::try_from("1980-01-01T00:00:00".parse::<NaiveDateTime>().unwrap()),
157    ///     Ok(DateTime::MIN)
158    /// );
159    /// assert_eq!(
160    ///     DateTime::try_from("2107-12-31T23:59:58".parse::<NaiveDateTime>().unwrap()),
161    ///     Ok(DateTime::MAX)
162    /// );
163    ///
164    /// // Before `1980-01-01 00:00:00`.
165    /// assert!(DateTime::try_from("1979-12-31T23:59:59".parse::<NaiveDateTime>().unwrap()).is_err());
166    /// // After `2107-12-31 23:59:59`.
167    /// assert!(DateTime::try_from("2108-01-01T00:00:00".parse::<NaiveDateTime>().unwrap()).is_err());
168    /// ```
169    fn try_from(dt: NaiveDateTime) -> Result<Self, Self::Error> {
170        let (date, time) = (dt.date().try_into()?, dt.time().into());
171        let dt = Self::new(date, time);
172        Ok(dt)
173    }
174}
175
176#[cfg(feature = "jiff")]
177impl TryFrom<civil::DateTime> for DateTime {
178    type Error = DateTimeRangeError;
179
180    /// Converts a [`civil::DateTime`] to a `DateTime`.
181    ///
182    /// <div class="warning">
183    ///
184    /// The resolution of MS-DOS date and time is 2 seconds. So this method
185    /// rounds towards zero, truncating any fractional part of the exact result
186    /// of dividing seconds by 2.
187    ///
188    /// </div>
189    ///
190    /// # Errors
191    ///
192    /// Returns [`Err`] if `dt` is out of range for MS-DOS date and time.
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// # use dos_date_time::{DateTime, jiff::civil};
198    /// #
199    /// assert_eq!(
200    ///     DateTime::try_from(civil::date(1980, 1, 1).at(0, 0, 0, 0)),
201    ///     Ok(DateTime::MIN)
202    /// );
203    /// assert_eq!(
204    ///     DateTime::try_from(civil::date(2107, 12, 31).at(23, 59, 58, 0)),
205    ///     Ok(DateTime::MAX)
206    /// );
207    ///
208    /// // Before `1980-01-01 00:00:00`.
209    /// assert!(DateTime::try_from(civil::date(1979, 12, 31).at(23, 59, 59, 0)).is_err());
210    /// // After `2107-12-31 23:59:59`.
211    /// assert!(DateTime::try_from(civil::date(2108, 1, 1).at(0, 0, 0, 0)).is_err());
212    /// ```
213    fn try_from(dt: civil::DateTime) -> Result<Self, Self::Error> {
214        let (date, time) = (dt.date().try_into()?, dt.time().into());
215        let dt = Self::new(date, time);
216        Ok(dt)
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use time::macros::datetime;
223
224    use super::*;
225    use crate::{Date, Time, error::DateTimeRangeErrorKind};
226
227    #[test]
228    fn from_date_time_to_primitive_date_time() {
229        assert_eq!(
230            PrimitiveDateTime::from(DateTime::MIN),
231            datetime!(1980-01-01 00:00:00)
232        );
233        // <https://devblogs.microsoft.com/oldnewthing/20030905-02/?p=42653>.
234        assert_eq!(
235            PrimitiveDateTime::from(DateTime::new(
236                Date::new(0b0010_1101_0111_1010).unwrap(),
237                Time::new(0b1001_1011_0010_0000).unwrap()
238            )),
239            datetime!(2002-11-26 19:25:00)
240        );
241        // <https://github.com/zip-rs/zip/blob/v0.6.4/src/types.rs#L553-L569>.
242        assert_eq!(
243            PrimitiveDateTime::from(DateTime::new(
244                Date::new(0b0100_1101_0111_0001).unwrap(),
245                Time::new(0b0101_0100_1100_1111).unwrap()
246            )),
247            datetime!(2018-11-17 10:38:30)
248        );
249        assert_eq!(
250            PrimitiveDateTime::from(DateTime::MAX),
251            datetime!(2107-12-31 23:59:58)
252        );
253    }
254
255    #[cfg(feature = "chrono")]
256    #[test]
257    fn from_date_time_to_chrono_naive_date_time() {
258        assert_eq!(
259            NaiveDateTime::from(DateTime::MIN),
260            "1980-01-01T00:00:00".parse::<NaiveDateTime>().unwrap()
261        );
262        // <https://devblogs.microsoft.com/oldnewthing/20030905-02/?p=42653>.
263        assert_eq!(
264            NaiveDateTime::from(DateTime::new(
265                Date::new(0b0010_1101_0111_1010).unwrap(),
266                Time::new(0b1001_1011_0010_0000).unwrap()
267            )),
268            "2002-11-26T19:25:00".parse::<NaiveDateTime>().unwrap()
269        );
270        // <https://github.com/zip-rs/zip/blob/v0.6.4/src/types.rs#L553-L569>.
271        assert_eq!(
272            NaiveDateTime::from(DateTime::new(
273                Date::new(0b0100_1101_0111_0001).unwrap(),
274                Time::new(0b0101_0100_1100_1111).unwrap()
275            )),
276            "2018-11-17T10:38:30".parse::<NaiveDateTime>().unwrap()
277        );
278        assert_eq!(
279            NaiveDateTime::from(DateTime::MAX),
280            "2107-12-31T23:59:58".parse::<NaiveDateTime>().unwrap()
281        );
282    }
283
284    #[cfg(feature = "jiff")]
285    #[test]
286    fn from_date_time_to_jiff_civil_date_time() {
287        assert_eq!(
288            civil::DateTime::from(DateTime::MIN),
289            civil::date(1980, 1, 1).at(0, 0, 0, 0)
290        );
291        // <https://devblogs.microsoft.com/oldnewthing/20030905-02/?p=42653>.
292        assert_eq!(
293            civil::DateTime::from(DateTime::new(
294                Date::new(0b0010_1101_0111_1010).unwrap(),
295                Time::new(0b1001_1011_0010_0000).unwrap()
296            )),
297            civil::date(2002, 11, 26).at(19, 25, 0, 0)
298        );
299        // <https://github.com/zip-rs/zip/blob/v0.6.4/src/types.rs#L553-L569>.
300        assert_eq!(
301            civil::DateTime::from(DateTime::new(
302                Date::new(0b0100_1101_0111_0001).unwrap(),
303                Time::new(0b0101_0100_1100_1111).unwrap()
304            )),
305            civil::date(2018, 11, 17).at(10, 38, 30, 0)
306        );
307        assert_eq!(
308            civil::DateTime::from(DateTime::MAX),
309            civil::date(2107, 12, 31).at(23, 59, 58, 0)
310        );
311    }
312
313    #[test]
314    fn try_from_primitive_date_time_to_date_time_before_dos_date_time_epoch() {
315        assert_eq!(
316            DateTime::try_from(datetime!(1979-12-31 23:59:58)).unwrap_err(),
317            DateTimeRangeErrorKind::Negative.into()
318        );
319        assert_eq!(
320            DateTime::try_from(datetime!(1979-12-31 23:59:59)).unwrap_err(),
321            DateTimeRangeErrorKind::Negative.into()
322        );
323    }
324
325    #[test]
326    fn try_from_primitive_date_time_to_date_time() {
327        assert_eq!(
328            DateTime::try_from(datetime!(1980-01-01 00:00:00)).unwrap(),
329            DateTime::MIN
330        );
331        assert_eq!(
332            DateTime::try_from(datetime!(1980-01-01 00:00:01)).unwrap(),
333            DateTime::MIN
334        );
335        // <https://devblogs.microsoft.com/oldnewthing/20030905-02/?p=42653>.
336        assert_eq!(
337            DateTime::try_from(datetime!(2002-11-26 19:25:00)).unwrap(),
338            DateTime::new(
339                Date::new(0b0010_1101_0111_1010).unwrap(),
340                Time::new(0b1001_1011_0010_0000).unwrap()
341            )
342        );
343        // <https://github.com/zip-rs/zip/blob/v0.6.4/src/types.rs#L553-L569>.
344        assert_eq!(
345            DateTime::try_from(datetime!(2018-11-17 10:38:30)).unwrap(),
346            DateTime::new(
347                Date::new(0b0100_1101_0111_0001).unwrap(),
348                Time::new(0b0101_0100_1100_1111).unwrap()
349            )
350        );
351        assert_eq!(
352            DateTime::try_from(datetime!(2107-12-31 23:59:58)).unwrap(),
353            DateTime::MAX
354        );
355        assert_eq!(
356            DateTime::try_from(datetime!(2107-12-31 23:59:59)).unwrap(),
357            DateTime::MAX
358        );
359    }
360
361    #[test]
362    fn try_from_primitive_date_time_to_date_time_with_too_big_date_time() {
363        assert_eq!(
364            DateTime::try_from(datetime!(2108-01-01 00:00:00)).unwrap_err(),
365            DateTimeRangeErrorKind::Overflow.into()
366        );
367    }
368
369    #[cfg(feature = "chrono")]
370    #[test]
371    fn try_from_chrono_naive_date_time_to_date_time_before_dos_date_time_epoch() {
372        assert_eq!(
373            DateTime::try_from("1979-12-31T23:59:58".parse::<NaiveDateTime>().unwrap())
374                .unwrap_err(),
375            DateTimeRangeErrorKind::Negative.into()
376        );
377        assert_eq!(
378            DateTime::try_from("1979-12-31T23:59:59".parse::<NaiveDateTime>().unwrap())
379                .unwrap_err(),
380            DateTimeRangeErrorKind::Negative.into()
381        );
382    }
383
384    #[cfg(feature = "chrono")]
385    #[test]
386    fn try_from_chrono_naive_date_time_to_date_time() {
387        assert_eq!(
388            DateTime::try_from("1980-01-01T00:00:00".parse::<NaiveDateTime>().unwrap()).unwrap(),
389            DateTime::MIN
390        );
391        assert_eq!(
392            DateTime::try_from("1980-01-01T00:00:01".parse::<NaiveDateTime>().unwrap()).unwrap(),
393            DateTime::MIN
394        );
395        // <https://devblogs.microsoft.com/oldnewthing/20030905-02/?p=42653>.
396        assert_eq!(
397            DateTime::try_from("2002-11-26T19:25:00".parse::<NaiveDateTime>().unwrap()).unwrap(),
398            DateTime::new(
399                Date::new(0b0010_1101_0111_1010).unwrap(),
400                Time::new(0b1001_1011_0010_0000).unwrap()
401            )
402        );
403        // <https://github.com/zip-rs/zip/blob/v0.6.4/src/types.rs#L553-L569>.
404        assert_eq!(
405            DateTime::try_from("2018-11-17T10:38:30".parse::<NaiveDateTime>().unwrap()).unwrap(),
406            DateTime::new(
407                Date::new(0b0100_1101_0111_0001).unwrap(),
408                Time::new(0b0101_0100_1100_1111).unwrap()
409            )
410        );
411        assert_eq!(
412            DateTime::try_from("2107-12-31T23:59:58".parse::<NaiveDateTime>().unwrap()).unwrap(),
413            DateTime::MAX
414        );
415        assert_eq!(
416            DateTime::try_from("2107-12-31T23:59:59".parse::<NaiveDateTime>().unwrap()).unwrap(),
417            DateTime::MAX
418        );
419    }
420
421    #[cfg(feature = "chrono")]
422    #[test]
423    fn try_from_chrono_naive_date_time_to_date_time_with_too_big_date_time() {
424        assert_eq!(
425            DateTime::try_from("2108-01-01T00:00:00".parse::<NaiveDateTime>().unwrap())
426                .unwrap_err(),
427            DateTimeRangeErrorKind::Overflow.into()
428        );
429    }
430
431    #[cfg(feature = "jiff")]
432    #[test]
433    fn try_from_jiff_civil_date_time_to_date_time_before_dos_date_time_epoch() {
434        assert_eq!(
435            DateTime::try_from(civil::date(1979, 12, 31).at(23, 59, 58, 0)).unwrap_err(),
436            DateTimeRangeErrorKind::Negative.into()
437        );
438        assert_eq!(
439            DateTime::try_from(civil::date(1979, 12, 31).at(23, 59, 59, 0)).unwrap_err(),
440            DateTimeRangeErrorKind::Negative.into()
441        );
442    }
443
444    #[cfg(feature = "jiff")]
445    #[test]
446    fn try_from_jiff_civil_date_time_to_date_time() {
447        assert_eq!(
448            DateTime::try_from(civil::date(1980, 1, 1).at(0, 0, 0, 0)).unwrap(),
449            DateTime::MIN
450        );
451        assert_eq!(
452            DateTime::try_from(civil::date(1980, 1, 1).at(0, 0, 1, 0)).unwrap(),
453            DateTime::MIN
454        );
455        // <https://devblogs.microsoft.com/oldnewthing/20030905-02/?p=42653>.
456        assert_eq!(
457            DateTime::try_from(civil::date(2002, 11, 26).at(19, 25, 0, 0)).unwrap(),
458            DateTime::new(
459                Date::new(0b0010_1101_0111_1010).unwrap(),
460                Time::new(0b1001_1011_0010_0000).unwrap()
461            )
462        );
463        // <https://github.com/zip-rs/zip/blob/v0.6.4/src/types.rs#L553-L569>.
464        assert_eq!(
465            DateTime::try_from(civil::date(2018, 11, 17).at(10, 38, 30, 0)).unwrap(),
466            DateTime::new(
467                Date::new(0b0100_1101_0111_0001).unwrap(),
468                Time::new(0b0101_0100_1100_1111).unwrap()
469            )
470        );
471        assert_eq!(
472            DateTime::try_from(civil::date(2107, 12, 31).at(23, 59, 58, 0)).unwrap(),
473            DateTime::MAX
474        );
475        assert_eq!(
476            DateTime::try_from(civil::date(2107, 12, 31).at(23, 59, 59, 0)).unwrap(),
477            DateTime::MAX
478        );
479    }
480
481    #[cfg(feature = "jiff")]
482    #[test]
483    fn try_from_jiff_civil_date_time_to_date_time_with_too_big_date_time() {
484        assert_eq!(
485            DateTime::try_from(civil::date(2108, 1, 1).at(0, 0, 0, 0)).unwrap_err(),
486            DateTimeRangeErrorKind::Overflow.into()
487        );
488    }
489}