1use chrono::{TimeZone, Utc};
2use derive_more::{From, Into};
3use std::{
4 cmp::Ordering,
5 ffi::{c_char, CStr},
6 fmt::{self, Debug, Formatter},
7 ops::Deref,
8 ptr::copy_nonoverlapping,
9 time::{Duration, SystemTime},
10};
11
12#[repr(transparent)]
13#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord, From, Into)]
14pub struct EpicsEnum(pub u16);
15
16#[repr(transparent)]
17#[derive(Clone, Copy)]
18pub struct EpicsTimeStamp(pub sys::epicsTimeStamp);
19
20impl EpicsTimeStamp {
21 pub fn sec(&self) -> u32 {
22 self.0.secPastEpoch
23 }
24 pub fn nsec(&self) -> u32 {
25 self.0.nsec
26 }
27
28 pub fn to_system(self) -> SystemTime {
29 let unix_epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
30 let epics_epoch = Utc.with_ymd_and_hms(1990, 1, 1, 0, 0, 0).unwrap();
31 let diff = (epics_epoch - unix_epoch).to_std().unwrap();
32 SystemTime::UNIX_EPOCH
33 + diff
34 + (Duration::from_secs(self.0.secPastEpoch as u64)
35 + Duration::from_nanos(self.0.nsec as u64))
36 }
37}
38
39impl PartialEq for EpicsTimeStamp {
40 fn eq(&self, other: &Self) -> bool {
41 self.0.secPastEpoch == other.0.secPastEpoch && self.0.nsec == other.0.nsec
42 }
43}
44
45impl Eq for EpicsTimeStamp {}
46
47impl PartialOrd for EpicsTimeStamp {
48 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
49 Some(self.cmp(other))
50 }
51}
52
53impl Ord for EpicsTimeStamp {
54 fn cmp(&self, other: &Self) -> Ordering {
55 let o = self.0.secPastEpoch.cmp(&other.0.secPastEpoch);
56 if matches!(o, Ordering::Equal) {
57 self.0.nsec.cmp(&other.0.nsec)
58 } else {
59 o
60 }
61 }
62}
63
64impl Debug for EpicsTimeStamp {
65 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
66 write!(
67 f,
68 "EpicsTimeStamp {{ sec: {}, nsec: {} }}",
69 self.sec(),
70 self.nsec()
71 )
72 }
73}
74
75#[derive(Clone, Copy)]
76#[repr(transparent)]
77pub struct StaticCString<const N: usize> {
78 data: [c_char; N],
79}
80
81impl<const N: usize> Default for StaticCString<N> {
82 fn default() -> Self {
83 Self { data: [0; N] }
84 }
85}
86
87impl<const N: usize> PartialEq for StaticCString<N> {
88 fn eq(&self, other: &Self) -> bool {
89 self.deref().eq(other.deref())
90 }
91}
92
93impl<const N: usize> Eq for StaticCString<N> {}
94
95impl<const N: usize> PartialOrd for StaticCString<N> {
96 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
97 self.deref().partial_cmp(other.deref())
98 }
99}
100
101impl<const N: usize> Ord for StaticCString<N> {
102 fn cmp(&self, other: &Self) -> Ordering {
103 self.deref().cmp(other.deref())
104 }
105}
106
107impl<const N: usize> StaticCString<N> {
108 pub const MAX_LEN: usize = N - 1;
109
110 pub fn len(&self) -> Option<usize> {
111 self.data
112 .iter()
113 .copied()
114 .enumerate()
115 .find(|(_, c)| *c == 0)
116 .map(|(i, _)| i)
117 }
118 pub fn is_empty(&self) -> bool {
119 self.data[0] == 0
120 }
121 pub fn from_array(data: [c_char; N]) -> Option<Self> {
122 if data.iter().copied().any(|c| c == 0) {
123 Some(Self { data })
124 } else {
125 None
126 }
127 }
128 pub fn from_cstr(cstr: &CStr) -> Option<Self> {
129 let bytes = cstr.to_bytes();
130 if bytes.len() < N {
131 let mut this = Self::default();
132 unsafe {
133 copy_nonoverlapping(
134 bytes.as_ptr() as *const c_char,
135 this.data.as_mut_ptr(),
136 bytes.len() + 1,
137 )
138 };
139 Some(this)
140 } else {
141 None
142 }
143 }
144}
145
146impl<const N: usize> Deref for StaticCString<N> {
147 type Target = CStr;
148 fn deref(&self) -> &CStr {
149 debug_assert!(
150 self.data.iter().copied().any(|c| c == 0),
151 "String is not nul-terminated"
152 );
153 unsafe { CStr::from_ptr(self.data.as_ptr()) }
154 }
155}
156
157impl<const N: usize> Debug for StaticCString<N> {
158 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159 write!(f, "{:?}", self.deref())
160 }
161}
162
163pub type EpicsString = StaticCString<{ sys::MAX_STRING_SIZE as usize }>;