dsmr_parse/
tst.rs

1use core::str;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
4pub struct Tst {
5	/// Year normalized from 2 digits by mapping it to 1969..=2068 range
6	pub year: u16,
7	pub month: u8,
8	pub day: u8,
9	pub hour: u8,
10	pub minute: u8,
11	pub second: u8,
12	/// DST active flag
13	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}