diff --git a/chrono-tz-build/src/lib.rs b/chrono-tz-build/src/lib.rs index 0557219..a1272c6 100644 --- a/chrono-tz-build/src/lib.rs +++ b/chrono-tz-build/src/lib.rs @@ -2,7 +2,7 @@ extern crate parse_zoneinfo; #[cfg(feature = "filter-by-regex")] extern crate regex; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashSet}; use std::env; use std::fs::File; use std::io::{self, BufRead, BufReader, Write}; @@ -30,17 +30,14 @@ fn strip_comments(mut line: String) -> String { // Generate a list of the time zone periods beyond the first that apply // to this zone, as a string representation of a static slice. -fn format_rest(rest: Vec<(i64, FixedTimespan)>) -> String { +fn format_rest(rest: Vec<(i64, FixedTimespan)>, abbreviations: &str) -> String { let mut ret = "&[\n".to_string(); for (start, FixedTimespan { utc_offset, dst_offset, name }) in rest { ret.push_str(&format!( - " ({start}, FixedTimespan {{ \ - utc_offset: {utc}, dst_offset: {dst}, name: \"{name}\" \ - }}),\n", + " ({start}, FixedTimespan {{ offset: {offset}, abbreviation: {index_len} }}),\n", start = start, - utc = utc_offset, - dst = dst_offset, - name = name, + offset = utc_offset << 14 | (dst_offset & ((1 << 14) - 1)), + index_len = (abbreviations.find(&name).unwrap() << 3) | name.len(), )); } ret.push_str(" ]"); @@ -68,12 +65,29 @@ fn convert_bad_chars(name: &str) -> String { // TimeZone for any contained struct that implements `Timespans`. fn write_timezone_file(timezone_file: &mut File, table: &Table) -> io::Result<()> { let zones = table.zonesets.keys().chain(table.links.keys()).collect::>(); + + // Collect all unique abbreviations into a HashSet, sort, and concatenate into a string. + let mut abbreviations = HashSet::new(); + for zone in &zones { + let timespans = table.timespans(zone).unwrap(); + for (_, timespan) in timespans.rest.into_iter().chain(Some((0, timespans.first))) { + abbreviations.insert(timespan.name.clone()); + } + } + let mut abbreviations: Vec<_> = abbreviations.iter().collect(); + abbreviations.sort(); + let mut abbreviations_str = String::new(); + for abbr in abbreviations.drain(..) { + abbreviations_str.push_str(abbr) + } + writeln!(timezone_file, "use core::fmt::{{self, Debug, Display, Formatter}};",)?; writeln!(timezone_file, "use core::str::FromStr;\n",)?; writeln!( timezone_file, "use crate::timezone_impl::{{TimeSpans, FixedTimespanSet, FixedTimespan}};\n", )?; + writeln!(timezone_file, "pub(crate) const ABBREVIATIONS: &str = \"{}\";\n", abbreviations_str)?; writeln!( timezone_file, "/// TimeZones built at compile time from the tz database @@ -112,8 +126,7 @@ fn write_timezone_file(timezone_file: &mut File, table: &Table) -> io::Result<() writeln!( timezone_file, "static TIMEZONES_UNCASED: ::phf::Map<&'static uncased::UncasedStr, Tz> = \n{};", - // FIXME(petrosagg): remove this once rust-phf/rust-phf#232 is released - map.build().to_string().replace("::std::mem::transmute", "::core::mem::transmute") + map.build() )?; } @@ -206,19 +219,16 @@ impl FromStr for Tz {{ " Tz::{zone} => {{ const REST: &[(i64, FixedTimespan)] = {rest}; FixedTimespanSet {{ - first: FixedTimespan {{ - utc_offset: {utc}, - dst_offset: {dst}, - name: \"{name}\", - }}, + first: FixedTimespan {{ offset: {offset}, abbreviation: {index_len} }}, rest: REST }} }},\n", zone = zone_name, - rest = format_rest(timespans.rest), - utc = timespans.first.utc_offset, - dst = timespans.first.dst_offset, - name = timespans.first.name, + rest = format_rest(timespans.rest, &abbreviations_str), + offset = + timespans.first.utc_offset << 14 | (timespans.first.dst_offset & ((1 << 14) - 1)), + index_len = (abbreviations_str.find(×pans.first.name).unwrap() << 3) + | timespans.first.name.len(), )?; } write!( diff --git a/chrono-tz/src/directory.rs b/chrono-tz/src/directory.rs index 5741dfc..025afc4 100644 --- a/chrono-tz/src/directory.rs +++ b/chrono-tz/src/directory.rs @@ -1,5 +1,4 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] -#![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/directory.rs")); diff --git a/chrono-tz/src/lib.rs b/chrono-tz/src/lib.rs index 5393fe8..593a773 100644 --- a/chrono-tz/src/lib.rs +++ b/chrono-tz/src/lib.rs @@ -163,7 +163,8 @@ mod tests { use super::IANA_TZDB_VERSION; use super::US::Eastern; use super::UTC; - use chrono::{Duration, NaiveDate, TimeZone}; + use crate::TzOffset; + use chrono::{DateTime, Duration, NaiveDate, TimeZone}; #[test] fn london_to_berlin() { @@ -412,4 +413,10 @@ mod tests { assert_eq!(4, numbers.len()); assert!(IANA_TZDB_VERSION.ends_with(|c: char| c.is_ascii_lowercase())); } + + #[test] + fn test_type_size() { + assert_eq!(core::mem::size_of::(), 8); + assert_eq!(core::mem::size_of::>(), 20); + } } diff --git a/chrono-tz/src/timezone_impl.rs b/chrono-tz/src/timezone_impl.rs index 4c1e3f4..123727c 100644 --- a/chrono-tz/src/timezone_impl.rs +++ b/chrono-tz/src/timezone_impl.rs @@ -6,7 +6,7 @@ use chrono::{ }; use crate::binary_search::binary_search; -use crate::timezones::Tz; +use crate::timezones::{Tz, ABBREVIATIONS}; /// Returns [`Tz::UTC`]. impl Default for Tz { @@ -20,37 +20,34 @@ impl Default for Tz { /// For example, [`::US::Eastern`] is composed of at least two /// `FixedTimespan`s: `EST` and `EDT`, that are variously in effect. #[derive(Copy, Clone, PartialEq, Eq)] -pub struct FixedTimespan { - /// The base offset from UTC; this usually doesn't change unless the government changes something - pub utc_offset: i32, - /// The additional offset from UTC for this timespan; typically for daylight saving time - pub dst_offset: i32, - /// The name of this timezone, for example the difference between `EDT`/`EST` - pub name: &'static str, +pub(crate) struct FixedTimespan { + /// We encode two values in the offset: the base offset from utc and the extra offset due to + /// DST. It is encoded as `(utc_offset << 14) | (dst_offset & ((1 << 14) - 1))`. + pub(crate) offset: i32, + /// The abbreviation of this offset, for example the difference between `EDT`/`EST`. + /// Stored as a slice of the `ABBREVIATIONS` static as `index << 3 | len`. + pub(crate) abbreviation: i16, } -impl Offset for FixedTimespan { - fn fix(&self) -> FixedOffset { - FixedOffset::east_opt(self.utc_offset + self.dst_offset).unwrap() +impl FixedTimespan { + fn base_offset(&self) -> i32 { + self.offset >> 14 } -} -impl Display for FixedTimespan { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - write!(f, "{}", self.name) - } -} - -impl Debug for FixedTimespan { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - write!(f, "{}", self.name) + fn saving(&self) -> i32 { + (self.offset << 18) >> 18 } } #[derive(Copy, Clone, PartialEq, Eq)] pub struct TzOffset { tz: Tz, - offset: FixedTimespan, + /// We encode two values in the offset: the base offset from utc and the extra offset due to + /// DST. It is encoded as `(utc_offset << 14) | (saving & ((1 << 14) - 1))`. + offset: i32, + /// The abbreviation of this offset, for example the difference between `EDT`/`EST`. + /// Stored as a slice of the `ABBREVIATIONS` static as `index << 3 | len`. + abbreviation: i16, } /// Detailed timezone offset components that expose any special conditions currently in effect. @@ -126,7 +123,7 @@ pub trait OffsetName { impl TzOffset { fn new(tz: Tz, offset: FixedTimespan) -> Self { - TzOffset { tz, offset } + TzOffset { tz, offset: offset.offset, abbreviation: offset.abbreviation } } fn map_localresult(tz: Tz, result: LocalResult) -> LocalResult { @@ -142,11 +139,11 @@ impl TzOffset { impl OffsetComponents for TzOffset { fn base_utc_offset(&self) -> Duration { - Duration::seconds(self.offset.utc_offset as i64) + Duration::seconds((self.offset >> 14) as i64) } fn dst_offset(&self) -> Duration { - Duration::seconds(self.offset.dst_offset as i64) + Duration::seconds(((self.offset << 18) >> 18) as i64) } } @@ -156,25 +153,27 @@ impl OffsetName for TzOffset { } fn abbreviation(&self) -> &str { - self.offset.name + let index = (self.abbreviation >> 3) as usize; + let len = (self.abbreviation & 0b111) as usize; + &ABBREVIATIONS[index..index + len] } } impl Offset for TzOffset { fn fix(&self) -> FixedOffset { - self.offset.fix() + FixedOffset::east_opt((self.offset >> 14) + ((self.offset << 18) >> 18)).unwrap() } } impl Display for TzOffset { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - Display::fmt(&self.offset, f) + f.write_str(self.abbreviation()) } } impl Debug for TzOffset { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - Debug::fmt(&self.offset, f) + f.write_str(self.abbreviation()) } } @@ -243,21 +242,17 @@ impl FixedTimespanSet { None } else { let span = self.rest[index - 1]; - Some(span.0 + span.1.utc_offset as i64 + span.1.dst_offset as i64) + Some(span.0 + (span.1.base_offset() + span.1.saving()) as i64) }, end: if index == self.rest.len() { None } else if index == 0 { - Some( - self.rest[index].0 - + self.first.utc_offset as i64 - + self.first.dst_offset as i64, - ) + Some(self.rest[index].0 + (self.first.base_offset() + self.first.saving()) as i64) } else { Some( self.rest[index].0 - + self.rest[index - 1].1.utc_offset as i64 - + self.rest[index - 1].1.dst_offset as i64, + + self.rest[index - 1].1.base_offset() as i64 + + self.rest[index - 1].1.saving() as i64, ) }, }