use std::fs;
use crate::layout::TimeZone;
pub(crate) struct Tzif {
changes: Vec<(i64, usize)>,
types: Vec<(i32, bool)>,
rule: Option<Rule>,
}
#[derive(Clone, Copy)]
enum Day {
Julian(i64),
Year(i64),
Month(i64, i64, i64),
}
type Change = (Day, i64);
struct Rule {
std: i32,
dst: Option<(i32, Change, Change)>,
}
pub(crate) fn zone(name: &str) -> Tzif {
let path = format!("/usr/share/zoneinfo/{name}");
let data = fs::read(&path).unwrap_or_else(|e| panic!("{path}: {e}"));
read(&data).unwrap_or_else(|| panic!("{path}: not a TZif file this reads"))
}
fn be32(b: &[u8], at: usize) -> Option<i64> {
Some(i64::from(i32::from_be_bytes(
b.get(at..at + 4)?.try_into().ok()?,
)))
}
fn be64(b: &[u8], at: usize) -> Option<i64> {
Some(i64::from_be_bytes(b.get(at..at + 8)?.try_into().ok()?))
}
fn counts(b: &[u8], at: usize) -> Option<[usize; 6]> {
if b.get(at..at + 4)? != b"TZif" {
return None;
}
let mut c = [0; 6];
for (k, n) in c.iter_mut().enumerate() {
*n = be32(b, at + 20 + 4 * k)? as usize;
}
Some(c)
}
fn block(c: [usize; 6], time: usize) -> usize {
let [isut, isstd, leap, times, types, chars] = c;
times * time + times + types * 6 + chars + leap * (time + 4) + isstd + isut
}
fn read(b: &[u8]) -> Option<Tzif> {
let first = counts(b, 0)?;
if b[4] < b'2' {
return None;
}
let at = 44 + block(first, 4);
let c = counts(b, at)?;
let [_, _, _, times, types, _] = c;
let data = at + 44;
let mut changes = Vec::with_capacity(times);
for k in 0..times {
let when = be64(b, data + 8 * k)?;
let ty = *b.get(data + 8 * times + k)? as usize;
changes.push((when, ty));
}
let info = data + 9 * times;
let mut zone_types = Vec::with_capacity(types);
for k in 0..types {
let off = be32(b, info + 6 * k)? as i32;
let dst = *b.get(info + 6 * k + 4)? != 0;
zone_types.push((off, dst));
}
let footer = &b[at + 44 + block(c, 8)..];
let rule = std::str::from_utf8(footer)
.ok()
.map(|s| s.trim_matches('\n'))
.filter(|s| !s.is_empty())
.and_then(parse_rule);
Some(Tzif {
changes,
types: zone_types,
rule,
})
}
fn parse_rule(s: &str) -> Option<Rule> {
let mut p = s.as_bytes();
name(&mut p)?;
let std = -offset(&mut p)?;
if p.is_empty() {
return Some(Rule { std, dst: None });
}
name(&mut p)?;
let dst = if p.first().is_some_and(|&c| c != b',') {
-offset(&mut p)?
} else {
std + 3600
};
let (start, end) = if p.is_empty() {
((Day::Month(3, 2, 0), 7200), (Day::Month(11, 1, 0), 7200))
} else {
p = p.strip_prefix(b",")?;
let start = change(&mut p)?;
p = p.strip_prefix(b",")?;
let end = change(&mut p)?;
(start, end)
};
Some(Rule {
std,
dst: Some((dst, start, end)),
})
}
fn name(p: &mut &[u8]) -> Option<()> {
if let Some(rest) = p.strip_prefix(b"<") {
let end = rest.iter().position(|&c| c == b'>')?;
*p = &rest[end + 1..];
} else {
let end = p
.iter()
.position(|c| !c.is_ascii_alphabetic())
.unwrap_or(p.len());
if end < 3 {
return None;
}
*p = &p[end..];
}
Some(())
}
fn number(p: &mut &[u8]) -> Option<i64> {
let end = p
.iter()
.position(|c| !c.is_ascii_digit())
.unwrap_or(p.len());
if end == 0 {
return None;
}
let n = std::str::from_utf8(&p[..end]).ok()?.parse().ok()?;
*p = &p[end..];
Some(n)
}
fn time(p: &mut &[u8]) -> Option<i64> {
let sign = match p.first() {
Some(b'-') => -1,
Some(b'+') => 1,
_ => 0,
};
if sign != 0 {
*p = &p[1..];
}
let mut t = number(p)? * 3600;
for unit in [60, 1] {
let Some(rest) = p.strip_prefix(b":") else {
break;
};
*p = rest;
t += number(p)? * unit;
}
Some(if sign < 0 { -t } else { t })
}
fn offset(p: &mut &[u8]) -> Option<i32> {
time(p).map(|t| t as i32)
}
fn change(p: &mut &[u8]) -> Option<(Day, i64)> {
let day = match p.first()? {
b'J' => {
*p = &p[1..];
Day::Julian(number(p)?)
}
b'M' => {
*p = &p[1..];
let m = number(p)?;
*p = p.strip_prefix(b".")?;
let w = number(p)?;
*p = p.strip_prefix(b".")?;
Day::Month(m, w, number(p)?)
}
_ => Day::Year(number(p)?),
};
let at = match p.strip_prefix(b"/") {
Some(rest) => {
*p = rest;
time(p)?
}
None => 7200,
};
Some((day, at))
}
fn is_leap(y: i64) -> bool {
y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
}
const DAYS_BEFORE: [i64; 13] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
fn days_to_year(y: i64) -> i64 {
let y1 = y - 1;
365 * (y - 1970) + (y1.div_euclid(4) - 1969 / 4) - (y1.div_euclid(100) - 1969 / 100)
+ (y1.div_euclid(400) - 1969 / 400)
}
fn year_of(days: i64) -> (i64, i64) {
let mut y = 1970 + days.div_euclid(366);
while days_to_year(y + 1) <= days {
y += 1;
}
while days_to_year(y) > days {
y -= 1;
}
(y, days - days_to_year(y))
}
fn rule_time(year: i64, (day, at): (Day, i64), off: i32) -> i64 {
let s = match day {
Day::Julian(d) => (d - 1) * 86400 + if is_leap(year) && d >= 60 { 86400 } else { 0 },
Day::Year(d) => d * 86400,
Day::Month(mon, week, wday) => {
let m1 = (mon + 9) % 12 + 1;
let yy0 = if mon <= 2 { year - 1 } else { year };
let (yy1, yy2) = (yy0 / 100, yy0 % 100);
let mut dow = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
if dow < 0 {
dow += 7;
}
let mut d = wday - dow;
if d < 0 {
d += 7;
}
let days_in = DAYS_BEFORE[mon as usize] - DAYS_BEFORE[mon as usize - 1]
+ i64::from(mon == 2 && is_leap(year));
for _ in 1..week {
if d + 7 >= days_in {
break;
}
d += 7;
}
d += DAYS_BEFORE[mon as usize - 1];
if is_leap(year) && mon > 2 {
d += 1;
}
d * 86400
}
};
s + at - i64::from(off)
}
impl Rule {
fn offset(&self, at: i64) -> i32 {
let Some((dst, start, end)) = self.dst else {
return self.std;
};
let (year, yday) = year_of(at.div_euclid(86400));
let ysec = yday * 86400 + at.rem_euclid(86400);
let start = rule_time(year, start, self.std);
let end = rule_time(year, end, dst);
if end < start {
if ysec < end || ysec >= start {
dst
} else {
self.std
}
} else if ysec < start || ysec >= end {
self.std
} else {
dst
}
}
}
impl Tzif {
fn first(&self) -> usize {
let used = self.changes.iter().any(|&(_, t)| t == 0);
if !used {
return 0;
}
if let Some(&(_, t)) = self.changes.first()
&& self.types[t].1
{
for k in (0..t).rev() {
if !self.types[k].1 {
return k;
}
}
}
for (k, &(_, dst)) in self.types.iter().enumerate() {
if !dst {
return k;
}
}
0
}
}
impl TimeZone for Tzif {
fn offset(&self, at: i64) -> i32 {
match self.changes.last() {
Some(&(last, _)) if at >= last && self.rule.is_some() => {
return self.rule.as_ref().map_or(0, |r| r.offset(at));
}
None => {
if let Some(r) = &self.rule {
return r.offset(at);
}
}
_ => {}
}
let n = self.changes.partition_point(|&(when, _)| when <= at);
let ty = if n == 0 {
self.first()
} else {
self.changes[n - 1].1
};
self.types[ty].0
}
}