diff --git a/Cargo.lock b/Cargo.lock index 9c01c8e..4410e35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,10 +202,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" dependencies = [ "chrono", - "chrono-tz-build", + "chrono-tz-build 0.0.2", "phf 0.10.1", ] +[[package]] +name = "chrono-tz" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1369bc6b9e9a7dfdae2055f6ec151fe9c554a9d23d357c0237cee2e25eaabb7" +dependencies = [ + "chrono", + "chrono-tz-build 0.2.0", + "phf 0.11.2", +] + [[package]] name = "chrono-tz-build" version = "0.0.2" @@ -214,7 +225,18 @@ checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" dependencies = [ "parse-zoneinfo", "phf 0.10.1", - "phf_codegen", + "phf_codegen 0.10.0", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2f5ebdc942f57ed96d560a6d1a459bae5851102a25d5bf89dc04ae453e31ecf" +dependencies = [ + "parse-zoneinfo", + "phf 0.11.2", + "phf_codegen 0.11.2", ] [[package]] @@ -628,7 +650,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f80f221db1bc708b71128757b9396727c04de86968081e18e89b0575e03be071" dependencies = [ - "phf 0.11.1", + "phf 0.11.2", ] [[package]] @@ -886,11 +908,12 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ - "phf_shared 0.11.1", + "phf_macros", + "phf_shared 0.11.2", ] [[package]] @@ -899,10 +922,20 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" dependencies = [ - "phf_generator", + "phf_generator 0.10.0", "phf_shared 0.10.0", ] +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator 0.11.1", + "phf_shared 0.11.2", +] + [[package]] name = "phf_generator" version = "0.10.0" @@ -913,6 +946,29 @@ dependencies = [ "rand", ] +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.1", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "phf_shared" version = "0.10.0" @@ -925,9 +981,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] @@ -1350,7 +1406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ab29bb4f3e256ae6ad5c3e2775aa1f8829f2c0c101fc407bfd3a6df15c60c5" dependencies = [ "chrono", - "chrono-tz", + "chrono-tz 0.6.1", "globwalk", "humansize", "lazy_static", @@ -1411,11 +1467,16 @@ name = "time-banner" version = "0.1.0" dependencies = [ "axum", + "chrono", + "chrono-tz 0.8.3", "dotenvy", "envy", "futures", "lazy_static", + "phf 0.11.2", + "phf_codegen 0.11.2", "png", + "regex", "resvg", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 6899ed0..140d41e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,14 @@ envy = "0.4.2" tera = "1.19.0" lazy_static = "1.4.0" timeago = "0.4.1" +chrono-tz = "0.8.3" +phf = { version = "0.11.2", features = ["macros"] } +phf_codegen = "0.11.1" +chrono = "0.4.26" + +[build-dependencies] +chrono = "0.4.26" +regex = "1.8.4" +phf = { version = "0.11.1", default-features = false } +phf_codegen = "0.11.1" +lazy_static = "1.4.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..f3dab19 --- /dev/null +++ b/build.rs @@ -0,0 +1,72 @@ +use std::env; +use std::fs::File; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::path::Path; +use regex::Regex; +use chrono::{FixedOffset}; +use lazy_static::lazy_static; + +lazy_static! { + static ref FULL_PATTERN: Regex = Regex::new(r"([A-Z]+)\s\t.+\s\tUTC([−+±]\d{2}(?::\d{2})?)").unwrap(); + static ref OFFSET_PATTERN: Regex = Regex::new(r"([−+±])(\d{2}(?::\d{2})?)").unwrap(); +} + +const HOUR: u32 = 3600; + +fn parse_offset(raw_offset: &str) -> i32 { + let capture = OFFSET_PATTERN.captures(raw_offset).expect("RegEx failed to match offset"); + println!("{}: {}", raw_offset, capture.get(1).expect("First group capture failed").as_str()); + + let is_west = capture.get(1).unwrap().as_str() == "−"; + let time = capture.get(2).expect("Second group capture failed").as_str(); + let (hours, minutes) = if time.contains(':') { + let mut split = time.split(':'); + let hours = split.next().unwrap().parse::().unwrap(); + let minutes = split.next().unwrap().parse::().unwrap(); + + (hours, minutes) + } else { + // Minutes not specified, assume 0 + (time.parse::().unwrap(), 0) + }; + + let value = (hours * HOUR) + (minutes * 60); + return if is_west { value as i32 * -1 } else { value as i32 }; +} + +fn main() { + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); + let raw_tz = BufReader::new(File::open("./src/abbr_tz").unwrap()); + + let mut file = BufWriter::new(File::create(&path).unwrap()); + + let mut builder: phf_codegen::Map = phf_codegen::Map::new(); + + for line in raw_tz.lines() { + let line = line.unwrap(); + if line.starts_with('#') { + continue; + } + + let capture = FULL_PATTERN.captures(&line).expect("RegEx failed to match line"); + + let abbreviation = capture.get(1).unwrap().as_str(); + let raw_offset = capture.get(2).unwrap().as_str(); + + let offset = if !raw_offset.starts_with('±') { + parse_offset(raw_offset) + } else { + 0 + }; + + builder.entry(String::from(abbreviation), &format!("\"{}\"", offset).to_string()); + } + + write!( + &mut file, + "static TIMEZONES: phf::Map<&'static str, &'static str> = {}", + builder.build() + ) + .unwrap(); + write!(&mut file, ";\n").unwrap(); +} \ No newline at end of file diff --git a/src/abbr.rs b/src/abbr.rs new file mode 100644 index 0000000..5b9d0bc --- /dev/null +++ b/src/abbr.rs @@ -0,0 +1,35 @@ +use chrono::{FixedOffset, TimeZone}; +use chrono::format::Fixed; +use phf::{Map, phf_map}; + +const HOUR: i32 = 60 * 60; + +// Generated by build.rs, phf_codegen +include!(concat!(env!("OUT_DIR"), "/codegen.rs")); + +/* + Parse an abbreviation of a timezone into a UTC offset. + Note: This is not standardized at all and is simply built on a reference of Time Zone abbreviations + from Wikipedia (as of 2023-7-20). + */ +pub fn parse_abbreviation(abbreviation: &str) -> Result { + let offset_integer_string = TIMEZONES.get(abbreviation); + if offset_integer_string.is_none() { + return Err("Failed to find abbreviation".to_string()); + } + + let offset = FixedOffset::east_opt(offset_integer_string.unwrap().parse().expect("Failed to parse stored offset")); + return offset.ok_or("Failed to parse offset".to_string()); +} + + +#[cfg(test)] +mod tests { + use chrono::FixedOffset; + use crate::abbr::parse_abbreviation; + + #[test] + fn parse_offset() { + assert_eq!(parse_abbreviation("CST").unwrap(), FixedOffset::west_opt(6 * 3600).unwrap()); + } +} \ No newline at end of file diff --git a/src/abbr_tz b/src/abbr_tz new file mode 100644 index 0000000..0a14362 --- /dev/null +++ b/src/abbr_tz @@ -0,0 +1,208 @@ +ACDT Australian Central Daylight Saving Time UTC+10:30 +ACST Australian Central Standard Time UTC+09:30 +ACT Acre Time UTC−05 +# ACT ASEAN Common Time (proposed) UTC+08:00 +ACWST Australian Central Western Standard Time (unofficial) UTC+08:45 +ADT Atlantic Daylight Time UTC−03 +AEDT Australian Eastern Daylight Saving Time UTC+11 +AEST Australian Eastern Standard Time UTC+10 +AET Australian Eastern Time UTC+10/UTC+11 +AFT Afghanistan Time UTC+04:30 +AKDT Alaska Daylight Time UTC−08 +AKST Alaska Standard Time UTC−09 +ALMT Alma-Ata Time[1] UTC+06 +AMST Amazon Summer Time (Brazil)[2] UTC−03 +AMT Amazon Time (Brazil)[3] UTC−04 +# AMT Armenia Time UTC+04 +ANAT Anadyr Time[4] UTC+12 +AQTT Aqtobe Time[5] UTC+05 +ART Argentina Time UTC−03 +# AST Arabia Standard Time UTC+03 +AST Atlantic Standard Time UTC−04 +AWST Australian Western Standard Time UTC+08 +AZOST Azores Summer Time UTC±00 +AZOT Azores Standard Time UTC−01 +AZT Azerbaijan Time UTC+04 +BNT Brunei Time UTC+08 +BIOT British Indian Ocean Time UTC+06 +BIT Baker Island Time UTC−12 +BOT Bolivia Time UTC−04 +BRST Brasília Summer Time UTC−02 +BRT Brasília Time UTC−03 +# BST Bangladesh Standard Time UTC+06 +# BST Bougainville Standard Time[6] UTC+11 +BST British Summer Time (British Standard Time from Mar 1968 to Oct 1971) UTC+01 +BTT Bhutan Time UTC+06 +CAT Central Africa Time UTC+02 +CCT Cocos Islands Time UTC+06:30 +CDT Central Daylight Time (North America) UTC−05 +# CDT Cuba Daylight Time[7] UTC−04 +CEST Central European Summer Time UTC+02 +CET Central European Time UTC+01 +CHADT Chatham Daylight Time UTC+13:45 +CHAST Chatham Standard Time UTC+12:45 +CHOT Choibalsan Standard Time UTC+08 +CHOST Choibalsan Summer Time UTC+09 +CHST Chamorro Standard Time UTC+10 +CHUT Chuuk Time UTC+10 +CIST Clipperton Island Standard Time UTC−08 +CKT Cook Island Time UTC−10 +CLST Chile Summer Time UTC−03 +CLT Chile Standard Time UTC−04 +COST Colombia Summer Time UTC−04 +COT Colombia Time UTC−05 +CST Central Standard Time (North America) UTC−06 +# CST China Standard Time UTC+08 +# CST Cuba Standard Time UTC−05 +CT Central Time UTC−06/UTC−05 +CVT Cape Verde Time UTC−01 +CWST Central Western Standard Time (Australia) unofficial UTC+08:45 +CXT Christmas Island Time UTC+07 +DAVT Davis Time UTC+07 +DDUT Dumont d'Urville Time UTC+10 +DFT AIX-specific equivalent of Central European Time[NB 1] UTC+01 +EASST Easter Island Summer Time UTC−05 +EAST Easter Island Standard Time UTC−06 +EAT East Africa Time UTC+03 +ECT Eastern Caribbean Time (does not recognise DST) UTC−04 +# ECT Ecuador Time UTC−05 +EDT Eastern Daylight Time (North America) UTC−04 +EEST Eastern European Summer Time UTC+03 +EET Eastern European Time UTC+02 +EGST Eastern Greenland Summer Time UTC±00 +EGT Eastern Greenland Time UTC−01 +EST Eastern Standard Time (North America) UTC−05 +ET Eastern Time (North America) UTC−05 / UTC−04 +FET Further-eastern European Time UTC+03 +FJT Fiji Time UTC+12 +FKST Falkland Islands Summer Time UTC−03 +FKT Falkland Islands Time UTC−04 +FNT Fernando de Noronha Time UTC−02 +GALT Galápagos Time UTC−06 +GAMT Gambier Islands Time UTC−09 +GET Georgia Standard Time UTC+04 +GFT French Guiana Time UTC−03 +GILT Gilbert Island Time UTC+12 +GIT Gambier Island Time UTC−09 +GMT Greenwich Mean Time UTC±00 +# GST South Georgia and the South Sandwich Islands Time UTC−02 +GST Gulf Standard Time UTC+04 +GYT Guyana Time UTC−04 +HDT Hawaii–Aleutian Daylight Time UTC−09 +HAEC Heure Avancée d'Europe Centrale French-language name for CEST UTC+02 +HST Hawaii–Aleutian Standard Time UTC−10 +HKT Hong Kong Time UTC+08 +HMT Heard and McDonald Islands Time UTC+05 +HOVST Hovd Summer Time (not used from 2017-present) UTC+08 +HOVT Hovd Time UTC+07 +ICT Indochina Time UTC+07 +IDLW International Date Line West time zone UTC−12 +IDT Israel Daylight Time UTC+03 +IOT Indian Ocean Time UTC+03 +IRDT Iran Daylight Time UTC+04:30 +IRKT Irkutsk Time UTC+08 +IRST Iran Standard Time UTC+03:30 +# IST Indian Standard Time UTC+05:30 +# IST Irish Standard Time[8] UTC+01 +IST Israel Standard Time UTC+02 +JST Japan Standard Time UTC+09 +KALT Kaliningrad Time UTC+02 +KGT Kyrgyzstan Time UTC+06 +KOST Kosrae Time UTC+11 +KRAT Krasnoyarsk Time UTC+07 +KST Korea Standard Time UTC+09 +LHST Lord Howe Standard Time UTC+10:30 +# LHST Lord Howe Summer Time UTC+11 +LINT Line Islands Time UTC+14 +MAGT Magadan Time UTC+12 +MART Marquesas Islands Time UTC−09:30 +MAWT Mawson Station Time UTC+05 +MDT Mountain Daylight Time (North America) UTC−06 +MET Middle European Time (same zone as CET) UTC+01 +MEST Middle European Summer Time (same zone as CEST) UTC+02 +MHT Marshall Islands Time UTC+12 +MIST Macquarie Island Station Time UTC+11 +MIT Marquesas Islands Time UTC−09:30 +MMT Myanmar Standard Time UTC+06:30 +MSK Moscow Time UTC+03 +# MST Malaysia Standard Time UTC+08 +MST Mountain Standard Time (North America) UTC−07 +MUT Mauritius Time UTC+04 +MVT Maldives Time UTC+05 +MYT Malaysia Time UTC+08 +NCT New Caledonia Time UTC+11 +NDT Newfoundland Daylight Time UTC−02:30 +NFT Norfolk Island Time UTC+11 +NOVT Novosibirsk Time [9] UTC+07 +NPT Nepal Time UTC+05:45 +NST Newfoundland Standard Time UTC−03:30 +NT Newfoundland Time UTC−03:30 +NUT Niue Time UTC−11 +NZDT New Zealand Daylight Time UTC+13 +NZST New Zealand Standard Time UTC+12 +OMST Omsk Time UTC+06 +ORAT Oral Time UTC+05 +PDT Pacific Daylight Time (North America) UTC−07 +PET Peru Time UTC−05 +PETT Kamchatka Time UTC+12 +PGT Papua New Guinea Time UTC+10 +PHOT Phoenix Island Time UTC+13 +PHT Philippine Time UTC+08 +PHST Philippine Standard Time UTC+08 +PKT Pakistan Standard Time UTC+05 +PMDT Saint Pierre and Miquelon Daylight Time UTC−02 +PMST Saint Pierre and Miquelon Standard Time UTC−03 +PONT Pohnpei Standard Time UTC+11 +PST Pacific Standard Time (North America) UTC−08 +PWT Palau Time[10] UTC+09 +PYST Paraguay Summer Time[11] UTC−03 +PYT Paraguay Time[12] UTC−04 +RET Réunion Time UTC+04 +ROTT Rothera Research Station Time UTC−03 +SAKT Sakhalin Island Time UTC+11 +SAMT Samara Time UTC+04 +SAST South African Standard Time UTC+02 +SBT Solomon Islands Time UTC+11 +SCT Seychelles Time UTC+04 +SDT Samoa Daylight Time UTC−10 +SGT Singapore Time UTC+08 +SLST Sri Lanka Standard Time UTC+05:30 +SRET Srednekolymsk Time UTC+11 +SRT Suriname Time UTC−03 +SST Samoa Standard Time UTC−11 +# SST Singapore Standard Time UTC+08 +SYOT Showa Station Time UTC+03 +TAHT Tahiti Time UTC−10 +THA Thailand Standard Time UTC+07 +TFT French Southern and Antarctic Time[13] UTC+05 +TJT Tajikistan Time UTC+05 +TKT Tokelau Time UTC+13 +TLT Timor Leste Time UTC+09 +TMT Turkmenistan Time UTC+05 +TRT Turkey Time UTC+03 +TOT Tonga Time UTC+13 +TVT Tuvalu Time UTC+12 +ULAST Ulaanbaatar Summer Time UTC+09 +ULAT Ulaanbaatar Standard Time UTC+08 +UTC Coordinated Universal Time UTC±00 +UYST Uruguay Summer Time UTC−02 +UYT Uruguay Standard Time UTC−03 +UZT Uzbekistan Time UTC+05 +VET Venezuelan Standard Time UTC−04 +VLAT Vladivostok Time UTC+10 +VOLT Volgograd Time UTC+03 +VOST Vostok Station Time UTC+06 +VUT Vanuatu Time UTC+11 +WAKT Wake Island Time UTC+12 +WAST West Africa Summer Time UTC+02 +WAT West Africa Time UTC+01 +WEST Western European Summer Time UTC+01 +WET Western European Time UTC±00 +WIB Western Indonesian Time UTC+07 +WIT Eastern Indonesian Time UTC+09 +WITA Central Indonesia Time UTC+08 +WGST West Greenland Summer Time[14] UTC−02 +WGT West Greenland Time[15] UTC−03 +WST Western Standard Time UTC+08 +YAKT Yakutsk Time UTC+09 +YEKT Yekaterinburg Time UTC+05 \ No newline at end of file