1use crate::zipstore;
7use crate::ZONEINFO_ZIP;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct ZoneMeta<'a> {
12 pub lat: f64,
14 pub lon: f64,
16 pub commentary: &'a str,
18 codes: &'a str,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Country<'a> {
25 pub code: &'a str,
27 pub name: &'a str,
29}
30
31impl ZoneMeta<'static> {
32 pub fn countries(&self) -> impl Iterator<Item = Country<'static>> {
34 self.codes.split(',').map(|code| Country {
35 code,
36 name: iso_name(code),
37 })
38 }
39}
40
41pub fn meta(name: &str) -> Option<ZoneMeta<'static>> {
43 let data = zipstore::find(ZONEINFO_ZIP, "zone1970.tab").ok()?;
44 let text = core::str::from_utf8(data).ok()?;
45 for line in text.split('\n') {
46 if line.is_empty() || line.starts_with('#') {
47 continue;
48 }
49 let mut fields = line.split('\t');
50 let codes = fields.next()?;
51 let coord = fields.next()?;
52 let zname = match fields.next() {
53 Some(z) => z,
54 None => continue,
55 };
56 if zname != name {
57 continue;
58 }
59 let commentary = fields.next().unwrap_or("");
60 let (lat, lon) = parse_iso6709(coord);
61 return Some(ZoneMeta {
62 lat,
63 lon,
64 commentary,
65 codes,
66 });
67 }
68 None
69}
70
71fn iso_name(code: &str) -> &'static str {
73 let data = match zipstore::find(ZONEINFO_ZIP, "iso3166.tab") {
74 Ok(d) => d,
75 Err(_) => return "",
76 };
77 let text = core::str::from_utf8(data).unwrap_or("");
78 for line in text.split('\n') {
79 if line.is_empty() || line.starts_with('#') {
80 continue;
81 }
82 let mut parts = line.splitn(2, '\t');
83 let c = parts.next().unwrap_or("");
84 if c == code {
85 return parts.next().unwrap_or("");
86 }
87 }
88 ""
89}
90
91pub fn parse_iso6709(s: &str) -> (f64, f64) {
93 let b = s.as_bytes();
94 let mut lon_start = None;
96 for (i, &c) in b.iter().enumerate().skip(1) {
97 if c == b'+' || c == b'-' {
98 lon_start = Some(i);
99 break;
100 }
101 }
102 let Some(lon_start) = lon_start else {
103 return (0.0, 0.0);
104 };
105 let lat = parse_dms(&s[..lon_start], 2);
106 let lon = parse_dms(&s[lon_start..], 3);
107 (lat, lon)
108}
109
110fn parse_dms(s: &str, deg_digits: usize) -> f64 {
113 let b = s.as_bytes();
114 if b.len() < 1 + deg_digits + 2 {
115 return 0.0;
116 }
117 let neg = b[0] == b'-';
118 let mut i = 1; let deg = atoi(&b[i..i + deg_digits]);
121 i += deg_digits;
122 let min = atoi(&b[i..i + 2]);
123 i += 2;
124 let sec = if b.len() >= i + 2 {
125 atoi(&b[i..i + 2])
126 } else {
127 0
128 };
129
130 let total_seconds = deg * 3600 + min * 60 + sec;
132 let val_e4 = (total_seconds * 10000 + 1800) / 3600;
133 let v = val_e4 as f64 / 10000.0;
134 if neg {
135 -v
136 } else {
137 v
138 }
139}
140
141fn atoi(b: &[u8]) -> i64 {
142 let mut n = 0i64;
143 for &c in b {
144 if c.is_ascii_digit() {
145 n = n * 10 + (c - b'0') as i64;
146 }
147 }
148 n
149}