From 47c4497e0c85cf6a7faf4b938fcf79d796ce8902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 16 Jan 2025 21:35:01 +0100 Subject: [PATCH] 2025a --- Cargo.toml | 2 +- Makefile | 27 -- benchmarks/Cargo.toml | 29 -- benchmarks/benches/by-name.rs | 152 -------- examples/current-time/src/main.rs | 2 +- fuzz/.gitignore | 1 - fuzz/Cargo.toml | 43 --- fuzz/bin/afl.rs | 5 - fuzz/bin/honggfuzz.rs | 7 - fuzz/bin/libfuzzer.rs | 5 - fuzz/run.sh | 8 - make-tzdb/Cargo.toml | 23 -- make-tzdb/LICENSE.md | 1 - make-tzdb/README.md | 1 - make-tzdb/generate_lookup_table.py | 272 --------------- make-tzdb/src/main.rs | 537 ----------------------------- make-tzdb/src/parse.rs | 241 ------------- rustfmt.toml | 4 +- src/lib.rs | 2 +- tzdb.tar.lz.sha | 1 - tzdb_data/Cargo.toml | 4 +- tzdb_data/src/generated | 2 +- tzdb_data/src/lib.rs | 3 +- 23 files changed, 10 insertions(+), 1362 deletions(-) delete mode 100644 Makefile delete mode 100644 benchmarks/Cargo.toml delete mode 100644 benchmarks/benches/by-name.rs delete mode 100644 fuzz/.gitignore delete mode 100644 fuzz/Cargo.toml delete mode 100644 fuzz/bin/afl.rs delete mode 100644 fuzz/bin/honggfuzz.rs delete mode 100644 fuzz/bin/libfuzzer.rs delete mode 100755 fuzz/run.sh delete mode 100644 make-tzdb/Cargo.toml delete mode 120000 make-tzdb/LICENSE.md delete mode 100644 make-tzdb/README.md delete mode 100644 make-tzdb/generate_lookup_table.py delete mode 100644 make-tzdb/src/main.rs delete mode 100644 make-tzdb/src/parse.rs delete mode 100644 tzdb.tar.lz.sha diff --git a/Cargo.toml b/Cargo.toml index 04f575b..5618c9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ local = ["iana-time-zone"] [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--generate-link-to-definition", "--cfg=docsrs"] [workspace] members = [ diff --git a/Makefile b/Makefile deleted file mode 100644 index 4568bbc..0000000 --- a/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -.DELETE_ON_ERROR: - -TZDB_VERSION := tzdb-2024b - -tzdb_data/src/generated/mod.rs: tmp/${TZDB_VERSION}/usr/share/zoneinfo/ tzdb.tar.lz.sha - cd make-tzdb && cargo r -- ../$(@D) ../$< ../tzdb.tar.lz.sha - cargo +nightly fmt -- \ - $(@D)/by_name.rs \ - $(@D)/mod.rs \ - $(@D)/raw_tzdata.rs \ - $(@D)/test_all_names.rs \ - $(@D)/time_zone.rs \ - $(@D)/tzdata.rs \ - $(@D)/tz_names.rs - -tmp/${TZDB_VERSION}/usr/share/zoneinfo/: tmp/${TZDB_VERSION}/ - cd tmp/${TZDB_VERSION}/ && make PACKRATDATA=backzone PACKRATLIST=zone.tab TOPDIR="." install - -tmp/${TZDB_VERSION}/: tmp/${TZDB_VERSION}.tar.lz - cd tmp/ && tar xf $("] -repository = "https://github.com/Kijewski/tzdb" -description = "… benchmarking …" -license = "Apache-2.0" -# rust-version = "1.56" -publish = false - -[dependencies] -chrono-tz = { version = "0.8", features = ["case-insensitive"] } -tzdb_data = { path = "../tzdb_data" } - -[dev-dependencies] -criterion = { version = "0.5", default-features = false, features = ["html_reports"] } -minstant = "0.1" -rand = { version = "0.8", default-features = false, features = ["std"] } -rand_xoshiro = "0.6" -test-strategy = "0.3" -structmeta = "0.2" - -[[bench]] -name = "by-name" -harness = false - -[workspace] -members = ["."] diff --git a/benchmarks/benches/by-name.rs b/benchmarks/benches/by-name.rs deleted file mode 100644 index 0466bee..0000000 --- a/benchmarks/benches/by-name.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::iter::FusedIterator; -use std::str::FromStr; -use std::time::Duration; - -use criterion::black_box; -use minstant::Instant; -use rand::seq::{IteratorRandom, SliceRandom}; -use rand::SeedableRng; -use rand_xoshiro::Xoroshiro128PlusPlus; -use tzdb_data::{find_raw, TZ_NAMES}; - -fn main() { - #[cfg(not(miri))] - { - criterion::criterion_group!(benches, benchmark_by_name); - benches(); - - criterion::Criterion::default() - .configure_from_args() - .with_plots() - .final_summary(); - } -} - -fn benchmark_by_name(c: &mut criterion::Criterion) { - let names = generate_names(); - let names: Vec<&str> = names.iter().map(String::as_str).collect(); - - macro_rules! bench_functions { - ($($ident:ident($name:pat) $body:block)*) => { $({ - fn $ident(limit: u64, names: &[&str]) -> Duration { - let start = Instant::now(); - for $name in names.iter().cycle().take64(limit) { - let _ = black_box($body); - } - start.elapsed() - } - - c.bench_function( - stringify!($id), - |b| b.iter_custom(|iters| $ident(iters, black_box(&names))), - ); - })* }; - } - - bench_functions! { - tzdb_find_raw(name) { - crate::find_raw(name.as_bytes()) - } - chrono_tz_from_str(name) { - chrono_tz::Tz::from_str(name) - } - chrono_tz_from_str_insensitive(name) { - chrono_tz::Tz::from_str_insensitive(name) - } - } -} - -fn generate_names() -> Vec { - let rng = &mut Xoroshiro128PlusPlus::seed_from_u64(2024_02_01); - - let mut names: Vec<_> = TZ_NAMES.iter().map(|s| String::from(*s)).collect(); - - // insert 10% unknown names - for _ in 0..names.len().div_ceil(10) { - let mut continent = *b"abcdefghijklmnopqrstuvwxyz"; - let mut city = *b"abcdefghijklmnopqrstuvwxyz"; - - continent.shuffle(rng); - city.shuffle(rng); - - let continent = &mut continent[..(4..=8).choose(rng).unwrap()]; - let city = &mut city[..(4..=12).choose(rng).unwrap()]; - - continent[0] = continent[0].to_ascii_uppercase(); - city[0] = city[0].to_ascii_uppercase(); - - let continent = std::str::from_utf8(continent).unwrap(); - let city = std::str::from_utf8(city).unwrap(); - - names.push(format!("{}/{}", continent, city)); - } - - // collect all names with "random" capitalization - let mut names: Vec<_> = names - .into_iter() - .flat_map(|name| { - let upper = name.to_uppercase(); - let lower = name.to_lowercase(); - let inverted = name - .chars() - .map(|c| match c { - 'A'..='Z' => c.to_ascii_lowercase(), - 'a'..='z' => c.to_ascii_uppercase(), - c => c, - }) - .collect(); - let spongebob1 = name - .chars() - .enumerate() - .map(|(i, c)| { - if i % 2 == 0 { - c.to_ascii_uppercase() - } else { - c.to_ascii_lowercase() - } - }) - .collect(); - let spongebob2 = name - .chars() - .enumerate() - .map(|(i, c)| { - if i % 2 == 1 { - c.to_ascii_uppercase() - } else { - c.to_ascii_lowercase() - } - }) - .collect(); - [name, upper, lower, inverted, spongebob1, spongebob2] - }) - .collect(); - - // randomize order - names.shuffle(rng); - names -} - -#[derive(Debug, Clone, Copy)] -struct Take64 { - iter: I, - limit: u64, -} - -impl Iterator for Take64 { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.limit = self.limit.checked_sub(1)?; - self.iter.next() - } -} - -impl FusedIterator for Take64 {} - -trait Take64Ext: Sized { - fn take64(self, limit: u64) -> Take64 { - Take64 { iter: self, limit } - } -} - -impl Take64Ext for I {} diff --git a/examples/current-time/src/main.rs b/examples/current-time/src/main.rs index 09daf74..c52763f 100644 --- a/examples/current-time/src/main.rs +++ b/examples/current-time/src/main.rs @@ -3,7 +3,7 @@ use std::env::args; use std::process::exit; -use tzdb::{local_tz, now, time_zone, tz_by_name, TZ_NAMES}; +use tzdb::{TZ_NAMES, local_tz, now, time_zone, tz_by_name}; pub fn main() -> Result<(), now::NowError> { let mut args = args().fuse(); diff --git a/fuzz/.gitignore b/fuzz/.gitignore deleted file mode 100644 index 98e70c0..0000000 --- a/fuzz/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/corpus diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml deleted file mode 100644 index d97d0e5..0000000 --- a/fuzz/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "tzdb-fuzz" -version = "0.0.0" -authors = ["René Kijewski "] -repository = "https://github.com/Kijewski/tzdb" -description = "… fuzz …" -license = "Apache-2.0" -publish = false -edition = "2018" - -[package.metadata] -cargo-fuzz = true - -[dependencies] -tzdb_data = { path = "../tzdb_data" } - -afl = { version = "0.15", optional = true } -honggfuzz = { version = "0.5", optional = true } -libfuzzer-sys = { version = "0.4", optional = true } - -[[bin]] -name = "tzdb-fuzz-afl" -path = "bin/afl.rs" -test = false -doc = false -required-features = ["afl"] - -[[bin]] -name = "tzdb-fuzz-honggfuzz" -path = "bin/honggfuzz.rs" -test = false -doc = false -required-features = ["honggfuzz"] - -[[bin]] -name = "tzdb-fuzz-libfuzzer" -path = "bin/libfuzzer.rs" -test = false -doc = false -required-features = ["libfuzzer-sys"] - -[workspace] -members = ["."] diff --git a/fuzz/bin/afl.rs b/fuzz/bin/afl.rs deleted file mode 100644 index c7d612a..0000000 --- a/fuzz/bin/afl.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - afl::fuzz!(|name: &[u8]| { - let _ = tzdb_data::find_tz(name); - }); -} diff --git a/fuzz/bin/honggfuzz.rs b/fuzz/bin/honggfuzz.rs deleted file mode 100644 index c7edce1..0000000 --- a/fuzz/bin/honggfuzz.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - loop { - honggfuzz::fuzz!(|name: &[u8]| { - let _ = tzdb_data::find_tz(name); - }); - } -} diff --git a/fuzz/bin/libfuzzer.rs b/fuzz/bin/libfuzzer.rs deleted file mode 100644 index 002b273..0000000 --- a/fuzz/bin/libfuzzer.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![no_main] - -libfuzzer_sys::fuzz_target!(|name: &[u8]| { - let _ = tzdb_data::find_tz(name); -}); diff --git a/fuzz/run.sh b/fuzz/run.sh deleted file mode 100755 index 5b6156d..0000000 --- a/fuzz/run.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -exuo pipefail -cd "${0%/*}" -export RUSTC_BOOTSTRAP=1 -nice \ - cargo fuzz run tzdb-fuzz-libfuzzer --features="libfuzzer-sys" \ - --debug-assertions --release --jobs 16 -- \ - -max_total_time=180 -max_len=40 -timeout=5ms diff --git a/make-tzdb/Cargo.toml b/make-tzdb/Cargo.toml deleted file mode 100644 index 00a3a68..0000000 --- a/make-tzdb/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "make-tzdb" -version = "0.0.0" -edition = "2018" -authors = ["René Kijewski "] -repository = "https://github.com/Kijewski/tzdb" -description = "Tool to generate `tzdb/src/generated.rs`" -license = "MIT OR Apache-2.0" -publish = false - -[dependencies] -anyhow = "1" -convert_case = "0.6" -indexmap = "2" -itertools = "0.12" -ron = "0.8" -serde = { version = "1", features = ["derive"] } -subprocess = "0.2" -tz-rs = "0.6" -walkdir = "2" - -[workspace] -members = ["."] diff --git a/make-tzdb/LICENSE.md b/make-tzdb/LICENSE.md deleted file mode 120000 index 7eabdb1..0000000 --- a/make-tzdb/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.md \ No newline at end of file diff --git a/make-tzdb/README.md b/make-tzdb/README.md deleted file mode 100644 index 9ee94b8..0000000 --- a/make-tzdb/README.md +++ /dev/null @@ -1 +0,0 @@ -Tool to generate `tzdb/src/generated.rs`. Execute `make` in the project root. diff --git a/make-tzdb/generate_lookup_table.py b/make-tzdb/generate_lookup_table.py deleted file mode 100644 index a028926..0000000 --- a/make-tzdb/generate_lookup_table.py +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/env python3 - -from logging import basicConfig, INFO -from shutil import which -from subprocess import PIPE, Popen -from sys import stdin, stdout -from typing import Optional -from re import match - - -def convert(stdin, stdout): - gperf = which('gperf') - if not gperf: - raise Exception('No gperf') - gperf = Popen( - ['gperf', '--ignore-case', '--readonly-tables', '--struct-type', '--global-table', '-m', '1000'], - stdin=stdin, stdout=PIPE, - ) - with gperf: - stdin, _ = gperf.communicate(timeout=120) - match gperf.returncode: - case 0: - pass - case code: - raise Exception(f'gperf returned {code!r}') - stdin = str(stdin, 'UTF-8', 'strict') - - total_keywords: Optional[int] = None - min_word_length: Optional[int] = None - max_word_length: Optional[int] = None - min_hash_value: Optional[int] = None - max_hash_value: Optional[int] = None - duplicates: Optional[int] = None - maximum_key_range: Optional[int] = None - asso_values: list[int] = [] - hash_init: Optional[str] = [] - hash_switch_fst_idx: Optional[int] = None - hash_switch_idx: Optional[int|str] = None - hash_switch: dict[int|str, tuple[str, Optional[str]]] = {} - table: list[Optional[tuple[str, str]]] = [] - - state = 'start' - line: str - for line in stdin.splitlines(): - line = line.strip() - if not line: - continue - - match state: - case 'start': - match [s.strip() for s in line.split()]: - case ('#define', 'TOTAL_KEYWORDS', s): - total_keywords = int(s) - continue - case ('#define', 'MIN_WORD_LENGTH', s): - min_word_length = int(s) - continue - case ('#define', 'MAX_WORD_LENGTH', s): - max_word_length = int(s) - continue - case ('#define', 'MIN_HASH_VALUE', s): - min_hash_value = int(s) - continue - case ('#define', 'MAX_HASH_VALUE', s): - max_hash_value = int(s) - continue - - m = match(r'/\* maximum key range = (\d+), duplicates = (\d+) \*/', line) - if m: - maximum_key_range, duplicates = m.groups() - maximum_key_range = int(maximum_key_range) - duplicates = int(duplicates) - state = 'pre_asso_values' - - case 'pre_asso_values': - if 'asso_values[] =' in line: - state = 'asso_values' - - case 'asso_values': - if '}' in line: - state = 'pre_hash_switch' - elif '{' not in line: - s = (s.strip() for s in line.split(',')) - asso_values.extend(int(s) for s in s if s) - - case 'pre_hash_switch': - m = match(r'register unsigned int hval = ([^;]+);', line) - if m: - hash_init, = m.groups() - state = 'hash_switch' - continue - - case 'hash_switch': - if line == 'default:': - hash_switch_idx = 'default' - continue - - m = match(r'(return)?[^\)]+\)str\[([^\]]+)*\](?:([\+\-]\d+))?\];', line) - if m: - ret, idx, offs = m.groups() - if ret: - hash_switch_idx = 'finally' - assert hash_switch_idx is not None - hash_switch[hash_switch_idx] = idx, offs - hash_switch_idx = None - if ret: - state = 'pre_wordlist' - continue - - m = match(r'case (\d+):', line) - if m: - hash_switch_idx, = m.groups() - hash_switch_idx = int(hash_switch_idx) - if hash_switch_fst_idx is None: - hash_switch_fst_idx = hash_switch_idx - continue - - case 'pre_wordlist': - if line.endswith('wordlist[] ='): - state = 'wordlist' - - case 'wordlist': - if line == '};': - state = 'done' - break - - m = match(r'{"([^"]+)", "([^"]+)"}', line) - if m: - name, canon = m.groups() - table.append((name, canon)) - continue - - for _ in range(line.count('{""}')): - table.append(None) - - assert duplicates == 0 - - print('// GENERATED FILE', file=stdout) - print('// ALL CHANGES MADE IN THIS FOLDER WILL BE LOST!', file=stdout) - print(file=stdout) - print('use tz::TimeZoneRef;', file=stdout) - print(file=stdout) - print('use crate::eq_ignore_ascii_case;', file=stdout) - print('use super::raw_tzdata;', file=stdout) - print('use super::tzdata;', file=stdout) - print(file=stdout) - - print('#[derive(Clone, Copy)]', file=stdout) - print('#[repr(u16)]', file=stdout) - print('pub(crate) enum Index {', file=stdout) - idx = 0 - for entry in table: - match entry: - case (name, canon): - print(f' V{idx} = {idx + 1},', file=stdout) - idx += 1 - entry_count = idx - print('}', file=stdout) - print(file=stdout) - - print(f'const WORDLIST: [Option; {len(table)}] = [', file=stdout) - idx = 0 - for entry in table: - match entry: - case (name, canon): - print(f' Some(Index::V{idx}),', file=stdout) - idx += 1 - case _: - print(' None,', file=stdout) - print('];', file=stdout) - print(file=stdout) - - print(f'const NAMES: [&[u8]; {entry_count + 1}] = [', file=stdout) - print(f' b"",', file=stdout) - for entry in table: - match entry: - case (name, canon): - print(f' b"{name}",', file=stdout) - print('];', file=stdout) - print(file=stdout) - - print(f'pub(crate) const TIME_ZONES: [&TimeZoneRef<\'static>; {entry_count + 1}] = [', file=stdout) - for entry in table: - match entry: - case (name, canon): - print(f' &tzdata::{canon},', file=stdout) - break - for entry in table: - match entry: - case (name, canon): - print(f' &tzdata::{canon},', file=stdout) - print('];', file=stdout) - print(file=stdout) - - print(f'pub(crate) const RAW_TIME_ZONES: [&[u8]; {entry_count + 1}] = [', file=stdout) - for entry in table: - match entry: - case (name, canon): - print(f' raw_tzdata::{canon},', file=stdout) - break - for entry in table: - match entry: - case (name, canon): - print(f' raw_tzdata::{canon},', file=stdout) - print('];', file=stdout) - print(file=stdout) - - asso_values.pop() - print(f'const ASSO_VALUES: [u16; 257] = [', file=stdout) - for asso_value in asso_values: - print(f' {asso_value},', file=stdout) - print(f'{max_hash_value + 1}];', file=stdout) - print(file=stdout) - - print('pub(crate) const fn find_key(s: &[u8]) -> Option {', file=stdout) - print(' let len = s.len();', file=stdout) - print(f' if !matches!(len, {min_word_length}..={max_word_length}) {{', file=stdout) - print(' return None;', file=stdout) - print(' }', file=stdout) - print(file=stdout) - - def hash_add(idx, offs): - value = f's[{idx}] as usize' - if offs: - if offs.startswith('+'): - value = f'({value}).wrapping_add({offs[1:]})' - elif offs.startswith('-'): - value = f'({value}).wrapping_sub({offs[1:]})' - else: - raise Exception(f'offs? {offs!r}') - return f'key = key.wrapping_add(ASSO_VALUES[{value}] as usize);' - - match hash_init: - case 'len': - print(' let mut key: usize = len;', file=stdout) - case _: - raise Exception(f'hash_init == {hash_init!r} != "len"') - match hash_switch.get('finally'): - case (idx, offs): - print(f' {hash_add(idx, offs)}', file=stdout) - for item in reversed(hash_switch.items()): - match item: - case (int(key), (idx, offs)): - print(f' if len >= {key} {{', file=stdout) - print(f' {hash_add(idx, offs)}', file=stdout) - print(' }', file=stdout) - match hash_switch.get('default'): - case (idx, offs): - print(f' if len > {hash_switch_fst_idx} {{', file=stdout) - print(f' {hash_add(idx, offs)}', file=stdout) - print(' }', file=stdout) - print(file=stdout) - - print(f' if key > {max_hash_value} {{', file=stdout) - print(' return None;', file=stdout) - print(' }', file=stdout) - print(' let key = match WORDLIST[key] {', file=stdout) - print(' Some(key) => key,', file=stdout) - print(' None => return None,', file=stdout) - print(' };', file=stdout) - print(' if !eq_ignore_ascii_case(s, NAMES[key as u16 as usize]) {', file=stdout) - print(' return None;', file=stdout) - print(' }', file=stdout) - print(file=stdout) - print(' Some(key)', file=stdout) - print('}', file=stdout) - print(file=stdout) - - -if __name__ == '__main__': - basicConfig(level=INFO) - convert(stdin, stdout) diff --git a/make-tzdb/src/main.rs b/make-tzdb/src/main.rs deleted file mode 100644 index 9d1c219..0000000 --- a/make-tzdb/src/main.rs +++ /dev/null @@ -1,537 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright 2022 René Kijewski -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod parse; - -use std::cmp::Ordering; -use std::env::{args, var_os}; -use std::fmt::Write as _; -use std::fs::{create_dir_all, read, read_to_string, OpenOptions}; -use std::io::Write as _; -use std::path::{Path, PathBuf}; - -use anyhow::anyhow; -use convert_case::{Case, Casing}; -use indexmap::IndexMap; -use itertools::Itertools; -use subprocess::{Popen, PopenConfig, Redirection}; -use tz::TimeZone; -use walkdir::WalkDir; - -const GENERATED_FILE: &str = r#"// GENERATED FILE -// ALL CHANGES MADE IN THIS FOLDER WILL BE LOST! - -"#; - -#[derive(Debug, Clone)] -struct TzName { - /// "Europe/Belfast" - canon: String, - /// "Europe/Guernsey" - full: String, - /// Some("europe") // Snake - major: Option, - /// "GUERNSEY" // UpperSnake - minor: String, -} - -impl PartialEq for TzName { - fn eq(&self, other: &Self) -> bool { - self.cmp(other) == Ordering::Equal - } -} - -impl Eq for TzName {} - -impl PartialOrd for TzName { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for TzName { - fn cmp(&self, other: &Self) -> Ordering { - match self.major.is_some().cmp(&other.major.is_some()) { - Ordering::Equal => match self.major.cmp(&other.major) { - Ordering::Equal => self.minor.cmp(&other.minor), - r => r, - }, - r => r, - } - } -} - -impl TzName { - fn new(base_path: &Path, path: &Path) -> Option { - let mut path = path.iter().fuse(); - for _ in base_path { - path.next(); - } - let a = path.next().and_then(|o| o.to_str()); - let b = path.next().and_then(|o| o.to_str()); - let c = path.next().and_then(|o| o.to_str()); - match [a, b, c] { - [Some("etc"), ..] => None, - [Some(a), None, None] => Some(Self { - canon: "".to_owned(), - full: a.to_owned(), - major: None, - minor: prepare_casing(a).to_case(Case::UpperSnake), - }), - [Some(a), Some(b), None] => Some(Self { - canon: "".to_owned(), - full: format!("{a}/{b}"), - major: Some(prepare_casing(a).to_case(Case::Snake)), - minor: prepare_casing(b).to_case(Case::UpperSnake), - }), - [Some(a), Some(b), Some(c)] => Some(Self { - canon: "".to_owned(), - full: format!("{a}/{b}/{c}"), - major: Some(prepare_casing(&format!("{a}/{b}")).to_case(Case::Snake)), - minor: prepare_casing(c).to_case(Case::UpperSnake), - }), - _ => None, - } - } -} - -pub fn main() -> anyhow::Result<()> { - let mut args = args().into_iter().fuse(); - let _ = args.next(); // exe path - - let target_dir = PathBuf::from(args.next().unwrap_or_else(|| "tzdb/generated".to_owned())); - create_dir_all(&target_dir)?; - - let entries_by_bytes = collect_entries_by_bytes(&mut args)?; - let entries_by_major = collect_entries_by_major(&entries_by_bytes)?; - - gen_mod(&mut args, &target_dir)?; - // generate lookup table - gen_lookup_table(&entries_by_bytes, &target_dir)?; - // generate exhaustive by-name test - gen_test_all_names(&entries_by_bytes, &target_dir)?; - // all known time zones as reference to (raw_)tzdata - gen_time_zones(&entries_by_major, &target_dir)?; - // list of known time zone names - gen_tz_names(entries_by_major, &target_dir)?; - // parsed time zone data by canonical name - gen_tzdata(&entries_by_bytes, &target_dir)?; - // raw time zone data by canonical name - gen_raw_tzdata(entries_by_bytes, &target_dir)?; - - Ok(()) -} - -fn gen_mod(args: &mut impl Iterator, target_dir: &Path) -> Result<(), anyhow::Error> { - let hash_file = args.next().unwrap_or_else(|| "tzdb.tar.lz.sha".to_owned()); - let hash_file = read_to_string(&hash_file)?; - let (hash, version) = hash_file - .trim() - .split_once(" ") - .ok_or_else(|| anyhow!("Hash file {hash_file:?} malformed."))?; - let version = version.rsplit_once('/').unwrap_or(("", version)).1; - let version = version.split_once('.').unwrap_or((version, "")).0; - let version = version.rsplit_once('-').unwrap_or(("", version)).1; - - let r = format!( - r#"{GENERATED_FILE} -// MIT No Attribution -// -// Copyright 2022-2024 René Kijewski -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -// associated documentation files (the "Software"), to deal in the Software without restriction, -// including without limitation the rights to use, copy, modify, merge, publish, distribute, -// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#![allow(unknown_lints)] -#![allow(clippy::pedantic)] - -#[cfg(all(test, not(miri)))] -mod test_all_names; - -pub(crate) mod by_name; -mod raw_tzdata; -mod tzdata; -mod tz_names; - -/// All defined time zones statically accessible -pub mod time_zone; - -/// The version of the source Time Zone Database -pub const VERSION: &str = {version:?}; - -/// The SHA512 hash of the source Time Zone Database (using the "Complete Distribution") -pub const VERSION_HASH: &str = {hash:?}; - -#[allow(unreachable_pub)] // false positive -pub use self::tz_names::TZ_NAMES; -"# - ); - write_string(r, target_dir.join("mod.rs"))?; - Ok(()) -} - -fn collect_entries_by_major(entries_by_bytes: &IndexMap, Vec>) -> anyhow::Result, Vec<&TzName>)>> { - let entries_by_major = entries_by_bytes - .values() - .flat_map(|entries| entries.iter()) - .map(|tz_entry| (tz_entry.major.as_deref(), tz_entry)) - .sorted_by(|(l, _), (r, _)| match (l, r) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Greater, - (Some(_), None) => Ordering::Less, - (Some(l), Some(r)) => l.cmp(r), - }) - .group_by(|(k, _)| k.map(|s| s.to_owned())) - .into_iter() - .map(|(major, entries)| { - let mut entries = entries.map(|(_, e)| e).collect_vec(); - entries.sort(); - (major, entries) - }) - .collect_vec(); - - let max_len = entries_by_major - .iter() - .flat_map(|(_, entries)| entries.iter()) - .map(|entry| entry.full.len()) - .max() - .unwrap(); - assert!(max_len <= 32); - - Ok(entries_by_major) -} - -fn collect_entries_by_bytes(args: &mut impl Iterator) -> anyhow::Result, Vec>> { - let mut base_path = args - .next() - .unwrap_or_else(|| "/usr/share/zoneinfo/posix/".to_owned()); - while base_path.ends_with('/') { - if base_path.len() == 1 { - break; - } - base_path.pop(); - } - if base_path.is_empty() { - base_path.push('.'); - } - if !base_path.ends_with('/') { - base_path.push('/'); - } - let base_path = Path::new(&base_path).canonicalize()?; - - let mut entries_by_bytes: IndexMap, Vec> = IndexMap::new(); - let walkdir = WalkDir::new(&base_path) - .min_depth(1) - .contents_first(true) - .follow_links(true); - for entry in walkdir { - let entry = entry?; - if !entry.file_type().is_file() { - continue; - } - let path = base_path.join(entry.path()); - let Ok(bytes) = read(&path) else { - continue; - }; - if !TimeZone::from_tz_data(&bytes).is_ok() { - continue; - } - let Some(tz_entry) = TzName::new(&base_path, &path) else { - continue; - }; - entries_by_bytes.entry(bytes).or_default().push(tz_entry); - } - for entries in entries_by_bytes.values_mut() { - entries.sort(); - let canon = prepare_casing(&entries.first().unwrap().full).to_case(Case::UpperSnake); - for entry in entries { - entry.canon = canon.clone(); - } - } - entries_by_bytes.sort_by(|_, l, _, r| l[0].canon.cmp(&r[0].canon)); - - Ok(entries_by_bytes) -} - -fn gen_raw_tzdata(entries_by_bytes: IndexMap, Vec>, target_dir: &Path) -> anyhow::Result<()> { - let mut r = GENERATED_FILE.to_owned(); - writeln!(r, "#![allow(unknown_lints)]")?; - writeln!(r, "#![allow(clippy::octal_escapes)]")?; - writeln!(r)?; - for (bytes, entries) in &entries_by_bytes { - writeln!( - r, - "pub(crate) const {}: &[u8] = b\"{}\";", - &entries[0].canon, - hex_encode(bytes)?, - )?; - } - write_string(r, target_dir.join("raw_tzdata.rs"))?; - Ok(()) -} - -fn gen_tzdata(entries_by_bytes: &IndexMap, Vec>, target_dir: &Path) -> anyhow::Result<()> { - let mut r = GENERATED_FILE.to_owned(); - writeln!(r, "use tz::timezone::RuleDay;")?; - writeln!(r, "use tz::timezone::TransitionRule;")?; - writeln!(r, "use tz::TimeZoneRef;")?; - writeln!(r)?; - writeln!(r, "use crate::new_alternate_time;")?; - writeln!(r, "use crate::new_local_time_type;")?; - writeln!(r, "use crate::new_month_week_day;")?; - writeln!(r, "use crate::new_time_zone_ref;")?; - writeln!(r, "use crate::new_transition;")?; - writeln!(r)?; - for (bytes, entries) in entries_by_bytes { - writeln!(r)?; - writeln!( - r, - "pub(crate) const {}: TimeZoneRef<'static> = {};", - &entries[0].canon, - tz_convert(bytes), - )?; - } - write_string(r, target_dir.join("tzdata.rs"))?; - Ok(()) -} - -fn gen_tz_names(entries_by_major: Vec<(Option, Vec<&TzName>)>, target_dir: &Path) -> anyhow::Result<()> { - let mut time_zones_list = entries_by_major - .iter() - .flat_map(|(_, entries)| entries.iter()) - .map(|entry| entry.full.as_str()) - .collect_vec(); - time_zones_list.sort_by_key(|l| l.to_ascii_lowercase()); - - let mut r = GENERATED_FILE.to_owned(); - writeln!(r, "/// A list of all known time zones")?; - writeln!(r, "pub const TZ_NAMES: &[&str] = &[",)?; - for name in time_zones_list { - writeln!(r, " {:?},", name)?; - } - writeln!(r, "];")?; - write_string(r, target_dir.join("tz_names.rs"))?; - Ok(()) -} - -fn gen_time_zones(entries_by_major: &Vec<(Option, Vec<&TzName>)>, target_dir: &Path) -> anyhow::Result<()> { - let mut r = GENERATED_FILE.to_owned(); - for (folder, entries) in entries_by_major { - if let Some(folder) = folder { - let doc = entries[0].full.as_str(); - let doc = match doc.rsplit_once('/') { - Some((doc, _)) => doc, - None => doc, - }; - writeln!(r, "/// {doc}")?; - writeln!(r, "pub mod {folder} {{")?; - } - for entry in entries { - writeln!(r, " /// Time zone data for `{:?}`", entry.full)?; - writeln!( - r, - "pub const {}: tz::TimeZoneRef<'static> = crate::generated::tzdata::{};", - entry.minor, entry.canon, - )?; - } - - for entry in entries { - writeln!( - r, - " /// Raw, unparsed time zone data for `{:?}`", - entry.full - )?; - writeln!( - r, - "pub const RAW_{}: &[u8] = crate::generated::raw_tzdata::{};", - entry.minor, entry.canon, - )?; - } - - if folder.is_some() { - writeln!(r, "}}")?; - } - } - write_string(r, target_dir.join("time_zone.rs"))?; - Ok(()) -} - -fn gen_test_all_names(entries_by_bytes: &IndexMap, Vec>, target_dir: &Path) -> anyhow::Result<()> { - let mut r = GENERATED_FILE.to_owned(); - writeln!(r, "#[test]")?; - writeln!(r, "fn test() {{")?; - writeln!(r, " use crate::{{find_raw, find_tz, time_zone}};")?; - writeln!(r)?; - writeln!( - r, - " const TIME_ZONES: &[(&tz::TimeZoneRef<'static>, &[u8], &[&[u8]])] = &[" - )?; - for entries in entries_by_bytes.values() { - for entry in entries { - let name = match entry.major { - Some(ref major) => format!("{}::{}", major, &entry.minor), - None => format!("{}", &entry.minor), - }; - let raw_name = match entry.major { - Some(ref major) => format!("{}::RAW_{}", major, &entry.minor), - None => format!("RAW_{}", &entry.minor), - }; - - writeln!(r, " (")?; - writeln!(r, " &time_zone::{name},")?; - writeln!(r, " time_zone::{raw_name},")?; - writeln!(r, " &[")?; - for f in [ - |s: &str| s.to_owned(), - |s: &str| s.to_ascii_lowercase(), - |s: &str| s.to_ascii_uppercase(), - |s: &str| { - s.chars() - .map(|c| match c { - 'A'..='Z' => c.to_ascii_lowercase(), - 'a'..='z' => c.to_ascii_uppercase(), - c => c, - }) - .collect() - }, - |s: &str| { - s.chars() - .enumerate() - .map(|(i, c)| { - if i % 2 == 0 { - c.to_ascii_uppercase() - } else { - c.to_ascii_lowercase() - } - }) - .collect() - }, - |s: &str| { - s.chars() - .enumerate() - .map(|(i, c)| { - if i % 2 == 1 { - c.to_ascii_uppercase() - } else { - c.to_ascii_lowercase() - } - }) - .collect() - }, - ] { - writeln!(r, " b{:?},", f(&entry.full))?; - } - writeln!(r, " ],")?; - writeln!(r, " ),")?; - } - } - writeln!(r, " ];")?; - writeln!(r)?; - writeln!( - r, - " for &(tz, raw, names) in TIME_ZONES {{ for name in names {{", - )?; - writeln!( - r, - " assert_eq!(Some(tz), find_tz(name), \"find_tz({{:?}})\", name);", - )?; - writeln!( - r, - " assert_eq!(Some(raw), find_raw(name), \"find_raw({{:?}})\", name);", - )?; - writeln!(r, " }} }}")?; - writeln!(r, "}}")?; - write_string(r, target_dir.join("test_all_names.rs"))?; - Ok(()) -} - -fn gen_lookup_table(entries_by_bytes: &IndexMap, Vec>, target_dir: &Path) -> anyhow::Result<()> { - let mut keywords = String::new(); - writeln!( - keywords, - "struct keyword {{ const char* name; const char* canon; }}" - )?; - writeln!(keywords, "%%")?; - for entries in entries_by_bytes.values() { - for entry in entries { - writeln!(keywords, "{:?}, {:?}", entry.full, entry.canon)?; - } - } - writeln!(keywords, "%%")?; - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(target_dir.join("by_name.rs"))?; - let mut gperf = Popen::create( - &["/usr/bin/env", "python3", "generate_lookup_table.py"], - PopenConfig { - stdin: Redirection::Pipe, - stdout: Redirection::File(file), - cwd: Some(var_os("CARGO_MANIFEST_DIR").ok_or(anyhow!("!CARGO_MANIFEST_DIR"))?), - ..PopenConfig::default() - }, - )?; - gperf.communicate(Some(&keywords))?; - Ok(()) -} - -fn write_string(mut s: String, f: PathBuf) -> std::io::Result<()> { - if !s.ends_with("\n") { - s.push('\n'); - } - std::fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(f)? - .write_all(s.as_bytes()) -} - -fn prepare_casing(name: &str) -> String { - name.replace('/', " ") - .replace("GMT+", " GMT plus ") - .replace("GMT-", " GMT minus ") -} - -fn tz_convert(bytes: &[u8]) -> crate::parse::TimeZone { - let tz = TimeZone::from_tz_data(bytes).unwrap(); - let s = format!("{:?}", tz); - let s = s.replace('{', "("); - let s = s.replace('}', ")"); - ron::from_str::(&s).unwrap() -} - -fn hex_encode(v: &[u8]) -> Result { - let mut s = String::with_capacity(v.len() * 4); - for &b in v { - match b { - 0 => s.push_str("\\0"), - c @ (1..=31 | 127..=255 | b'"' | b'\\') => write!(s, "\\x{c:02x}")?, - c @ 32..=126 => s.push(c as char), - } - } - Ok(s) -} diff --git a/make-tzdb/src/parse.rs b/make-tzdb/src/parse.rs deleted file mode 100644 index e5117eb..0000000 --- a/make-tzdb/src/parse.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::fmt; - -use serde::Deserialize; - -#[derive(Debug, Deserialize)] -pub(crate) enum TimeZone { - TimeZone { - transitions: Vec, - local_time_types: Vec, - leap_seconds: Vec, - extra_rule: Option, - }, -} - -#[derive(Debug, Deserialize)] -pub(crate) enum Transition { - Transition { - unix_leap_time: i64, - local_time_type_index: usize, - }, -} - -#[derive(Debug, Deserialize)] -pub(crate) enum LeapSecond { - LeapSecond { - unix_leap_time: i64, - correction: i32, - }, -} - -#[derive(Debug, Deserialize)] -pub(crate) enum TransitionRule { - Fixed(LocalTimeType), - Alternate(AlternateTime), -} - -#[derive(Debug, Deserialize)] -pub(crate) enum AlternateTime { - AlternateTime { - std: LocalTimeType, - dst: LocalTimeType, - dst_start: RuleDay, - dst_start_time: i32, - dst_end: RuleDay, - dst_end_time: i32, - }, -} - -#[derive(Debug, Deserialize)] -pub(crate) enum LocalTimeType { - LocalTimeType { - ut_offset: i32, - is_dst: bool, - time_zone_designation: Option, - }, -} - -#[derive(Debug, Deserialize)] -pub(crate) enum RuleDay { - Julian1WithoutLeap(Julian1WithoutLeap), - Julian0WithLeap(Julian0WithLeap), - MonthWeekDay(MonthWeekDay), -} - -#[derive(Debug, Deserialize)] -pub(crate) enum Julian1WithoutLeap { - Julian1WithoutLeap(u16), -} - -#[derive(Debug, Deserialize)] -pub(crate) enum Julian0WithLeap { - Julian0WithLeap(u16), -} - -#[derive(Debug, Deserialize)] -pub(crate) enum MonthWeekDay { - MonthWeekDay { month: u8, week: u8, week_day: u8 }, -} - -impl fmt::Display for Transition { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Transition::Transition { - unix_leap_time, - local_time_type_index, - } = &self; - writeln!( - f, - "new_transition({}, {})", - unix_leap_time, local_time_type_index - )?; - Ok(()) - } -} - -impl fmt::Display for LocalTimeType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let LocalTimeType::LocalTimeType { - ut_offset, - is_dst, - time_zone_designation, - } = self; - let time_zone_designation = time_zone_designation.as_deref().map(DisplayTzd); - writeln!( - f, - "new_local_time_type({}, {}, {})", - ut_offset, - is_dst, - DisplayOption(time_zone_designation.as_ref()), - )?; - Ok(()) - } -} - -impl fmt::Display for LeapSecond { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let LeapSecond::LeapSecond { - unix_leap_time, - correction, - } = self; - writeln!(f, "new_leap_second({}, {})", unix_leap_time, correction)?; - Ok(()) - } -} - -impl fmt::Display for TransitionRule { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TransitionRule::Fixed(t) => writeln!(f, "TransitionRule::Fixed({})", t)?, - TransitionRule::Alternate(t) => { - writeln!(f, "TransitionRule::Alternate({})", t)?; - }, - } - Ok(()) - } -} - -impl fmt::Display for AlternateTime { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let AlternateTime::AlternateTime { - std, - dst, - dst_start, - dst_start_time, - dst_end, - dst_end_time, - } = self; - writeln!( - f, - "new_alternate_time({}, {}, {}, {}, {}, {})", - std, dst, dst_start, dst_start_time, dst_end, dst_end_time, - ) - } -} - -impl fmt::Display for MonthWeekDay { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let MonthWeekDay::MonthWeekDay { - month, - week, - week_day, - } = self; - writeln!(f, "new_month_week_day({}, {}, {})", month, week, week_day) - } -} - -impl fmt::Display for Julian0WithLeap { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Julian0WithLeap::Julian0WithLeap(t) = self; - writeln!(f, "new_julian0_with_leap({})", t) - } -} - -impl fmt::Display for Julian1WithoutLeap { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Julian1WithoutLeap::Julian1WithoutLeap(t) = self; - writeln!(f, "new_julian1_without_leap({})", t) - } -} - -impl fmt::Display for RuleDay { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RuleDay::Julian0WithLeap(t) => writeln!(f, "RuleDay::Julian0WithLeap({})", t)?, - RuleDay::Julian1WithoutLeap(t) => writeln!(f, "RuleDay::Julian1WithoutLeap({})", t)?, - RuleDay::MonthWeekDay(t) => { - writeln!(f, "RuleDay::MonthWeekDay({})", t)?; - }, - } - Ok(()) - } -} - -pub(crate) struct DisplayVec<'a, T>(pub(crate) &'a [T]); - -impl<'a, T: fmt::Display> fmt::Display for DisplayVec<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "[")?; - for elem in self.0 { - writeln!(f, " {},", elem)?; - } - writeln!(f, "]") - } -} - -pub(crate) struct DisplayOption<'a, T>(Option<&'a T>); - -impl<'a, T: fmt::Display> fmt::Display for DisplayOption<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - Some(v) => writeln!(f, "Some({})", v), - None => writeln!(f, "None"), - } - } -} - -struct DisplayTzd<'a>(&'a str); - -impl fmt::Display for DisplayTzd<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "b{:?}", &self.0) - } -} - -impl fmt::Display for TimeZone { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let TimeZone::TimeZone { - transitions, - local_time_types, - leap_seconds, - extra_rule, - } = self; - writeln!( - f, - "new_time_zone_ref(&{}, &{}, &{}, &{})", - DisplayVec(transitions), - DisplayVec(local_time_types), - DisplayVec(leap_seconds), - DisplayOption(extra_rule.as_ref()), - ) - } -} diff --git a/rustfmt.toml b/rustfmt.toml index 8ba7056..048159e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,6 @@ combine_control_expr = false -edition = "2021" +edition = "2018" +style_edition = "2024" format_macro_matchers = true match_block_trailing_comma = true imports_granularity = "Module" @@ -11,4 +12,3 @@ group_imports = "StdExternalCrate" unstable_features = true use_field_init_shorthand = true use_try_shorthand = true -version = "Two" diff --git a/src/lib.rs b/src/lib.rs index bb0c1c9..f52d2e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,7 @@ pub mod changelog; pub mod now; #[cfg_attr(docsrs, doc(no_inline))] -pub use tzdb_data::{time_zone, TZ_NAMES, VERSION, VERSION_HASH}; +pub use tzdb_data::{TZ_NAMES, VERSION, VERSION_HASH, time_zone}; /// Find a time zone by name, e.g. `"Europe/Berlin"` (case-insensitive) /// diff --git a/tzdb.tar.lz.sha b/tzdb.tar.lz.sha deleted file mode 100644 index 482ba74..0000000 --- a/tzdb.tar.lz.sha +++ /dev/null @@ -1 +0,0 @@ -72446e5cf445515512437c8deaae3063b093aab9620d6441cafaa9b3b71603c857f7ba53557579595788bbc901cd6142404b4db6b0e9f2b23d57b2b3cbc837a8 tmp/tzdb-2024b.tar.lz diff --git a/tzdb_data/Cargo.toml b/tzdb_data/Cargo.toml index 79f3ccb..31ba4b1 100644 --- a/tzdb_data/Cargo.toml +++ b/tzdb_data/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tzdb_data" -version = "0.1.3" +version = "0.1.4" edition = "2018" authors = ["René Kijewski "] repository = "https://github.com/Kijewski/tzdb" @@ -16,4 +16,4 @@ tz-rs = { version = "^0.6.14", default-features = false, features = ["const"] } [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--generate-link-to-definition", "--cfg=docsrs"] diff --git a/tzdb_data/src/generated b/tzdb_data/src/generated index e04be62..9bfec30 160000 --- a/tzdb_data/src/generated +++ b/tzdb_data/src/generated @@ -1 +1 @@ -Subproject commit e04be62b599f2f417327f58680a30ee1c3b370a4 +Subproject commit 9bfec307454bb99564b06eda9619c8e8739149b0 diff --git a/tzdb_data/src/lib.rs b/tzdb_data/src/lib.rs index d442447..9354635 100644 --- a/tzdb_data/src/lib.rs +++ b/tzdb_data/src/lib.rs @@ -50,7 +50,7 @@ mod generated; #[cfg_attr(docsrs, doc(inline))] -pub use crate::generated::{time_zone, TZ_NAMES, VERSION, VERSION_HASH}; +pub use crate::generated::{TZ_NAMES, VERSION, VERSION_HASH, time_zone}; /// Find a time zone by name, e.g. `b"Europe/Berlin"` (case-insensitive) /// @@ -91,6 +91,7 @@ pub const fn find_raw(s: &[u8]) -> Option<&'static [u8]> { } #[allow(clippy::out_of_bounds_indexing)] +#[allow(clippy::ref_option)] #[must_use] const fn new_time_zone_ref( transitions: &'static [tz::timezone::Transition],