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