use bytesutil::ReadBytes;
use std::{fmt::Display, io::Read};
pub struct LocalTimeTypeRecord {
pub utoff: i32,
pub dst: bool,
pub idx: u8,
}
pub struct LeapSecondRecord {
pub occurrence: i64,
pub correction: i32,
}
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
InvalidSignature,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Io(e) => write!(f, "io error: {}", e),
Error::InvalidSignature => f.write_str("invalid TZIF signature"),
}
}
}
impl std::error::Error for Error {}
pub struct Data {
pub transition_times: Vec<i64>,
pub transition_types: Vec<u8>,
pub local_time_type_records: Vec<LocalTimeTypeRecord>,
pub leap_second_records: Vec<LeapSecondRecord>,
}
pub struct Header {
pub version: u8,
pub isutcnt: u32,
pub isstdcnt: u32,
pub leapcnt: u32,
pub timecnt: u32,
pub typecnt: u32,
pub charcnt: u32, }
pub struct Block {
pub header: Header,
pub data: Data,
}
pub struct TZIF {
pub block_v1: Block,
pub block_v2p: Option<Block>,
}
impl Header {
fn time_size(&self) -> usize {
match self.version {
0x00 => 4,
_ => 8,
}
}
fn read<R: Read>(mut reader: R) -> Result<Header, Error> {
let mut header: [u8; 44] = [0; 44];
reader.read_exact(&mut header).map_err(Error::Io)?;
if &header[0..4] != b"TZif" {
return Err(Error::InvalidSignature);
}
Ok(Header {
version: header[4],
isutcnt: u32::read_bytes_be(&header[20..24]),
isstdcnt: u32::read_bytes_be(&header[24..28]),
leapcnt: u32::read_bytes_be(&header[28..32]),
timecnt: u32::read_bytes_be(&header[32..36]),
typecnt: u32::read_bytes_be(&header[36..40]),
charcnt: u32::read_bytes_be(&header[40..44]),
})
}
}
impl Data {
fn read<R: Read>(mut reader: R, header: &Header) -> Result<Data, Error> {
let size = header.time_size();
let mut transition_times = vec![0; size * header.timecnt as usize];
reader
.read_exact(&mut transition_times)
.map_err(Error::Io)?;
let mut transition_types = vec![0; header.timecnt as usize];
reader
.read_exact(&mut transition_types)
.map_err(Error::Io)?;
let mut local_time_type_records = vec![0; 6 * header.typecnt as usize];
reader
.read_exact(&mut local_time_type_records)
.map_err(Error::Io)?;
let mut time_zone_designations = vec![0; header.charcnt as usize];
reader
.read_exact(&mut time_zone_designations)
.map_err(Error::Io)?;
let mut leap_second_records = vec![0; (size + 4) * header.leapcnt as usize];
reader
.read_exact(&mut leap_second_records)
.map_err(Error::Io)?;
let mut std_wall_indicators = vec![0; header.isstdcnt as usize];
reader
.read_exact(&mut std_wall_indicators)
.map_err(Error::Io)?;
let mut ut_indicators = vec![0; header.isutcnt as usize];
reader.read_exact(&mut ut_indicators).map_err(Error::Io)?;
let local_time_type_records = local_time_type_records
.as_slice()
.chunks(6)
.map(|v| LocalTimeTypeRecord {
utoff: i32::read_bytes_be(&v[0..4]),
dst: v[4] == 1,
idx: v[5],
})
.collect();
if header.version == 0x00 {
Ok(Data {
transition_types,
transition_times: transition_times
.as_slice()
.chunks(4)
.map(|v| i32::read_bytes_be(v) as i64)
.collect(),
leap_second_records: leap_second_records
.as_slice()
.chunks(8)
.map(|v| LeapSecondRecord {
occurrence: i32::read_bytes_be(&v[0..4]) as i64,
correction: i32::read_bytes_be(&v[4..8]),
})
.collect(),
local_time_type_records,
})
} else {
Ok(Data {
transition_types,
transition_times: transition_times
.as_slice()
.chunks(8)
.map(i64::read_bytes_be)
.collect(),
leap_second_records: leap_second_records
.as_slice()
.chunks(12)
.map(|v| LeapSecondRecord {
occurrence: i64::read_bytes_be(&v[0..8]),
correction: i32::read_bytes_be(&v[8..12]),
})
.collect(),
local_time_type_records,
})
}
}
}
impl TZIF {
pub fn read<R: Read>(mut reader: R) -> Result<TZIF, Error> {
let mut header_v1 = Header::read(&mut reader)?;
header_v1.version = 0x00; let block_v1 = Block {
data: Data::read(&mut reader, &header_v1)?,
header: header_v1,
};
let block_v2p = match Header::read(&mut reader) {
Ok(header_v2) => Some(Block {
data: Data::read(&mut reader, &header_v2)?,
header: header_v2,
}),
_ => None,
};
Ok(TZIF {
block_v1,
block_v2p,
})
}
}