extern crate parse_zoneinfo;
extern crate regex;
use std::io::Write;
use std::path::Path;
use parse_zoneinfo::line::Line;
use parse_zoneinfo::table::Table;
use parse_zoneinfo::transitions::{FixedTimespan, TableTransitions};
const TIMENS_TZ_FILTER: &str = "TIMENS_TZ_FILTER";
fn strip_comments(mut line: String) -> String {
if let Some(pos) = line.find('#') {
line.truncate(pos);
};
line
}
fn convert_bad_chars(name: &str) -> String {
let name = name.replace('/', "__").replace('+', "Plus");
if let Some(pos) = name.find('-') {
if name[pos + 1..].chars().next().map(char::is_numeric).unwrap_or(false) {
name.replace('-', "Minus")
} else {
name.replace('-', "")
}
} else {
name
}
}
fn re_filter() -> Option<regex::Regex> {
match std::env::var(TIMENS_TZ_FILTER) {
Ok(regex) => Some(regex::Regex::new(®ex).unwrap()),
Err(std::env::VarError::NotPresent) => None,
Err(std::env::VarError::NotUnicode(_)) => panic!("invalid unicode in {TIMENS_TZ_FILTER}"),
}
}
fn write_timezone_file(f: &mut std::fs::File, table: &Table) -> std::io::Result<()> {
let re = re_filter();
let zones = table
.zonesets
.keys()
.chain(table.links.keys())
.filter(move |&str| match &str[..] {
"GMT" | "UTC" | "Europe/London" | "America/New_York" | "Asia/Hong_Kong" => true,
_ => re.as_ref().map_or(false, |re| re.is_match(str)),
})
.collect::<std::collections::BTreeSet<_>>();
writeln!(f, "use crate::timezone::{{TzInfo, TzOffset, TzParseError}};\n\n")?;
writeln!(f, "#[derive(Clone, Copy, PartialEq, Eq, Hash)]")?;
writeln!(f, "pub enum Tz {{")?;
for zone in &zones {
writeln!(f, " /// {zone}")?;
writeln!(f, " {},", convert_bad_chars(zone))?;
}
writeln!(f, "}}\n")?;
writeln!(f, "impl Tz {{")?;
writeln!(f, " pub fn name(self) -> &'static str {{")?;
writeln!(f, " match self {{")?;
for zone in &zones {
let zone_name = convert_bad_chars(zone);
writeln!(f, " Tz::{zone_name} => \"{zone}\",")?;
}
writeln!(f, " }}")?;
writeln!(f, " }}")?;
writeln!(f, "}}\n")?;
writeln!(f, "impl std::fmt::Debug for Tz {{")?;
writeln!(f, " fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{")?;
writeln!(f, " f.write_str(self.name().as_ref())")?;
writeln!(f, " }}")?;
writeln!(f, "}}\n")?;
writeln!(f, "impl std::fmt::Display for Tz {{")?;
writeln!(f, " fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{")?;
writeln!(f, " f.write_str(self.name().as_ref())")?;
writeln!(f, " }}")?;
writeln!(f, "}}\n")?;
writeln!(f, "impl std::str::FromStr for Tz {{")?;
writeln!(f, " type Err = TzParseError;")?;
writeln!(f, " fn from_str(s: &str) -> Result<Self, Self::Err> {{")?;
writeln!(f, " match s {{")?;
for zone in &zones {
let zone_name = convert_bad_chars(zone);
writeln!(f, " \"{zone}\" => Ok(Tz::{zone_name}),")?;
}
writeln!(f, " _ => Err(TzParseError::UnknownZone(s.to_string())),")?;
writeln!(f, " }}")?;
writeln!(f, " }}")?;
writeln!(f, "}}\n")?;
writeln!(f, "impl Tz {{")?;
writeln!(f, " pub fn tz_info(&self) -> TzInfo {{")?;
writeln!(f, " match self {{")?;
for zone in &zones {
let zone_name = convert_bad_chars(zone);
let timespans = table.timespans(zone).unwrap();
writeln!(f, " Self::{zone_name} => {{")?;
writeln!(f, " const TZ: TzInfo = TzInfo {{")?;
writeln!(
f,
" first: TzOffset {{ utc_offset: {}, dst_offset: {} }},",
timespans.first.utc_offset, timespans.first.dst_offset
)?;
writeln!(f, " rest: &[")?;
for (start, FixedTimespan { utc_offset, dst_offset, .. }) in timespans.rest {
writeln!(
f,
" ({start}, TzOffset {{ utc_offset: {utc_offset}, dst_offset: {dst_offset} }}),"
)?;
}
writeln!(f, " ],")?;
writeln!(f, " }};")?;
writeln!(f, " TZ")?;
writeln!(f, " }},")?;
}
writeln!(f, " }}")?;
writeln!(f, " }}")?;
writeln!(f, "}}\n")?;
writeln!(f, "#[cfg(feature = \"with_serde\")]")?;
writeln!(f, "mod with_serde {{")?;
writeln!(f, " use super::Tz;")?;
writeln!(f, " use serde::{{Deserialize, Deserializer, Serialize, Serializer}};")?;
writeln!(f, " use std::str::FromStr;")?;
writeln!(f, " impl Serialize for Tz {{")?;
writeln!(
f,
" fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {{"
)?;
writeln!(f, " self.to_string().serialize(serializer)")?;
writeln!(f, " }}")?;
writeln!(f, " }}")?;
writeln!(f, " impl<'de> Deserialize<'de> for Tz {{")?;
writeln!(f, " fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {{")?;
writeln!(f, " let s = String::deserialize(deserializer)?;")?;
writeln!(f, " Tz::from_str(&s).map_err(serde::de::Error::custom)")?;
writeln!(f, " }}")?;
writeln!(f, " }}")?;
writeln!(f, "}}\n")?;
Ok(())
}
fn main() {
println!("cargo:rerun-if-env-changed={TIMENS_TZ_FILTER}");
let out_dir = std::env::var("OUT_DIR").unwrap();
let parser = parse_zoneinfo::line::LineParser::new();
let mut table = parse_zoneinfo::table::TableBuilder::new();
let tzfiles = [
"tz/africa",
"tz/antarctica",
"tz/asia",
"tz/australasia",
"tz/backward",
"tz/etcetera",
"tz/europe",
"tz/northamerica",
"tz/southamerica",
];
let lines = tzfiles
.iter()
.map(Path::new)
.map(|p| {
Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| String::new()))
.join(p)
})
.map(|path| {
std::fs::File::open(&path)
.unwrap_or_else(|e| panic!("cannot open {}: {}", path.display(), e))
})
.map(std::io::BufReader::new)
.flat_map(std::io::BufRead::lines)
.map(Result::unwrap)
.map(strip_comments);
for line in lines {
match parser.parse_str(&line).unwrap() {
Line::Zone(zone) => table.add_zone_line(zone).unwrap(),
Line::Continuation(cont) => table.add_continuation_line(cont).unwrap(),
Line::Rule(rule) => table.add_rule_line(rule).unwrap(),
Line::Link(link) => table.add_link_line(link).unwrap(),
Line::Space => {}
}
}
let table = table.build();
let timezone_path = Path::new(&out_dir).join("timezone_data.rs");
let mut timezone_file = std::fs::File::create(timezone_path).unwrap();
write_timezone_file(&mut timezone_file, &table).unwrap();
}