1use core::str;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
4pub struct Tst {
5 pub year: u16,
7 pub month: u8,
8 pub day: u8,
9 pub hour: u8,
10 pub minute: u8,
11 pub second: u8,
12 pub dst: bool,
14}
15
16impl Tst {
17 pub fn try_from_bytes(bytes: &[u8]) -> Option<Self> {
18 let mut parts = bytes.chunks(2);
19 let year = normalize_two_digit_year(str::from_utf8(parts.next()?).ok()?.parse().ok()?);
20 let month = str::from_utf8(parts.next()?).ok()?.parse().ok()?;
21 let day = str::from_utf8(parts.next()?).ok()?.parse().ok()?;
22 let hour = str::from_utf8(parts.next()?).ok()?.parse().ok()?;
23 let minute = str::from_utf8(parts.next()?).ok()?.parse().ok()?;
24 let second = str::from_utf8(parts.next()?).ok()?.parse().ok()?;
25 let dst = parts.next()? == b"S";
26 Some(Self {
27 year,
28 month,
29 day,
30 hour,
31 minute,
32 second,
33 dst,
34 })
35 }
36
37 #[cfg(feature = "jiff")]
38 pub fn to_jiff(&self, timezone: &jiff::tz::TimeZone) -> Option<jiff::Zoned> {
39 jiff::civil::DateTime::new(
40 i16::try_from(self.year).unwrap_or(i16::MAX),
41 i8::try_from(self.month).unwrap_or(i8::MAX),
42 i8::try_from(self.day).unwrap_or(i8::MAX),
43 i8::try_from(self.hour).unwrap_or(i8::MAX),
44 i8::try_from(self.minute).unwrap_or(i8::MAX),
45 i8::try_from(self.second).unwrap_or(i8::MAX),
46 0,
47 )
48 .map(|d| timezone.to_ambiguous_zoned(d))
49 .ok()
50 .and_then(|d| {
51 let strategy = if self.dst {
52 jiff::tz::Disambiguation::Earlier
53 } else {
54 jiff::tz::Disambiguation::Later
55 };
56 d.disambiguate(strategy).ok()
57 })
58 }
59
60 #[cfg(feature = "chrono")]
61 pub fn to_chrono<Tz: chrono::TimeZone>(&self, timezone: &Tz) -> Option<chrono::DateTime<Tz>> {
62 let d = timezone.from_local_datetime(&chrono::NaiveDateTime::new(
63 chrono::NaiveDate::from_ymd_opt(i32::from(self.year), u32::from(self.month), u32::from(self.day))?,
64 chrono::NaiveTime::from_hms_opt(u32::from(self.hour), u32::from(self.minute), u32::from(self.second))?,
65 ));
66 match d {
67 chrono::LocalResult::None => None,
68 chrono::LocalResult::Single(d) => Some(d),
69 chrono::LocalResult::Ambiguous(min, max) => Some(if self.dst {
70 min
71 } else {
72 max
73 }),
74 }
75 }
76}
77
78fn normalize_two_digit_year(year: u16) -> u16 {
79 if (69..=99).contains(&year) {
80 year + 1900
81 } else if (0..=68).contains(&year) {
82 year + 2000
83 } else {
84 year
85 }
86}