use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::array::TryFromSliceError;
use core::str::from_utf8;
use std::fs;
use std::path::Path;
use crate::error::{ErrorKind, ResultExt};
use crate::time_zone::location::{Location, Transition, Zone};
pub fn read<P: AsRef<Path>>(path: P) -> crate::Result<Location> {
let input = fs::read(path).context_tzif()?;
parse(&mut input.as_slice())
}
fn parse(input: &mut &[u8]) -> crate::Result<Location> {
if input.len() < 44 {
return Err(ErrorKind::TimeZoneFile.into());
}
let (magic, remaining) = input.split_at(4);
*input = remaining;
if &magic != b"TZif" {
return Err(ErrorKind::TimeZoneFile.into());
}
let (p, remaining) = input.split_at(16);
*input = remaining;
let version = match p[0] {
0 | b'1' => 1,
b'2' => 2,
b'3' => 3,
b'4' => 4,
_ => {
return Err(ErrorKind::TimeZoneFile.into());
}
};
let mut time_size = 4;
loop {
let isutcnt = get_usize(input, 4).context_tzif()?;
let isstdcnt = get_usize(input, 4).context_tzif()?;
let leapcnt = get_usize(input, 4).context_tzif()?;
let timecnt = get_usize(input, 4).context_tzif()?;
let typecnt = get_usize(input, 4).context_tzif()?;
let charcnt = get_usize(input, 4).context_tzif()?;
if version >= 2 && time_size == 4 {
*input = &input[timecnt * 4..]; *input = &input[timecnt * 1..]; *input = &input[typecnt * 6..]; *input = &input[charcnt * 1..]; *input = &input[leapcnt * 4..]; *input = &input[isstdcnt * 1..]; *input = &input[isutcnt * 1..]; *input = &input[4 + 1 + 15..];
time_size = 8;
continue;
}
let transitions = parse_transitions(timecnt, input, time_size)?;
let zones = parse_zones(typecnt, charcnt, input)?;
return Ok(Location {
zones: zones.into_boxed_slice(),
transitions: transitions.into_boxed_slice(),
});
}
}
fn parse_transitions(
n: usize,
input: &mut &[u8],
time_size: usize,
) -> crate::Result<Vec<Transition>> {
let mut transitions = Vec::with_capacity(n);
for _ in 0..n {
let when = get_int(input, time_size).context_tzif()?;
transitions.push(Transition {
when,
zone: 0,
});
}
for i in 0..n {
transitions[i].zone = get_u8(input).context_tzif()?;
}
Ok(transitions)
}
fn parse_zones(n_zones: usize, n_name: usize, input: &mut &[u8]) -> crate::Result<Vec<Zone>> {
let names = from_utf8(&input[n_zones * 6..][..n_name]).context_tzif()?;
let mut zones = Vec::with_capacity(n_zones);
for _ in 0..n_zones {
let offset = get_i32(input).context_tzif()?;
let dst = get_u8(input).context_tzif()?;
let name_offset: usize = get_u8(input).context_tzif()?.into();
if name_offset >= names.len() {
return Err(ErrorKind::TimeZoneFile.into());
}
let Some(name_len) = names[name_offset..].find('\0') else {
return Err(ErrorKind::TimeZoneFile.into());
};
let name = &names[name_offset..name_offset + name_len];
zones.push(Zone {
offset,
dst: dst == 1,
name: Arc::from(name.to_string()),
})
}
Ok(zones)
}
fn get_u8(input: &mut &[u8]) -> Result<u8, TryFromSliceError> {
let (value, remaining) = input.split_at(1);
*input = remaining;
Ok(u8::from_be_bytes(value.try_into()?))
}
fn get_usize(input: &mut &[u8], size: usize) -> Result<usize, TryFromSliceError> {
let (value, remaining) = input.split_at(size);
*input = remaining;
let value = if size == 4 {
u32::from_be_bytes(value.try_into()?).into()
} else {
u64::from_be_bytes(value.try_into()?)
};
Ok(value as usize)
}
fn get_i32(input: &mut &[u8]) -> Result<i32, TryFromSliceError> {
let (value, remaining) = input.split_at(4);
*input = remaining;
Ok(i32::from_be_bytes(value.try_into()?))
}
fn get_i64(input: &mut &[u8]) -> Result<i64, TryFromSliceError> {
let (value, remaining) = input.split_at(8);
*input = remaining;
Ok(i64::from_be_bytes(value.try_into()?))
}
fn get_int(input: &mut &[u8], size: usize) -> Result<i64, TryFromSliceError> {
if size == 4 {
get_i32(input).map(Into::into)
} else {
get_i64(input)
}
}