use super::{FixValue, Tz};
use crate::Buffer;
const ERR_INVALID: &str = "Invalid time.";
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TzTime {
hour: u32,
minute: u32,
second: u32,
second_is_explicit: bool,
tz: Tz,
}
impl TzTime {
pub fn hour(&self) -> u32 {
self.hour
}
pub fn minute(&self) -> u32 {
self.minute
}
pub fn second(&self) -> u32 {
self.second
}
pub fn timezone(&self) -> Tz {
self.tz
}
}
impl<'a> FixValue<'a> for TzTime {
type Error = &'static str;
type SerializeSettings = ();
fn serialize_with<B>(&self, buffer: &mut B, _settings: ()) -> usize
where
B: Buffer,
{
let bytes = &[
self.hour() as u8 / 10,
self.hour() as u8 % 10,
b':',
self.minute() as u8 / 10,
self.minute() as u8 % 10,
b':',
self.second() as u8 / 10,
self.second() as u8 % 10,
];
buffer.extend_from_slice(bytes);
bytes.len() + self.timezone().serialize(buffer)
}
fn deserialize(data: &'a [u8]) -> Result<Self, Self::Error> {
if data.len() < 6 || data[2] != b':' {
return Err(ERR_INVALID);
}
let hour = ascii_digit_to_u32(data[0], 10) + ascii_digit_to_u32(data[1], 1);
let minute = ascii_digit_to_u32(data[3], 10) + ascii_digit_to_u32(data[4], 1);
match data[5] {
b':' => {
let second = ascii_digit_to_u32(data[6], 10) + ascii_digit_to_u32(data[7], 1);
Ok(TzTime {
hour,
minute,
second,
second_is_explicit: true,
tz: Tz::deserialize(&data[8..]).map_err(|_| ERR_INVALID)?,
})
}
_ => Ok(TzTime {
hour,
minute,
second: 0,
second_is_explicit: false,
tz: Tz::deserialize(&data[5..]).map_err(|_| ERR_INVALID)?,
}),
}
}
}
const fn ascii_digit_to_u32(digit: u8, multiplier: u32) -> u32 {
(digit as u32).wrapping_sub(b'0' as u32) * multiplier
}
#[cfg(test)]
mod test {
use super::FixValue;
use super::*;
struct TestCase {
bytes: &'static [u8],
hour: u32,
minute: u32,
second: u32,
}
impl TestCase {
const fn new(s: &'static str, hour: u32, minute: u32, second: u32) -> Self {
Self {
bytes: s.as_bytes(),
hour,
minute,
second,
}
}
}
const TEST_CASES: &[TestCase] = &[
TestCase::new("07:39Z", 7, 39, 0),
TestCase::new("02:39-05", 2, 39, 0),
TestCase::new("15:39+08", 15, 39, 0),
TestCase::new("13:09+05:30", 13, 9, 0),
];
#[test]
fn valid_test_cases() {
for test_case in TEST_CASES.iter() {
let tz_time = TzTime::deserialize(test_case.bytes).unwrap();
assert_eq!(tz_time.hour(), test_case.hour);
assert_eq!(tz_time.minute(), test_case.minute);
assert_eq!(tz_time.second(), test_case.second);
}
}
}