1#[cfg(feature = "chrono")]
9use chrono::{DateTime, Utc};
10
11pub const FIT_EPOCH_OFFSET_SECS: i64 = 631_065_600;
13
14#[cfg(feature = "chrono")]
19pub fn fit_to_datetime(fit_seconds: u32) -> Option<DateTime<Utc>> {
20 let unix = i64::from(fit_seconds).checked_add(FIT_EPOCH_OFFSET_SECS)?;
21 DateTime::from_timestamp(unix, 0)
22}
23
24#[cfg(feature = "chrono")]
27pub fn datetime_to_fit(dt: DateTime<Utc>) -> Option<u32> {
28 let secs = dt.timestamp().checked_sub(FIT_EPOCH_OFFSET_SECS)?;
29 if (0..=i64::from(u32::MAX)).contains(&secs) {
30 Some(secs as u32)
31 } else {
32 None
33 }
34}
35
36#[cfg(all(test, feature = "chrono"))]
37mod tests {
38 use super::*;
39 use chrono::TimeZone;
40
41 #[test]
42 fn fit_epoch_is_1989_12_31_utc() {
43 let dt = fit_to_datetime(0).unwrap();
44 assert_eq!(dt, Utc.with_ymd_and_hms(1989, 12, 31, 0, 0, 0).unwrap());
45 }
46
47 #[test]
48 fn known_value_round_trips() {
49 let dt = fit_to_datetime(995_749_880).unwrap();
50 assert_eq!(dt.timestamp(), 995_749_880 + FIT_EPOCH_OFFSET_SECS);
51 let back = datetime_to_fit(dt).unwrap();
52 assert_eq!(back, 995_749_880);
53 }
54
55 #[test]
56 fn datetime_before_fit_epoch_is_none() {
57 let dt = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
58 assert_eq!(datetime_to_fit(dt), None);
59 }
60
61 #[test]
62 fn unix_offset_is_correct() {
63 let unix_dt = DateTime::from_timestamp(FIT_EPOCH_OFFSET_SECS, 0).unwrap();
64 assert_eq!(
65 unix_dt,
66 Utc.with_ymd_and_hms(1989, 12, 31, 0, 0, 0).unwrap()
67 );
68 }
69}