use epics_base_rs::error::{CaError, CaResult};
use epics_base_rs::server::record::{FieldDesc, ProcessOutcome, Record};
use epics_base_rs::types::{DbFieldType, EpicsValue};
use chrono::Local;
const EPICS_EPOCH_OFFSET: i64 = 631152000;
const TIMESTAMP_FORMATS: &[&str] = &[
"%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", ];
pub struct TimestampRecord {
pub val: String,
pub oval: String,
pub rval: i32,
pub tst: i16,
}
impl Default for TimestampRecord {
fn default() -> Self {
Self {
val: String::new(),
oval: String::new(),
rval: 0,
tst: 0,
}
}
}
static FIELDS: &[FieldDesc] = &[
FieldDesc {
name: "VAL",
dbf_type: DbFieldType::String,
read_only: false,
},
FieldDesc {
name: "OVAL",
dbf_type: DbFieldType::String,
read_only: true,
},
FieldDesc {
name: "RVAL",
dbf_type: DbFieldType::Long,
read_only: false,
},
FieldDesc {
name: "TST",
dbf_type: DbFieldType::Short,
read_only: false,
},
];
impl TimestampRecord {
fn format_timestamp(&self) -> (String, i32) {
let now = Local::now();
let unix_secs = now.timestamp();
let sec_past_epoch = (unix_secs - EPICS_EPOCH_OFFSET) as i32;
if sec_past_epoch <= 0 {
return ("-NULL-".to_string(), 0);
}
let tst = self.tst.clamp(0, 10) as usize;
let formatted = if tst <= 8 {
now.format(TIMESTAMP_FORMATS[tst]).to_string()
} else {
let ms = now.timestamp_subsec_millis();
let base = if tst == 9 {
now.format("%b %d %Y %H:%M:%S").to_string()
} else {
now.format("%m/%d/%y %H:%M:%S").to_string()
};
format!("{}.{:03}", base, ms)
};
(formatted, sec_past_epoch)
}
}
impl Record for TimestampRecord {
fn record_type(&self) -> &'static str {
"timestamp"
}
fn process(&mut self) -> CaResult<ProcessOutcome> {
let (formatted, sec_past_epoch) = self.format_timestamp();
self.oval = std::mem::replace(&mut self.val, formatted);
self.rval = sec_past_epoch;
Ok(ProcessOutcome::complete())
}
fn get_field(&self, name: &str) -> Option<EpicsValue> {
match name {
"VAL" => Some(EpicsValue::String(self.val.clone())),
"OVAL" => Some(EpicsValue::String(self.oval.clone())),
"RVAL" => Some(EpicsValue::Long(self.rval)),
"TST" => Some(EpicsValue::Short(self.tst)),
_ => None,
}
}
fn put_field(&mut self, name: &str, value: EpicsValue) -> CaResult<()> {
match name {
"VAL" => match value {
EpicsValue::String(v) => {
self.val = v;
Ok(())
}
_ => Err(CaError::TypeMismatch(name.into())),
},
"RVAL" => match value {
EpicsValue::Long(v) => {
self.rval = v;
Ok(())
}
_ => Err(CaError::TypeMismatch(name.into())),
},
"TST" => match value {
EpicsValue::Short(v) => {
self.tst = v;
Ok(())
}
_ => Err(CaError::TypeMismatch(name.into())),
},
"OVAL" => Err(CaError::ReadOnlyField(name.into())),
_ => Err(CaError::FieldNotFound(name.into())),
}
}
fn field_list(&self) -> &'static [FieldDesc] {
FIELDS
}
fn clears_udf(&self) -> bool {
true
}
}