std_rs/records/
timestamp.rs1use epics_base_rs::error::{CaError, CaResult};
2use epics_base_rs::server::record::{FieldDesc, ProcessOutcome, Record};
3use epics_base_rs::types::{DbFieldType, EpicsValue};
4
5use chrono::Local;
6
7const EPICS_EPOCH_OFFSET: i64 = 631152000;
9
10const TIMESTAMP_FORMATS: &[&str] = &[
12 "%y/%m/%d %H:%M:%S", "%m/%d/%y %H:%M:%S", "%b %d %H:%M:%S %y", "%b %d %H:%M:%S", "%H:%M:%S", "%H:%M", "%d/%m/%y %H:%M:%S", "%d %b %H:%M:%S %y", "%d-%b-%Y %H:%M:%S", ];
22
23pub struct TimestampRecord {
27 pub val: String,
29 pub oval: String,
31 pub rval: i32,
33 pub tst: i16,
35}
36
37impl Default for TimestampRecord {
38 fn default() -> Self {
39 Self {
40 val: String::new(),
41 oval: String::new(),
42 rval: 0,
43 tst: 0,
44 }
45 }
46}
47
48static FIELDS: &[FieldDesc] = &[
49 FieldDesc {
50 name: "VAL",
51 dbf_type: DbFieldType::String,
52 read_only: false,
53 },
54 FieldDesc {
55 name: "OVAL",
56 dbf_type: DbFieldType::String,
57 read_only: true,
58 },
59 FieldDesc {
60 name: "RVAL",
61 dbf_type: DbFieldType::Long,
62 read_only: false,
63 },
64 FieldDesc {
65 name: "TST",
66 dbf_type: DbFieldType::Short,
67 read_only: false,
68 },
69];
70
71impl TimestampRecord {
72 fn format_timestamp(&self) -> (String, i32) {
73 let now = Local::now();
74 let unix_secs = now.timestamp();
75 let sec_past_epoch = (unix_secs - EPICS_EPOCH_OFFSET) as i32;
76
77 if sec_past_epoch <= 0 {
78 return ("-NULL-".to_string(), 0);
79 }
80
81 let tst = self.tst.clamp(0, 10) as usize;
82
83 let formatted = if tst <= 8 {
84 now.format(TIMESTAMP_FORMATS[tst]).to_string()
85 } else {
86 let ms = now.timestamp_subsec_millis();
88 let base = if tst == 9 {
89 now.format("%b %d %Y %H:%M:%S").to_string()
90 } else {
91 now.format("%m/%d/%y %H:%M:%S").to_string()
93 };
94 format!("{}.{:03}", base, ms)
95 };
96
97 (formatted, sec_past_epoch)
98 }
99}
100
101impl Record for TimestampRecord {
102 fn record_type(&self) -> &'static str {
103 "timestamp"
104 }
105
106 fn process(&mut self) -> CaResult<ProcessOutcome> {
107 let (formatted, sec_past_epoch) = self.format_timestamp();
108 self.oval = std::mem::replace(&mut self.val, formatted);
109 self.rval = sec_past_epoch;
110 Ok(ProcessOutcome::complete())
111 }
112
113 fn get_field(&self, name: &str) -> Option<EpicsValue> {
114 match name {
115 "VAL" => Some(EpicsValue::String(self.val.clone())),
116 "OVAL" => Some(EpicsValue::String(self.oval.clone())),
117 "RVAL" => Some(EpicsValue::Long(self.rval)),
118 "TST" => Some(EpicsValue::Short(self.tst)),
119 _ => None,
120 }
121 }
122
123 fn put_field(&mut self, name: &str, value: EpicsValue) -> CaResult<()> {
124 match name {
125 "VAL" => match value {
126 EpicsValue::String(v) => {
127 self.val = v;
128 Ok(())
129 }
130 _ => Err(CaError::TypeMismatch(name.into())),
131 },
132 "RVAL" => match value {
133 EpicsValue::Long(v) => {
134 self.rval = v;
135 Ok(())
136 }
137 _ => Err(CaError::TypeMismatch(name.into())),
138 },
139 "TST" => match value {
140 EpicsValue::Short(v) => {
141 self.tst = v;
142 Ok(())
143 }
144 _ => Err(CaError::TypeMismatch(name.into())),
145 },
146 "OVAL" => Err(CaError::ReadOnlyField(name.into())),
147 _ => Err(CaError::FieldNotFound(name.into())),
148 }
149 }
150
151 fn field_list(&self) -> &'static [FieldDesc] {
152 FIELDS
153 }
154
155 fn clears_udf(&self) -> bool {
156 true
157 }
158}