tzcompile/compare/
semantic.rs1use crate::tzif::{LocalTimeType, ParsedTzif};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Difference {
22 pub what: String,
23 pub ours: String,
24 pub theirs: String,
25}
26
27type Eff = (i32, bool, String);
29
30fn eff(t: &LocalTimeType) -> Eff {
31 (t.utoff, t.is_dst, t.abbr.clone())
32}
33
34fn initial_type(p: &ParsedTzif) -> Eff {
36 if p.transitions.is_empty() {
37 return eff(&p.types[0]);
38 }
39 let idx = p.types.iter().position(|t| !t.is_dst).unwrap_or(0);
40 eff(&p.types[idx])
41}
42
43pub fn effective_at(p: &ParsedTzif, t: i64) -> (i32, bool, String) {
50 match p.transitions.iter().rev().find(|tr| tr.at <= t) {
51 Some(tr) => eff(&p.types[tr.type_index as usize]),
52 None => initial_type(p),
53 }
54}
55
56pub fn diff(ours: &ParsedTzif, theirs: &ParsedTzif) -> Vec<Difference> {
58 let mut diffs = Vec::new();
59
60 if ours.footer != theirs.footer {
61 diffs.push(Difference {
62 what: "footer (POSIX TZ)".into(),
63 ours: ours.footer.clone(),
64 theirs: theirs.footer.clone(),
65 });
66 }
67
68 let oi = initial_type(ours);
69 let ti = initial_type(theirs);
70 if oi != ti {
71 diffs.push(Difference {
72 what: "initial local-time-type".into(),
73 ours: format!("{oi:?}"),
74 theirs: format!("{ti:?}"),
75 });
76 }
77
78 if ours.transitions.len() != theirs.transitions.len() {
79 diffs.push(Difference {
80 what: "transition count".into(),
81 ours: ours.transitions.len().to_string(),
82 theirs: theirs.transitions.len().to_string(),
83 });
84 return diffs;
86 }
87
88 for (i, (a, b)) in ours.transitions.iter().zip(&theirs.transitions).enumerate() {
89 if a.at != b.at {
90 diffs.push(Difference {
91 what: format!("transition[{i}] instant"),
92 ours: a.at.to_string(),
93 theirs: b.at.to_string(),
94 });
95 }
96 let ea = eff(&ours.types[a.type_index as usize]);
97 let eb = eff(&theirs.types[b.type_index as usize]);
98 if ea != eb {
99 diffs.push(Difference {
100 what: format!("transition[{i}] target type"),
101 ours: format!("{ea:?}"),
102 theirs: format!("{eb:?}"),
103 });
104 }
105 }
106
107 diffs
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::tzif::{write_bytes, TzifData};
114
115 fn parsed(d: &TzifData) -> ParsedTzif {
116 crate::tzif::parse(&write_bytes(d).unwrap()).unwrap()
117 }
118
119 #[test]
120 fn identical_fixed_zones_match() {
121 let a = parsed(&TzifData::fixed(-18000, "EST", "EST5"));
122 let b = parsed(&TzifData::fixed(-18000, "EST", "EST5"));
123 assert!(diff(&a, &b).is_empty());
124 }
125
126 #[test]
127 fn offset_mismatch_is_reported() {
128 let a = parsed(&TzifData::fixed(-18000, "EST", "EST5"));
129 let b = parsed(&TzifData::fixed(-14400, "EDT", "EDT4"));
130 assert!(!diff(&a, &b).is_empty());
131 }
132}