1use std::time::{Duration, SystemTime};
2
3use crate::app::measurement::DoubleBit;
4use crate::app::variations::Variation;
5use crate::app::QualifierCode;
6
7use chrono::{DateTime, SecondsFormat, TimeZone, Utc};
8use scursor::{WriteCursor, WriteError};
9
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12pub struct Timestamp {
13 value: u64,
14}
15
16impl Timestamp {
17 pub const MAX_VALUE: u64 = 0x0000_FFFF_FFFF_FFFF;
19 pub(crate) const OUT_OF_RANGE: &'static str = "<out of range>";
20
21 pub const fn new(value: u64) -> Self {
23 Self {
24 value: value & Self::MAX_VALUE,
25 }
26 }
27
28 pub const fn min() -> Self {
30 Self::zero()
31 }
32
33 pub const fn zero() -> Self {
35 Self::new(0)
36 }
37
38 pub fn max() -> Self {
40 Self::new(Self::MAX_VALUE)
41 }
42
43 pub fn try_from_system_time(system_time: SystemTime) -> Option<Timestamp> {
45 Some(Timestamp::new(
46 u64::try_from(
47 system_time
48 .duration_since(SystemTime::UNIX_EPOCH)
49 .ok()?
50 .as_millis(),
51 )
52 .ok()?,
53 ))
54 }
55
56 pub fn to_datetime_utc(self) -> Option<DateTime<Utc>> {
58 Utc.timestamp_millis_opt(self.value as i64).single()
59 }
60
61 pub fn raw_value(&self) -> u64 {
63 self.value
64 }
65
66 pub(crate) fn write(self, cursor: &mut WriteCursor) -> Result<(), WriteError> {
67 cursor.write_u48_le(self.value)
68 }
69
70 pub(crate) fn read(cursor: &mut scursor::ReadCursor) -> Result<Self, scursor::ReadError> {
71 Ok(Self {
72 value: cursor.read_u48_le()?,
73 })
74 }
75
76 pub(crate) fn checked_add(self, x: Duration) -> Option<Timestamp> {
77 let max_add = Self::MAX_VALUE - self.value;
79 let millis = x.as_millis();
80 if millis > max_add as u128 {
81 return None;
82 }
83 Some(Timestamp::new(self.value + millis as u64))
84 }
85}
86
87impl std::fmt::Display for Timestamp {
88 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
89 match self.to_datetime_utc() {
90 Some(x) => write!(f, "{}", x.to_rfc3339_opts(SecondsFormat::Millis, true)),
91 None => f.write_str(Timestamp::OUT_OF_RANGE),
92 }
93 }
94}
95
96pub(crate) struct BitPair {
97 pub(crate) high: bool,
98 pub(crate) low: bool,
99}
100
101impl BitPair {
102 pub(crate) fn new(high: bool, low: bool) -> Self {
103 Self { high, low }
104 }
105}
106
107impl DoubleBit {
108 pub(crate) fn from(high: bool, low: bool) -> Self {
109 match (high, low) {
110 (false, false) => DoubleBit::Intermediate,
111 (false, true) => DoubleBit::DeterminedOff,
112 (true, false) => DoubleBit::DeterminedOn,
113 (true, true) => DoubleBit::Indeterminate,
114 }
115 }
116
117 pub(crate) fn to_bit_pair(self) -> BitPair {
118 match self {
119 DoubleBit::Intermediate => BitPair::new(false, false),
120 DoubleBit::DeterminedOff => BitPair::new(false, true),
121 DoubleBit::DeterminedOn => BitPair::new(true, false),
122 DoubleBit::Indeterminate => BitPair::new(true, true),
123 }
124 }
125
126 pub(crate) fn to_byte(self) -> u8 {
127 match self {
128 DoubleBit::Intermediate => 0b00,
129 DoubleBit::DeterminedOff => 0b01,
130 DoubleBit::DeterminedOn => 0b10,
131 DoubleBit::Indeterminate => 0b11,
132 }
133 }
134}
135
136impl std::fmt::Display for DoubleBit {
137 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
138 write!(f, "{self:?}")
139 }
140}
141
142impl std::fmt::Display for Variation {
143 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
144 let (g, v) = self.to_group_and_var();
145 write!(f, "g{g}v{v}")
146 }
147}
148
149impl QualifierCode {
150 pub(crate) fn description(self) -> &'static str {
151 match self {
152 QualifierCode::AllObjects => "all objects",
153 QualifierCode::Range8 => "1-byte start/stop",
154 QualifierCode::Range16 => "2-byte start/stop",
155 QualifierCode::Count8 => "1-byte count of objects",
156 QualifierCode::Count16 => "2-byte count of objects",
157 QualifierCode::CountAndPrefix8 => "1-byte count of objects",
158 QualifierCode::CountAndPrefix16 => "2-byte count of objects",
159 QualifierCode::FreeFormat16 => "2-byte free format",
160 }
161 }
162}
163
164#[cfg(test)]
165mod test {
166 use super::*;
167
168 #[test]
169 fn conversion_from_timestamp_to_datetime_utc_cannot_overflow() {
170 let timestamp = Timestamp::new(std::u64::MAX);
171 timestamp.to_datetime_utc();
172 }
173
174 #[test]
175 fn timestamp_display_formatting_works_as_expected() {
176 assert_eq!(format!("{}", Timestamp::min()), "1970-01-01T00:00:00.000Z");
177 assert_eq!(
178 format!("{}", Timestamp::max()),
179 "+10889-08-02T05:31:50.655Z"
180 );
181 }
182}