jiff-static-0.2.16/.cargo_vcs_info.json0000644000000001600000000000100133220ustar { "git": { "sha1": "2ef6045d57f530680e1b79a663c0490459b74c6b" }, "path_in_vcs": "crates/jiff-static" }jiff-static-0.2.16/COPYING000064400000000000000000000001761046102023000131540ustar 00000000000000This project is dual-licensed under the Unlicense and MIT licenses. You may use this code under the terms of either license. jiff-static-0.2.16/Cargo.lock0000644000000024550000000000100113060ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "jiff-static" version = "0.2.16" dependencies = [ "jiff-tzdb", "proc-macro2", "quote", "syn", ] [[package]] name = "jiff-tzdb" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" jiff-static-0.2.16/Cargo.toml0000644000000030340000000000100113230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "jiff-static" version = "0.2.16" authors = ["Andrew Gallant "] build = false include = [ "/src/**/*.rs", "COPYING", "LICENSE-MIT", "UNLICENSE", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Create static TimeZone values for Jiff (useful in core-only environments)." homepage = "https://github.com/BurntSushi/jiff/tree/master/crates/jiff-static" documentation = "https://docs.rs/jiff-tzdb" readme = "README.md" keywords = [ "date", "time", "static", "zone", "iana", ] categories = ["date-and-time"] license = "Unlicense OR MIT" repository = "https://github.com/BurntSushi/jiff" [features] default = [] perf-inline = [] tz-fat = [] tzdb = ["dep:jiff-tzdb"] [lib] name = "jiff_static" path = "src/lib.rs" bench = false proc-macro = true [dependencies.jiff-tzdb] version = "0.1.4" optional = true [dependencies.proc-macro2] version = "1.0.93" [dependencies.quote] version = "1.0.38" [dependencies.syn] version = "2.0.98" jiff-static-0.2.16/Cargo.toml.orig000064400000000000000000000026031046102023000150050ustar 00000000000000[package] name = "jiff-static" version = "0.2.16" #:version authors = ["Andrew Gallant "] license = "Unlicense OR MIT" homepage = "https://github.com/BurntSushi/jiff/tree/master/crates/jiff-static" repository = "https://github.com/BurntSushi/jiff" documentation = "https://docs.rs/jiff-tzdb" description = "Create static TimeZone values for Jiff (useful in core-only environments)." categories = ["date-and-time"] keywords = ["date", "time", "static", "zone", "iana"] workspace = "../.." edition = "2021" rust-version = "1.70" include = ["/src/**/*.rs", "COPYING", "LICENSE-MIT", "UNLICENSE"] [lib] name = "jiff_static" bench = false proc-macro = true [features] default = [] # This forces the jiff-tzdb crate to be included and makes the `get` proc macro # available (which pulls from the bundled tzdb). tzdb = ["dep:jiff-tzdb"] # This fattens up the TZif data to contain more explicit transitions. This may # improve the performance of time zone lookups. # # See the `tz-fat` feature in this repository's root `Cargo.toml` for more # context. tz-fat = [] # Equivalent to the eponymous feature in `jiff` proper. Except it isn't # enabled by default here, since we don't really care about that level of # perf at compile time. perf-inline = [] [dependencies] jiff-tzdb = { version = "0.1.4", path = "../jiff-tzdb", optional = true } proc-macro2 = "1.0.93" quote = "1.0.38" syn = "2.0.98" jiff-static-0.2.16/LICENSE-MIT000064400000000000000000000020711046102023000135510ustar 00000000000000The MIT License (MIT) Copyright (c) 2015 Andrew Gallant 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, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. jiff-static-0.2.16/README.md000064400000000000000000000014631046102023000134000ustar 00000000000000jiff-static =========== This is an optional dependency of `jiff` that embeds time zone data into your binary via procedural macros. It unlocks the use case of creating a `TimeZone` value in core-only environments without dynamic memory allocation. Users should generally not depend on this directly, but instead use it through Jiff. Namely, all of the procedural macros defined in this crate are re-exported through Jiff's public API. For example, one can enable the `static` or `static-tz` crate features in `jiff` to get `jiff::tz::get!` and `jiff::tz::include!`. **WARNING**: The `src/shared` directory in this crate is copied from the `../src/shared` directory. This copy is managed by `jiff-cli generate shared`. See the comments in the code for why this is done. ### Documentation https://docs.rs/jiff-static jiff-static-0.2.16/UNLICENSE000064400000000000000000000022731046102023000133710ustar 00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 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 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. For more information, please refer to jiff-static-0.2.16/src/lib.rs000064400000000000000000000353271046102023000140320ustar 00000000000000/*! This crate provides macros for defining `static` data structures for Jiff. The macros in this crate are re-exported in the [`jiff::tz`] sub-module. Users should _not_ depend on this crate directly or import from it. Instead, enable the `static` or `static-tz` features of Jiff and use the re-exports in `jiff::tz`. At present, the macros in this crate are limited to creating `TimeZone` in a `const` context. This works by reading TZif data (e.g., from `/usr/share/zoneinfo/America/New_York` or from [`jiff-tzdb`]) at compile time and generating Rust source code that builds a `TimeZone`. # Documentation The macros defined in this crate are documented on their corresponding re-exports in Jiff: * `get` is documented at [`jiff::tz::get`]. * `include` is documented at [`jiff::tz::include`]. # Compatibility The APIs required to build a `TimeZone` in a `const` context are exposed by Jiff but not part of Jiff's public API for the purposes of semver (and do not appear in `rustdoc`). The only guarantee provided by `jiff` and `jiff-static` is that there is exactly one version of `jiff` that `jiff-static` works with. Conventionally, this is indicated by the exact same version string. That is, `jiff-static 0.2.2` is only guaranteed to work with `jiff 0.2.2`. This compatibility constraint is managed by Jiff, so that you should never need to worry about it. In particular, users should never directly depend on this crate. Everything should be managed through the `jiff` crate. [`jiff-tzdb`]: https://docs.rs/jiff-tzdb [`jiff::tz`]: https://docs.rs/jiff/0.2/jiff/tz/index.html [`jiff::tz::get`]: https://docs.rs/jiff/0.2/jiff/tz/macro.get.html [`jiff::tz::include`]: https://docs.rs/jiff/0.2/jiff/tz/macro.include.html */ extern crate alloc; extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use self::shared::{ util::array_str::Abbreviation, PosixDay, PosixDayTime, PosixDst, PosixOffset, PosixRule, PosixTime, PosixTimeZone, TzifDateTime, TzifFixed, TzifIndicator, TzifLocalTimeType, TzifOwned, TzifTransitionInfo, TzifTransitionKind, TzifTransitionsOwned, }; /// A bundle of code copied from `src/shared`. /// /// The main thing we use in here is the parsing routine for TZif data and /// shared data types for representing TZif data. /// /// We also squash dead code warnings. This is somewhat precarious since /// ideally we wouldn't compile what we don't need. But in practice, it's /// annoying to get rid of everything we don't need in this context, and it /// should be pretty small anyway. #[allow(dead_code)] mod shared; // Public API docs are in Jiff. #[proc_macro] pub fn include(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as Include); proc_macro::TokenStream::from(input.quote()) } // Public API docs are in Jiff. #[cfg(feature = "tzdb")] #[proc_macro] pub fn get(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as Get); proc_macro::TokenStream::from(input.quote()) } /// The entry point for the `include!` macro. #[derive(Debug)] struct Include { tzif: TzifOwned, } impl Include { fn from_path_only(path: &str) -> Result { const NEEDLE: &str = "zoneinfo/"; let Some(zoneinfo) = path.rfind(NEEDLE) else { return Err(format!( "could not extract IANA time zone identifier from \ file path `{path}` \ (could not find `zoneinfo` in path), \ please provide IANA time zone identifier as second \ parameter", )); }; let idstart = zoneinfo.saturating_add(NEEDLE.len()); let id = &path[idstart..]; Include::from_path_with_id(id, path) } fn from_path_with_id(id: &str, path: &str) -> Result { let id = id.to_string(); let data = std::fs::read(path) .map_err(|e| format!("failed to read {path}: {e}"))?; let tzif = TzifOwned::parse(Some(id.clone()), &data).map_err(|e| { format!("failed to parse TZif data from {path}: {e}") })?; Ok(Include { tzif }) } fn quote(&self) -> proc_macro2::TokenStream { self.tzif.quote() } } impl syn::parse::Parse for Include { fn parse(input: syn::parse::ParseStream) -> syn::Result { let lit1 = input.parse::()?.value(); if !input.lookahead1().peek(syn::Token![,]) { return Ok( Include::from_path_only(&lit1).map_err(|e| input.error(e))? ); } input.parse::()?; if input.is_empty() { return Ok( Include::from_path_only(&lit1).map_err(|e| input.error(e))? ); } let lit2 = input.parse::()?.value(); // Permit optional trailing comma. if input.lookahead1().peek(syn::Token![,]) { input.parse::()?; } Ok(Include::from_path_with_id(&lit2, &lit1) .map_err(|e| input.error(e))?) } } /// The entry point for the `get!` macro. #[cfg(feature = "tzdb")] #[derive(Debug)] struct Get { tzif: TzifOwned, } #[cfg(feature = "tzdb")] impl Get { fn from_id(id: &str) -> Result { let (id, data) = jiff_tzdb::get(id).ok_or_else(|| { format!("could not find time zone `{id}` in bundled tzdb") })?; let id = id.to_string(); let tzif = TzifOwned::parse(Some(id.clone()), &data).map_err(|e| { format!("failed to parse TZif data from bundled `{id}`: {e}") })?; Ok(Get { tzif }) } fn quote(&self) -> proc_macro2::TokenStream { self.tzif.quote() } } #[cfg(feature = "tzdb")] impl syn::parse::Parse for Get { fn parse(input: syn::parse::ParseStream) -> syn::Result { let lit1 = input.parse::()?.value(); if input.lookahead1().peek(syn::Token![,]) { input.parse::()?; } Ok(Get::from_id(&lit1).map_err(|e| input.error(e))?) } } // Everything below at this point is quasi-quoting the `shared` data type // values into `static` data structures as Rust source code. impl TzifOwned { fn quote(&self) -> proc_macro2::TokenStream { let TzifOwned { ref fixed, ref types, ref transitions } = *self; let fixed = fixed.quote(); let types = types.iter().map(TzifLocalTimeType::quote); let transitions = transitions.quote(); quote! { { static TZ: jiff::tz::TimeZone = jiff::tz::TimeZone::__internal_from_tzif( &jiff::shared::TzifStatic { fixed: #fixed, types: &[#(#types),*], transitions: #transitions, }.into_jiff() ); // SAFETY: Since we are guaranteed that the `TimeZone` is // constructed above as a static TZif time zone, it follows // that it is safe to memcpy's its internal representation. // // NOTE: We arrange things this way so that `jiff::tz::get!` // can be used "by value" in most contexts. Basically, we // "pin" the time zone to a static so that it has a guaranteed // static lifetime. Otherwise, since `TimeZone` has a `Drop` // impl, it's easy to run afoul of this and have it be dropped // earlier than you like. Since this particular variant of // `TimeZone` can always be memcpy'd internally, we just do // this dance here to save the user from having to write out // their own `static`. // // NOTE: It would be nice if we could make this `copy` routine // safe, or at least panic if it's misused. But to do that, you // need to know the time zone variant. And to know the time // zone variant, you need to "look" at the tag in the pointer. // And looking at the address of a pointer in a `const` context // is precarious. unsafe { TZ.copy() } } } } } impl TzifFixed { fn quote(&self) -> proc_macro2::TokenStream { let TzifFixed { ref name, version, checksum, ref designations, ref posix_tz, } = *self; let name = name.as_ref().unwrap(); let posix_tz = posix_tz .as_ref() .map(|tz| { let tz = tz.quote(); quote!(Some(#tz)) }) .unwrap_or_else(|| quote!(None)); quote! { jiff::shared::TzifFixed { name: Some(#name), version: #version, checksum: #checksum, designations: #designations, posix_tz: #posix_tz, } } } } impl TzifTransitionsOwned { fn quote(&self) -> proc_macro2::TokenStream { let TzifTransitionsOwned { ref timestamps, ref civil_starts, ref civil_ends, ref infos, } = *self; let civil_starts: Vec<_> = civil_starts.iter().map(TzifDateTime::quote).collect(); let civil_ends: Vec<_> = civil_ends.iter().map(TzifDateTime::quote).collect(); let infos: Vec<_> = infos.iter().map(TzifTransitionInfo::quote).collect(); quote! { jiff::shared::TzifTransitions { timestamps: &[#(#timestamps),*], civil_starts: &[#(#civil_starts),*], civil_ends: &[#(#civil_ends),*], infos: &[#(#infos),*], } } } } impl TzifLocalTimeType { fn quote(&self) -> proc_macro2::TokenStream { let TzifLocalTimeType { offset, is_dst, ref designation, ref indicator, } = *self; let desig_start = designation.0; let desig_end = designation.1; let indicator = indicator.quote(); quote! { jiff::shared::TzifLocalTimeType { offset: #offset, is_dst: #is_dst, designation: (#desig_start, #desig_end), indicator: #indicator, } } } } impl TzifIndicator { fn quote(&self) -> proc_macro2::TokenStream { match *self { TzifIndicator::LocalWall => quote! { jiff::shared::TzifIndicator::LocalWall }, TzifIndicator::LocalStandard => quote! { jiff::shared::TzifIndicator::LocalStandard }, TzifIndicator::UTStandard => quote! { jiff::shared::TzifIndicator::UTStandard }, } } } impl TzifTransitionInfo { fn quote(&self) -> proc_macro2::TokenStream { let TzifTransitionInfo { type_index, kind } = *self; let kind = kind.quote(); quote! { jiff::shared::TzifTransitionInfo { type_index: #type_index, kind: #kind, } } } } impl TzifTransitionKind { fn quote(&self) -> proc_macro2::TokenStream { match *self { TzifTransitionKind::Unambiguous => quote! { jiff::shared::TzifTransitionKind::Unambiguous }, TzifTransitionKind::Gap => quote! { jiff::shared::TzifTransitionKind::Gap }, TzifTransitionKind::Fold => quote! { jiff::shared::TzifTransitionKind::Fold }, } } } impl TzifDateTime { fn quote(&self) -> proc_macro2::TokenStream { let year = self.year(); let month = self.month(); let day = self.day(); let hour = self.hour(); let minute = self.minute(); let second = self.second(); quote! { jiff::shared::TzifDateTime::new( #year, #month, #day, #hour, #minute, #second, ) } } } impl PosixTimeZone { fn quote(&self) -> proc_macro2::TokenStream { let PosixTimeZone { ref std_abbrev, ref std_offset, ref dst } = *self; let std_abbrev = std_abbrev.as_str(); let std_offset = std_offset.quote(); let dst = dst .as_ref() .map(|dst| { let dst = dst.quote(); quote!(Some(#dst)) }) .unwrap_or_else(|| quote!(None)); quote! { jiff::shared::PosixTimeZone { std_abbrev: #std_abbrev, std_offset: #std_offset, dst: #dst, } } } } impl PosixDst { fn quote(&self) -> proc_macro2::TokenStream { let PosixDst { ref abbrev, ref offset, ref rule } = *self; let abbrev = abbrev.as_str(); let offset = offset.quote(); let rule = rule.quote(); quote! { jiff::shared::PosixDst { abbrev: #abbrev, offset: #offset, rule: #rule, } } } } impl PosixRule { fn quote(&self) -> proc_macro2::TokenStream { let start = self.start.quote(); let end = self.end.quote(); quote! { jiff::shared::PosixRule { start: #start, end: #end } } } } impl PosixDayTime { fn quote(&self) -> proc_macro2::TokenStream { let PosixDayTime { ref date, ref time } = *self; let date = date.quote(); let time = time.quote(); quote! { jiff::shared::PosixDayTime { date: #date, time: #time } } } } impl PosixDay { fn quote(&self) -> proc_macro2::TokenStream { match *self { PosixDay::JulianOne(day) => quote! { jiff::shared::PosixDay::JulianOne(#day) }, PosixDay::JulianZero(day) => quote! { jiff::shared::PosixDay::JulianZero(#day) }, PosixDay::WeekdayOfMonth { month, week, weekday } => quote! { jiff::shared::PosixDay::WeekdayOfMonth { month: #month, week: #week, weekday: #weekday, } }, } } } impl PosixTime { fn quote(&self) -> proc_macro2::TokenStream { let PosixTime { second } = *self; quote! { jiff::shared::PosixTime { second: #second } } } } impl PosixOffset { fn quote(&self) -> proc_macro2::TokenStream { let PosixOffset { second } = *self; quote! { jiff::shared::PosixOffset { second: #second } } } } jiff-static-0.2.16/src/shared/crc32/mod.rs000064400000000000000000000033721046102023000162200ustar 00000000000000// auto-generated by: jiff-cli generate shared use self::table::{TABLE, TABLE16}; mod table; /// Returns the "masked" CRC32 checksum of the slice using the Castagnoli /// polynomial. /// /// This "masked" checksum is the same one used by the Snappy frame format. /// Masking is supposed to make the checksum robust with respect to data that /// contains the checksum itself. pub(crate) fn sum(buf: &[u8]) -> u32 { let sum = slice16(0, buf); (sum.wrapping_shr(15) | sum.wrapping_shl(17)).wrapping_add(0xA282EAD8) } /// Returns the CRC32 checksum of `buf` using the Castagnoli polynomial. /// /// This computes the checksum by looking at 16 bytes from the given slice /// per iteration. fn slice16(prev: u32, mut buf: &[u8]) -> u32 { let mut crc: u32 = !prev; while buf.len() >= 16 { crc ^= u32::from_le_bytes(buf[..4].try_into().unwrap()); crc = TABLE16[0][usize::from(buf[15])] ^ TABLE16[1][usize::from(buf[14])] ^ TABLE16[2][usize::from(buf[13])] ^ TABLE16[3][usize::from(buf[12])] ^ TABLE16[4][usize::from(buf[11])] ^ TABLE16[5][usize::from(buf[10])] ^ TABLE16[6][usize::from(buf[9])] ^ TABLE16[7][usize::from(buf[8])] ^ TABLE16[8][usize::from(buf[7])] ^ TABLE16[9][usize::from(buf[6])] ^ TABLE16[10][usize::from(buf[5])] ^ TABLE16[11][usize::from(buf[4])] ^ TABLE16[12][usize::from((crc >> 24) as u8)] ^ TABLE16[13][usize::from((crc >> 16) as u8)] ^ TABLE16[14][usize::from((crc >> 8) as u8)] ^ TABLE16[15][usize::from((crc) as u8)]; buf = &buf[16..]; } for &b in buf { crc = TABLE[usize::from((crc as u8) ^ b)] ^ (crc >> 8); } !crc } jiff-static-0.2.16/src/shared/crc32/table.rs000064400000000000000000001576631046102023000165450ustar 00000000000000// auto-generated by: jiff-cli generate shared // auto-generated by: jiff-cli generate crc32 pub(super) const TABLE: [u32; 256] = [ 0, 4067132163, 3778769143, 324072436, 3348797215, 904991772, 648144872, 3570033899, 2329499855, 2024987596, 1809983544, 2575936315, 1296289744, 3207089363, 2893594407, 1578318884, 274646895, 3795141740, 4049975192, 51262619, 3619967088, 632279923, 922689671, 3298075524, 2592579488, 1760304291, 2075979607, 2312596564, 1562183871, 2943781820, 3156637768, 1313733451, 549293790, 3537243613, 3246849577, 871202090, 3878099393, 357341890, 102525238, 4101499445, 2858735121, 1477399826, 1264559846, 3107202533, 1845379342, 2677391885, 2361733625, 2125378298, 820201905, 3263744690, 3520608582, 598981189, 4151959214, 85089709, 373468761, 3827903834, 3124367742, 1213305469, 1526817161, 2842354314, 2107672161, 2412447074, 2627466902, 1861252501, 1098587580, 3004210879, 2688576843, 1378610760, 2262928035, 1955203488, 1742404180, 2511436119, 3416409459, 969524848, 714683780, 3639785095, 205050476, 4266873199, 3976438427, 526918040, 1361435347, 2739821008, 2954799652, 1114974503, 2529119692, 1691668175, 2005155131, 2247081528, 3690758684, 697762079, 986182379, 3366744552, 476452099, 3993867776, 4250756596, 255256311, 1640403810, 2477592673, 2164122517, 1922457750, 2791048317, 1412925310, 1197962378, 3037525897, 3944729517, 427051182, 170179418, 4165941337, 746937522, 3740196785, 3451792453, 1070968646, 1905808397, 2213795598, 2426610938, 1657317369, 3053634322, 1147748369, 1463399397, 2773627110, 4215344322, 153784257, 444234805, 3893493558, 1021025245, 3467647198, 3722505002, 797665321, 2197175160, 1889384571, 1674398607, 2443626636, 1164749927, 3070701412, 2757221520, 1446797203, 137323447, 4198817972, 3910406976, 461344835, 3484808360, 1037989803, 781091935, 3705997148, 2460548119, 1623424788, 1939049696, 2180517859, 1429367560, 2807687179, 3020495871, 1180866812, 410100952, 3927582683, 4182430767, 186734380, 3756733383, 763408580, 1053836080, 3434856499, 2722870694, 1344288421, 1131464017, 2971354706, 1708204729, 2545590714, 2229949006, 1988219213, 680717673, 3673779818, 3383336350, 1002577565, 4010310262, 493091189, 238226049, 4233660802, 2987750089, 1082061258, 1395524158, 2705686845, 1972364758, 2279892693, 2494862625, 1725896226, 952904198, 3399985413, 3656866545, 731699698, 4283874585, 222117402, 510512622, 3959836397, 3280807620, 837199303, 582374963, 3504198960, 68661723, 4135334616, 3844915500, 390545967, 1230274059, 3141532936, 2825850620, 1510247935, 2395924756, 2091215383, 1878366691, 2644384480, 3553878443, 565732008, 854102364, 3229815391, 340358836, 3861050807, 4117890627, 119113024, 1493875044, 2875275879, 3090270611, 1247431312, 2660249211, 1828433272, 2141937292, 2378227087, 3811616794, 291187481, 34330861, 4032846830, 615137029, 3603020806, 3314634738, 939183345, 1776939221, 2609017814, 2295496738, 2058945313, 2926798794, 1545135305, 1330124605, 3173225534, 4084100981, 17165430, 307568514, 3762199681, 888469610, 3332340585, 3587147933, 665062302, 2042050490, 2346497209, 2559330125, 1793573966, 3190661285, 1279665062, 1595330642, 2910671697, ]; pub(super) const TABLE16: [[u32; 256]; 16] = [ [ 0, 4067132163, 3778769143, 324072436, 3348797215, 904991772, 648144872, 3570033899, 2329499855, 2024987596, 1809983544, 2575936315, 1296289744, 3207089363, 2893594407, 1578318884, 274646895, 3795141740, 4049975192, 51262619, 3619967088, 632279923, 922689671, 3298075524, 2592579488, 1760304291, 2075979607, 2312596564, 1562183871, 2943781820, 3156637768, 1313733451, 549293790, 3537243613, 3246849577, 871202090, 3878099393, 357341890, 102525238, 4101499445, 2858735121, 1477399826, 1264559846, 3107202533, 1845379342, 2677391885, 2361733625, 2125378298, 820201905, 3263744690, 3520608582, 598981189, 4151959214, 85089709, 373468761, 3827903834, 3124367742, 1213305469, 1526817161, 2842354314, 2107672161, 2412447074, 2627466902, 1861252501, 1098587580, 3004210879, 2688576843, 1378610760, 2262928035, 1955203488, 1742404180, 2511436119, 3416409459, 969524848, 714683780, 3639785095, 205050476, 4266873199, 3976438427, 526918040, 1361435347, 2739821008, 2954799652, 1114974503, 2529119692, 1691668175, 2005155131, 2247081528, 3690758684, 697762079, 986182379, 3366744552, 476452099, 3993867776, 4250756596, 255256311, 1640403810, 2477592673, 2164122517, 1922457750, 2791048317, 1412925310, 1197962378, 3037525897, 3944729517, 427051182, 170179418, 4165941337, 746937522, 3740196785, 3451792453, 1070968646, 1905808397, 2213795598, 2426610938, 1657317369, 3053634322, 1147748369, 1463399397, 2773627110, 4215344322, 153784257, 444234805, 3893493558, 1021025245, 3467647198, 3722505002, 797665321, 2197175160, 1889384571, 1674398607, 2443626636, 1164749927, 3070701412, 2757221520, 1446797203, 137323447, 4198817972, 3910406976, 461344835, 3484808360, 1037989803, 781091935, 3705997148, 2460548119, 1623424788, 1939049696, 2180517859, 1429367560, 2807687179, 3020495871, 1180866812, 410100952, 3927582683, 4182430767, 186734380, 3756733383, 763408580, 1053836080, 3434856499, 2722870694, 1344288421, 1131464017, 2971354706, 1708204729, 2545590714, 2229949006, 1988219213, 680717673, 3673779818, 3383336350, 1002577565, 4010310262, 493091189, 238226049, 4233660802, 2987750089, 1082061258, 1395524158, 2705686845, 1972364758, 2279892693, 2494862625, 1725896226, 952904198, 3399985413, 3656866545, 731699698, 4283874585, 222117402, 510512622, 3959836397, 3280807620, 837199303, 582374963, 3504198960, 68661723, 4135334616, 3844915500, 390545967, 1230274059, 3141532936, 2825850620, 1510247935, 2395924756, 2091215383, 1878366691, 2644384480, 3553878443, 565732008, 854102364, 3229815391, 340358836, 3861050807, 4117890627, 119113024, 1493875044, 2875275879, 3090270611, 1247431312, 2660249211, 1828433272, 2141937292, 2378227087, 3811616794, 291187481, 34330861, 4032846830, 615137029, 3603020806, 3314634738, 939183345, 1776939221, 2609017814, 2295496738, 2058945313, 2926798794, 1545135305, 1330124605, 3173225534, 4084100981, 17165430, 307568514, 3762199681, 888469610, 3332340585, 3587147933, 665062302, 2042050490, 2346497209, 2559330125, 1793573966, 3190661285, 1279665062, 1595330642, 2910671697, ], [ 0, 329422967, 658845934, 887597209, 1317691868, 1562966443, 1775194418, 2054015301, 2635383736, 2394315727, 3125932886, 2851302177, 3550388836, 3225172499, 4108030602, 3883469565, 1069937025, 744974838, 411091311, 186800408, 1901039709, 1659701290, 1443537075, 1168652484, 2731618873, 2977147470, 2241069783, 2520160928, 3965408229, 4294560658, 3407766283, 3636263804, 2139874050, 1814657909, 1489949676, 1265388443, 822182622, 581114537, 373600816, 98970183, 3802079418, 4047354061, 3319402580, 3598223395, 2887074150, 3216496913, 2337304968, 2566056447, 1078858371, 1408010996, 1728782957, 1957280282, 247755615, 493284136, 696337329, 975428550, 3713716539, 3472378188, 4196393429, 3921508770, 2479927527, 2154965136, 3029696521, 2805405822, 4279748100, 3971309171, 3629315818, 3421531805, 2979899352, 2722054063, 2530776886, 2239369025, 1644365244, 1906417099, 1162229074, 1457827109, 747201632, 1059847191, 197940366, 409914617, 3235002245, 3547377650, 3885434731, 4097154844, 2388153945, 2650459694, 2837276343, 3133144768, 1573319741, 1315204170, 2055455955, 1763794084, 323786209, 15601046, 873047311, 665533816, 2157716742, 2470362481, 2816021992, 3027996063, 3457565914, 3719617709, 3914560564, 4210158659, 495511230, 237665993, 986568272, 695160359, 1392674658, 1084235541, 1950857100, 1743073275, 3210335367, 2902150384, 2552030313, 2344516638, 4057183579, 3799067948, 3600188853, 3308527042, 575477567, 837783368, 84420561, 380288934, 1825011427, 2137386644, 1266828813, 1478549114, 4223924985, 3898696334, 3699821079, 3475264096, 3041499941, 2800419666, 2450303947, 2175677372, 1725380929, 1970643254, 1100089775, 1378914776, 677206173, 1006616810, 253257843, 482013188, 3288730488, 3617886991, 3812834198, 4041319393, 2324458148, 2569990867, 2915654218, 3194733117, 1494403264, 1253068983, 2119694382, 1844797529, 395880732, 70922603, 819829234, 595526021, 2219317755, 2548728204, 2735548693, 2964304226, 3401742375, 3647004752, 3985066185, 4263891134, 425515587, 184435252, 1041885869, 767259354, 1473690527, 1148462056, 1888717681, 1664160518, 3146639482, 2821681165, 2630408340, 2406105315, 4110911910, 3869577681, 3527588168, 3252691263, 647572418, 893105077, 31202092, 310281051, 1746094622, 2075251305, 1331067632, 1559552647, 81018109, 393651338, 596708371, 808686692, 1247698209, 1509737814, 1830514127, 2126116280, 2579562309, 2321704754, 3196440491, 2905036764, 3611991705, 3303540462, 4027559543, 3819779584, 991022460, 682841355, 475331986, 267806181, 1973136544, 1715025111, 1390320718, 1098646585, 2785349316, 3047659187, 2168471082, 2464327261, 3901714200, 4214093679, 3486146550, 3697854337, 2069880831, 1761429384, 1545269009, 1337489254, 903200291, 645342804, 311463629, 20059834, 3863682119, 4125721648, 3238931625, 3534533854, 2831252891, 3143886316, 2407812469, 2619790594, 1150955134, 1463334409, 1675566736, 1887274727, 168841122, 431151061, 760577868, 1056433979, 3650022854, 3391911345, 4274773288, 3983099231, 2533657626, 2225476717, 2957098228, 2749572227, ], [ 0, 2772537982, 1332695565, 3928932467, 2665391130, 1000289892, 3518101015, 1961911401, 944848581, 2635115707, 2000579784, 3531603638, 2794429151, 63834273, 3923822802, 1285642924, 1889697162, 3588485108, 1070411655, 2592914937, 4001159568, 1262308334, 2702412701, 72489443, 1223902031, 3987919153, 127668546, 2732426044, 3593332565, 1936487723, 2571285848, 1006839590, 3779394324, 1141205354, 2922096921, 191511399, 2140823310, 3671838064, 821366019, 2511642493, 3642082769, 2085902255, 2524616668, 859506082, 1204511179, 3800757173, 144978886, 2917507512, 2447804062, 883365088, 3733574803, 2076722925, 255337092, 2860101882, 1079472265, 3843482359, 2847389787, 217459237, 3872975446, 1134131240, 929635393, 2452131391, 2013679180, 3712474162, 3345318105, 1646531239, 2282410708, 759906474, 1505436867, 4244289213, 383022798, 3012945072, 4281646620, 1517628514, 2958814225, 354057839, 1642732038, 3299575928, 780486667, 2344934005, 3083337043, 310800173, 4171804510, 1575566624, 689527113, 2354629431, 1719012164, 3275200826, 2409022358, 718754280, 3237581211, 1706558437, 289957772, 3020551666, 1579627905, 4217808895, 639728589, 2204166579, 1766730176, 3423583166, 3103776727, 499010985, 4153445850, 1389436836, 510674184, 3140605814, 1360992005, 4099835259, 2158944530, 636449644, 3485578015, 1786782049, 1451427399, 4089615417, 434918474, 3165505076, 3361579613, 1830563875, 2268262480, 577987118, 1859270786, 3415452412, 566061711, 2231171313, 4027358360, 1431113446, 3210989205, 438459627, 2334619459, 778495293, 3293062478, 1628026672, 368694105, 2964865319, 1519812948, 4292285226, 3010873734, 372759544, 4229503883, 1498974709, 766045596, 2297004002, 1657257873, 3347459567, 4219800265, 1589942455, 3035257028, 296471226, 1700507347, 3222944941, 708115678, 2406837920, 3285464076, 1721083506, 2361091585, 704312447, 1560973334, 4165665384, 308658715, 3072610405, 1784908887, 3475119657, 621600346, 2152549412, 4106037325, 1375517235, 3151133248, 513009726, 1379054226, 4151517420, 492691615, 3088872161, 3438024328, 1772979446, 2206418053, 650303227, 448917981, 3212862371, 1437508560, 4042207662, 2216646087, 559859641, 3413116874, 1848743348, 579915544, 2278645094, 1845468437, 3367898987, 3159255810, 420477308, 4079040783, 1449175921, 1279457178, 3909314020, 53323159, 2792110057, 3533460352, 2011021822, 2649948557, 951227379, 1947453791, 3511835425, 998021970, 2654800172, 3939331397, 1334640443, 2778873672, 14921014, 1021348368, 2577471598, 1938806813, 3603843683, 2721984010, 125811828, 3981540359, 1209069177, 78755029, 2716870315, 1272899288, 4003427494, 2590970063, 1060012721, 3573564098, 1883361468, 2902854798, 138911472, 3798556291, 1193856253, 869836948, 2526624490, 2092432025, 3656804583, 2505519691, 806789173, 3661127750, 2138698296, 193566289, 2932343855, 1155974236, 3785840162, 3718541572, 2028331898, 2462786313, 931836279, 1132123422, 3862644576, 202737427, 2840860013, 3858059201, 1085595071, 2862226892, 266047410, 2066475995, 3731519909, 876919254, 2433035176, ], [ 0, 3712330424, 3211207553, 1646430521, 2065838579, 2791807819, 3292861042, 419477706, 4131677158, 721537374, 1227047015, 2489772767, 2372293141, 1344534701, 838955412, 4014267180, 3915690301, 874584965, 1443074748, 2336634884, 2454094030, 1325607542, 757179215, 4033087991, 522244827, 3261429859, 2689069402, 2097306594, 1677910824, 3108456848, 3680878761, 102787601, 3609531531, 174112307, 1749169930, 3037175218, 2886149496, 1900187584, 325060345, 3458575425, 560035693, 4230274517, 2651215084, 1128529492, 1514358430, 2265377830, 3844367647, 945934247, 1044489654, 3808694030, 2166785591, 1550003343, 1164153925, 2552643325, 4194613188, 658578812, 3355821648, 356543720, 2002970065, 2854702953, 3005740963, 1851940123, 205575202, 3506798234, 2879807463, 1994650975, 348224614, 3380926174, 3498339860, 230802604, 1877167509, 2997282605, 1575107585, 2158466745, 3800375168, 1069593912, 650120690, 4219840330, 2577870451, 1155695819, 1120071386, 2676442210, 4255501659, 551577571, 971038505, 3836048785, 2257058984, 1539462672, 3028716860, 1774397316, 199339709, 3601073157, 3483679951, 316741239, 1891868494, 2911254006, 2088979308, 2714165716, 3286526189, 513917525, 128023199, 3672428583, 3100006686, 1703146406, 2328307850, 1468170802, 899681035, 3907363251, 4058323321, 748729281, 1317157624, 2479329344, 2515008081, 1218597097, 713087440, 4156912488, 4005940130, 864051482, 1369630755, 2363966107, 1671666103, 3202757391, 3703880246, 25235598, 411150404, 3317957372, 2816904133, 2057511293, 1386268991, 2414175111, 3989301950, 813842438, 696449228, 4106703476, 2531646285, 1268806133, 2766449369, 2040594529, 461605208, 3334874080, 3754335018, 42152338, 1621211307, 3185840659, 3150215170, 1719784122, 77814659, 3655790907, 3236317681, 497279817, 2139187824, 2730803400, 1300241380, 2428875100, 4075239525, 799183581, 916597271, 3957817519, 2311391638, 1417716526, 2240142772, 1489008396, 987954741, 3886503053, 4272417863, 602031871, 1103155142, 2625987966, 1942077010, 2927891690, 3433471443, 300103531, 149131169, 3584435481, 3078925344, 1791035032, 1826712713, 2980365873, 3548794632, 247719344, 398679418, 3397842882, 2829352699, 1977734211, 2594508655, 1205904855, 633482478, 4169631318, 3783736988, 1019384868, 1591745821, 2208675749, 4177958616, 608386144, 1180808537, 2602835937, 2183440171, 1600195987, 1027835050, 3758501394, 256046398, 3523698566, 2955269823, 1835039751, 1952498893, 2837802613, 3406292812, 373444084, 274868197, 3441921373, 2936341604, 1916841692, 1799362070, 3053829294, 3559339415, 157458223, 3861267459, 996404923, 1497458562, 2214907194, 2634315248, 1078058824, 576935537, 4280745161, 774079059, 4083558635, 2437194194, 1275136874, 1426174880, 2286164248, 3932590113, 925055641, 3630686645, 86133517, 1728102964, 3125110924, 2739261510, 2113960702, 472052679, 3244775807, 3343332206, 436378070, 2015367407, 2774907479, 3160736413, 1629530149, 50471196, 3729230756, 822300808, 3964074544, 2388947721, 1394727345, 1243701627, 2539965379, 4115022586, 671344706, ], [ 0, 940666796, 1881333592, 1211347188, 3762667184, 3629437212, 2422694376, 2826309188, 3311864721, 4252394557, 3041252553, 2371140453, 623031585, 489937549, 1426090617, 1829832149, 2401395155, 3073576575, 4278238859, 3339833639, 1869078371, 1467396303, 524615739, 659845015, 1246063170, 1918109166, 979875098, 41343670, 2852181234, 2450635614, 3659664298, 3795018758, 464041303, 599382779, 1804233231, 1402668451, 4226616295, 3288097867, 2345653439, 3017718547, 3738156742, 3873362282, 2934792606, 2533101106, 1049231478, 110850010, 1319690030, 1991880834, 2492126340, 2895887144, 3836218332, 3703137392, 1959750196, 1289618840, 82687340, 1023204032, 1374543637, 1778167993, 567214157, 434008033, 2980602277, 2310606345, 3247095549, 4187738449, 928082606, 255853826, 1198765558, 2137184858, 3608466462, 4010130354, 2805336902, 2670159082, 4063650111, 3391557267, 2182397543, 3120943563, 309583759, 711110691, 1649475799, 1514172283, 3094547325, 2153931985, 3360805925, 4030774153, 1479980493, 1613224545, 672426645, 268764473, 2098462956, 1157984064, 221700020, 891793432, 2639380060, 2772488688, 3983761668, 3579973288, 754573305, 350793813, 1557856417, 1690988301, 3434897737, 4104982245, 3164519953, 2224017853, 3919500392, 3515857860, 2579237680, 2712495260, 165374680, 835323252, 2046408064, 1105779244, 2749087274, 2613760390, 3556335986, 3957853918, 1134428314, 2072997686, 868016066, 195932270, 1723643323, 1588451863, 379480803, 781124943, 2264468235, 3202901159, 4141601875, 3469392895, 1856165212, 1454619376, 511707652, 647062952, 2397531116, 3069576256, 4274369716, 3335838488, 2881871565, 2480189793, 3689349525, 3824578105, 1266704509, 1938886609, 1000521509, 62115977, 3783308431, 3650214691, 2443340759, 2847081595, 29690431, 970220947, 1911018855, 1240906443, 619167518, 485937330, 1422221382, 1825837034, 3298951598, 4239617538, 3028344566, 2358358362, 1963614219, 1293619111, 86556499, 1027199231, 2505039547, 2908664087, 3849126371, 3715919439, 2959960986, 2289828918, 3226449090, 4166966126, 1344853290, 1748613766, 537528946, 404448734, 4196925912, 3258543732, 2315968128, 2988159276, 443400040, 578605252, 1783586864, 1381896092, 1062144585, 123626981, 1332598033, 2004662973, 3742020857, 3877362517, 2938661793, 2537096205, 1509146610, 1642254430, 701587626, 297799430, 3115712834, 2175233774, 3381976602, 4052070838, 2626991203, 2760235983, 3971377979, 3567715479, 2094074579, 1153459583, 217306507, 887274023, 3604078113, 4005605773, 2800943481, 2665639637, 915693713, 243601213, 1186381769, 2124927077, 330749360, 732412444, 1670646504, 1535468868, 4092816128, 3420587180, 2211558488, 3149978612, 1113262757, 2051695881, 846845437, 174635601, 2719921173, 2584730553, 3527174989, 3928818913, 2268856628, 3207425688, 4145995372, 3473912256, 1736032132, 1600704552, 391864540, 793382768, 3447286646, 4117234906, 3176903726, 2236275586, 758961606, 355318378, 1562249886, 1695507762, 136208615, 806293323, 2017247167, 1076744211, 3898334807, 3494556155, 2558066959, 2691198627, ], [ 0, 4012927769, 3683426499, 884788186, 3002414967, 1573215342, 1769576372, 2252995757, 1611012127, 2402710278, 3146430684, 1421530053, 3539152744, 1036207217, 159354795, 3863995570, 3222024254, 792484647, 461410557, 4105239524, 1928922953, 2647223376, 2843060106, 1178979475, 2685020193, 1329218360, 2072414434, 2495013883, 318709590, 4258231375, 3379806101, 641979532, 2247366285, 1791262100, 1584969294, 2974342487, 922821114, 3627109091, 3968696633, 62777888, 3857845906, 180512139, 1048489553, 3511600456, 1460091365, 3090633468, 2357958950, 1673261631, 1173890739, 2865253802, 2658436720, 1900342633, 4144828868, 406682333, 746696967, 3283212830, 637419180, 3402519989, 4268924527, 289600886, 2534083035, 2017157826, 1283959064, 2746728961, 235166699, 3778294002, 3582524200, 985174065, 3169938588, 1405159301, 1736297567, 2286790470, 1845642228, 2167548141, 3046040375, 1522436142, 3707204739, 868687770, 125555776, 3897278297, 3456658389, 557318348, 361024278, 4206141455, 2096979106, 2479699899, 2809265249, 1212258168, 2920182730, 1094588627, 1971507977, 2595403792, 486229181, 4090179492, 3346523262, 675778407, 2347781478, 1690314367, 1350364581, 3209463484, 956660241, 3593801992, 3800685266, 230273483, 3958789497, 80100960, 813364666, 3746209443, 1493393934, 3056797975, 2190459597, 1841277396, 1274838360, 2764838465, 2423315867, 2134947458, 4178135599, 372842806, 579201772, 3451224565, 737830215, 3301576286, 4034315652, 524725917, 2567918128, 1983854889, 1115943667, 2914228714, 470333398, 4080590031, 3347322645, 682916876, 2935849121, 1104014264, 1970348130, 2587970427, 2081337289, 2470364368, 2810318602, 1219650579, 3472595134, 567014311, 360134781, 4198978404, 3691284456, 859106545, 126363435, 3904392242, 1861333151, 2176965510, 3044872284, 1515027269, 3154288631, 1395848430, 1737375540, 2294174765, 251111552, 3787965337, 3581610051, 978019162, 2583470427, 1993132610, 1114636696, 2906679937, 722048556, 3292134709, 4035262191, 531979766, 4193958212, 382390877, 578165127, 3443946142, 1259310643, 2755650858, 2424516336, 2142455273, 1508938085, 3066100348, 2189177254, 1833720511, 3943015954, 70634763, 814286545, 3753471432, 972458362, 3603358307, 3799656889, 222970528, 2332278285, 1681118484, 1351556814, 3216995799, 302836797, 4248602404, 3380628734, 649078759, 2700729162, 1338617939, 2071296905, 2487554192, 1913320482, 2637864763, 2844153057, 1186349048, 3237987157, 802138188, 460546966, 4098033807, 3523271683, 1026602778, 160201920, 3871086553, 1626729332, 2412085357, 3145288631, 1414078638, 2986787868, 1563864837, 1770677471, 2260340678, 15987563, 4022573170, 3682554792, 877607089, 2549676720, 2026410409, 1282693747, 2739155306, 621661639, 3393038046, 4269894916, 296814109, 4160676527, 416188854, 745685612, 3275893109, 1158403544, 2856042177, 2659677467, 1907826178, 1475660430, 3099894167, 2356701773, 1665663316, 3842113017, 171022048, 1049451834, 3518838307, 938660497, 3636640136, 3967709778, 55449931, 2231887334, 1782025983, 1586185509, 2981834300, ], [ 0, 1745038536, 3490077072, 3087365464, 2782971345, 3454265625, 1978047553, 501592201, 1311636819, 640602523, 2653660355, 4129851403, 3956095106, 2211320906, 1003184402, 1405636058, 2623273638, 4099462766, 1281205046, 610177022, 968572791, 1371018175, 3921503975, 2176731695, 3530950645, 3128240957, 40918629, 1785950893, 2006368804, 529919724, 2811272116, 3482564476, 1029407677, 1431875445, 3982350893, 2237593317, 2562410092, 4038617764, 1220354044, 549335860, 1937145582, 460673574, 2742036350, 3413314486, 3600202559, 3197474807, 110158511, 1855180391, 2701162779, 3372438995, 1896226955, 419761219, 81837258, 1826852866, 3571901786, 3169175954, 4012737608, 2267981952, 1059839448, 1462300944, 1254965657, 583953745, 2597001225, 4073206977, 2058815354, 313797554, 2863750890, 3266474530, 3747070635, 3075796579, 257006395, 1733474291, 882571817, 1553585889, 3835470777, 2359267185, 2440708088, 4185461552, 1098671720, 696208032, 3874291164, 2398081300, 921347148, 1592363140, 1124874253, 722408645, 2466931101, 4211690837, 2831220879, 3233950791, 2026330399, 281310679, 220317022, 1696786838, 3710360782, 3039080454, 1206682823, 804235279, 2548751703, 4293521823, 3792453910, 2316266974, 839522438, 1510552654, 163674516, 1640125788, 3653705732, 2982415564, 2887892037, 3290599565, 2082989525, 337955101, 3686235745, 3014939305, 196159473, 1672612665, 2119678896, 374642552, 2924601888, 3327315688, 2509931314, 4254707706, 1167907490, 765458026, 813319907, 1484352043, 3766230899, 2290037691, 4117630708, 2641175100, 627595108, 1298889644, 1351535397, 948824045, 2156434101, 3901472381, 3141792679, 3544244079, 1799727671, 54953727, 514012790, 1990204094, 3466948582, 2795914030, 1765143634, 20371610, 3107171778, 3509616906, 3436523907, 2765495627, 483616787, 1959806171, 655886593, 1327179209, 4145959057, 2669509721, 2197343440, 3942375448, 1392416064, 989706632, 3358952777, 2687934849, 406050009, 1882257425, 1842694296, 97936464, 3184726280, 3587194304, 2249748506, 3994770642, 1444817290, 1042089282, 603502027, 1274779907, 4093570139, 2617098387, 1416525807, 1013799719, 2221420159, 3966436023, 4052660798, 2576195318, 562621358, 1233897318, 440634044, 1916839540, 3393573676, 2722562020, 3215150957, 3617612709, 1873090301, 128334389, 2413365646, 3889833286, 1608470558, 937196758, 708430943, 1111154839, 4198471119, 2453453063, 3254056157, 2851592213, 301116749, 2045870469, 1679044876, 202841540, 3021105308, 3692119124, 327349032, 2072109024, 3280251576, 2877785712, 3059889913, 3730905649, 1717858153, 241648545, 1571753595, 900473523, 2376685547, 3853155107, 4165979050, 2420959074, 675910202, 1078640370, 2994899507, 3665929979, 1652872099, 176684907, 392318946, 2137088810, 3345225330, 2942778042, 4239357792, 2494323624, 749285104, 1151992376, 1498395313, 827104889, 2303322913, 3779774441, 786002069, 1188715613, 4276037893, 2531001805, 2335814980, 3812268428, 1530916052, 859619356, 1626639814, 150446350, 2968704086, 3639736478, 3306440727, 2903991519, 353505671, 2098281807, ], [ 0, 1228700967, 2457401934, 3678701417, 555582061, 1747058506, 3009771555, 4200137988, 1111164122, 185039357, 3494117012, 2575270835, 1663469239, 706411408, 4049501433, 3093430750, 2222328244, 3444208787, 370078714, 1597148893, 2775288793, 3965187838, 924021143, 2117012656, 3326938478, 2406576201, 1412822816, 487164423, 3880816387, 2926375460, 1965585741, 1007945834, 218129817, 1144789182, 2675482583, 3594838768, 740157428, 1696701139, 3194297786, 4149829789, 1329291587, 101129316, 3712195341, 2491409962, 1848042286, 656055817, 4234025312, 3043124295, 2306239533, 3226079498, 453940835, 1379068740, 2825645632, 3780612967, 974328846, 1932486953, 3410847991, 2188449232, 1496683193, 269086622, 3931171482, 2741802941, 2015891668, 823422451, 436259634, 1396487701, 2289578364, 3242478683, 991775071, 1914778744, 2842014481, 3763981878, 1480314856, 285717199, 3393402278, 2206156929, 2032553349, 807022754, 3948853195, 2724383468, 2658583174, 3612000161, 202258632, 1160922607, 3211477227, 4132912588, 756267685, 1680852866, 3696084572, 2507258747, 1312111634, 118047029, 4249895985, 3026991382, 1864941183, 638894936, 385920683, 1581044620, 2239255781, 3427019202, 907881670, 2132890081, 2758137480, 3982076847, 1429973617, 470275926, 3343077439, 2390699288, 1948657692, 1025135931, 3864973906, 2942480245, 2474026783, 3662338616, 17718609, 1211244662, 2993366386, 4216805461, 538173244, 1764729371, 3511526341, 2557599458, 1127569803, 168371372, 4031783336, 3110886543, 1646844902, 722773697, 872519268, 2101209923, 2792975402, 4014280973, 354161673, 1545627950, 2271538759, 3461911392, 1983550142, 1057419161, 3829557488, 2910721495, 1462178003, 505114100, 3311397533, 2355337146, 2960629712, 4182500087, 571434398, 1798510777, 2439650749, 3629539482, 51570675, 1244568276, 4065106698, 3144738349, 1614045508, 688397411, 3545307495, 2590860352, 1093264169, 135634446, 956429309, 1883082458, 2876836275, 3796202644, 404517264, 1361054903, 2321845214, 3277387513, 2067461927, 839289344, 3913420137, 2692640846, 1512535370, 320538733, 3361705732, 2170810915, 3178756681, 4098590574, 789512199, 1714650400, 2624223268, 3579184387, 236094058, 1194262349, 4283235987, 3060827060, 1832125661, 604535290, 3729882366, 2540503513, 1277789872, 85326743, 771841366, 1732059249, 3162089240, 4114995775, 253550395, 1176543772, 2640586101, 3562559570, 1815763340, 621159595, 4265780162, 3078545125, 1294457825, 68921030, 3747553711, 2523094152, 2859947234, 3813353925, 940551852, 1899221899, 2339034767, 3260459944, 420621505, 1345212902, 3897315384, 2708483359, 2050271862, 856217425, 3377582677, 2154671986, 1529423899, 303387964, 587282639, 1782400488, 2977546881, 4165320614, 35437218, 1260439429, 2422489324, 3646438859, 1631206421, 671498546, 4081239643, 3128867708, 1076346488, 152814431, 3529458742, 2606971153, 2809606523, 3997912156, 890227509, 2083763730, 2255139606, 3478572593, 336742744, 1563309183, 3846976929, 2893039750, 1999949807, 1040757448, 3293689804, 2372782827, 1445547394, 521482405, ], [ 0, 4097758792, 3985758817, 430902313, 3738157619, 720442491, 861804626, 3345010202, 3094606487, 1280124127, 1440884982, 2715614910, 1723609252, 2458052332, 2335042245, 2131967117, 1963693023, 2167752087, 2560248254, 1822728182, 2881769964, 1610254244, 1180011405, 2993380805, 3447218504, 960935680, 552188713, 3570888033, 330802043, 3884545331, 4263934234, 169389906, 3927386046, 506065398, 126287327, 4088933271, 886629773, 3236312005, 3645456364, 762833316, 1382339881, 2790916961, 3220508488, 1271650560, 2360022810, 2023080274, 1631243643, 2500074291, 2669458529, 1797415465, 1921871360, 2259925064, 1104377426, 3052249114, 2890050611, 1484552827, 661604086, 3545338046, 3405683863, 1052789471, 4188046533, 228479629, 338779812, 3759114476, 3519166861, 637333445, 1012130796, 3362600356, 252574654, 4214435318, 3801883103, 379778967, 1773259546, 2643402066, 2216694139, 1881065267, 3078490409, 1128324961, 1525666632, 2932933888, 2764679762, 1358396442, 1230532659, 3177621115, 2047240289, 2386083369, 2543301120, 1672045640, 481974469, 3901001357, 4046160548, 85284076, 3262487286, 910904510, 803487895, 3688535775, 1003822643, 3488335995, 3594830930, 578425370, 3843742720, 287574600, 143328865, 4239773737, 2208754852, 2006465260, 1849112261, 2584338573, 1567174295, 2841114847, 2969105654, 1153835710, 1323208172, 3135265700, 2739885965, 1467056581, 2417053663, 1680841111, 2105578942, 2310947830, 4138565499, 43231539, 456959258, 4009915218, 677559624, 3697044224, 3321063209, 835563873, 2791835115, 1381421987, 1274666890, 3217492418, 2024261592, 2358841744, 2503351737, 1627966449, 505149308, 3928301876, 4085919005, 129301333, 3235132751, 887808775, 759557934, 3648731494, 3546519092, 660422780, 1056066645, 3402406429, 229397511, 4187128399, 3762130534, 335763502, 1796236451, 2670637803, 2256649922, 1925146762, 3051333264, 1105293528, 1481538801, 2893064889, 1283400277, 3091330077, 2716792884, 1439706748, 2461065318, 1720596014, 2132883975, 2334125135, 4094480578, 3278474, 429722275, 3986939115, 717427441, 3741172921, 3344091280, 862723800, 963948938, 3444205506, 3571805163, 551271843, 3887821753, 327525873, 170568152, 4262756240, 2164736797, 1966708053, 1821809020, 2561167156, 1606975790, 2885048166, 2992200527, 1181191431, 2007645286, 2207574574, 2587616775, 1845833807, 2842033749, 1566255133, 1156850740, 2966090364, 3487158001, 1005000889, 575149200, 3598107352, 286657730, 3844659850, 4236760739, 146342123, 44150713, 4137646577, 4012930520, 453944208, 3698224522, 676379586, 838842347, 3317784995, 3134348590, 1324125030, 1464043343, 2742898951, 1679662877, 2418231637, 2307671420, 2108855092, 2646416344, 1770245520, 1881981369, 2215778289, 1131600363, 3075215267, 2934113162, 1524487618, 634317135, 3522182919, 3361682222, 1013048678, 4211157884, 255851828, 378597661, 3803064149, 3904276487, 478699087, 86463078, 4044981294, 913918516, 3259473020, 3689451605, 802571805, 1355119248, 2767957208, 3176440049, 1231713977, 2383067299, 2050256619, 1671127746, 2544219274, ], [ 0, 3411442597, 2470478267, 1477900830, 594376071, 3896184354, 2955801660, 2071695257, 1188752142, 2374799531, 3583666869, 516690192, 1706532489, 2934039852, 4143390514, 1033987223, 2377504284, 1189326265, 519395239, 3584240642, 2933433243, 1703860286, 1033380384, 4140718469, 3413064978, 3753655, 1479523497, 2474231564, 3892463765, 592720688, 2067974446, 2954146443, 512219849, 3587285356, 2378652530, 1183916247, 1038790478, 4139570411, 2930388725, 1711035728, 1482502599, 2466990690, 3407720572, 4967385, 2066760768, 2959491045, 3899704827, 589741662, 2469530837, 1482979184, 7507310, 3408197323, 2959046994, 2064188151, 589297897, 3897131852, 3588810715, 515808382, 1185441376, 2382241221, 4135948892, 1037298169, 1707414503, 2928896066, 1024439698, 4133080631, 2924426281, 1696157580, 509788181, 3577002928, 2367832494, 1182022155, 2077580956, 2961384761, 3902136103, 600024194, 1488464667, 2481868990, 3422071456, 11456773, 2965005198, 2079070251, 603644469, 3903625616, 2480346633, 1484877228, 9934770, 3418483735, 4133521536, 1027011365, 1696598331, 2926998174, 3574463751, 509314722, 1179483324, 2367358745, 596143963, 3906868478, 2965958368, 2073990469, 15014620, 3417530745, 2477103975, 1492377794, 1699906645, 2919563248, 4128376302, 1027898955, 1178595794, 2372504183, 3581898857, 506006476, 2923280711, 1701561058, 1031616764, 4130030425, 2370882752, 1174845285, 504384891, 3578148574, 3907473993, 598813164, 2074596338, 2968627287, 3414829006, 14441579, 1489675893, 2476531152, 2048879396, 2974355585, 3915377311, 571052346, 1500651171, 2451858694, 3392315160, 23389373, 1019576362, 4153670543, 2944729489, 1691580980, 531166637, 3573452296, 2364044310, 1203638195, 4155161912, 1023198877, 1693072515, 2948351782, 3569862847, 529642266, 1200048388, 2362520225, 2976929334, 2049322387, 573626253, 3915820072, 2451383217, 1498109972, 22913546, 3389774255, 1687728621, 2949565000, 4158140502, 1015958515, 1207288938, 2359541711, 3568649681, 534986356, 574773987, 3910410566, 2969754456, 2052366589, 19869540, 3396949185, 2456792799, 1496962426, 3912067057, 578493524, 2054022730, 2973474287, 3393196662, 18246099, 1493210061, 2455169128, 2952236287, 1688336218, 1018629444, 4158748385, 2358966648, 1204585181, 534411459, 3565945702, 1192287926, 2353440019, 3562037005, 520496296, 1685957425, 2938884244, 4147980938, 1013666095, 30029240, 3399241245, 2458563587, 1507643302, 581386303, 3924900762, 2984755588, 2058467873, 3399813290, 32731919, 1508215057, 2461266612, 3922230573, 580781704, 2055797910, 2984150835, 2357191588, 1193908225, 524247583, 3563657658, 2937230883, 1682238854, 1012012952, 4144262205, 1503069311, 2462154714, 3403122116, 25296481, 2063233528, 2980842077, 3921342531, 585927654, 525201265, 3558577364, 2349690570, 1197151599, 1008769782, 4151763283, 2942311245, 1681285352, 3559051875, 527739334, 1197626328, 2352228477, 4149192676, 1008327745, 1678714463, 2941869562, 2465741165, 1504592584, 28883158, 3404645235, 2979351786, 2059614031, 584437073, 3917723380, ], [ 0, 2540828609, 722442611, 3162402482, 1444885222, 3245262119, 2098244501, 3932249172, 2889770444, 995070477, 2268200127, 272632702, 4196489002, 1834000619, 3509505625, 1180645784, 1569766761, 3403762344, 1990140954, 3790525403, 193957775, 2633922638, 545265404, 3086082365, 4054767781, 1725902692, 3668001238, 1305523735, 2813454915, 817896834, 2361291568, 466584817, 3139533522, 743476499, 2418992033, 123671648, 3980281908, 2052046837, 3325153607, 1363158662, 387915550, 2154752223, 1007715949, 2875290028, 1090530808, 3597785657, 1779414155, 4252910410, 3870410683, 1908420730, 3451805384, 1523558665, 2964256093, 668926620, 2611047470, 214997999, 1250927223, 3724432822, 1635793668, 4143041733, 479236241, 2346805072, 933169634, 2700017187, 1940816725, 3839849620, 1486952998, 3486576103, 632402355, 2998945394, 247343296, 2580537089, 3750815385, 1222709592, 4104093674, 1676576811, 2307904639, 519971774, 2726317324, 905034445, 775831100, 3109014013, 87140175, 2453688462, 2015431898, 4015061787, 1395561897, 3294585448, 2181061616, 359771185, 2836382339, 1048458562, 3558828310, 1131323095, 4279300197, 1751189412, 3364878727, 1610485318, 3816841460, 1961989941, 2660289377, 165756064, 3047117330, 586065363, 1689361483, 4089473930, 1337853240, 3637506809, 850308781, 2782878060, 429995998, 2396045343, 2501854446, 40809263, 3188776349, 694233692, 3271587336, 1416724937, 3893358459, 2138970298, 958472482, 2924533475, 305051729, 2237616016, 1866339268, 4165985285, 1144097463, 3544218998, 3881633450, 1881993579, 3427966937, 1529047064, 2973905996, 640926605, 2588781887, 222059262, 1264804710, 3692205223, 1617754645, 4145876436, 494686592, 2316150337, 913557747, 2701279026, 3134045123, 767314946, 2445419184, 112448881, 3973220645, 2074312420, 3353153622, 1353508759, 385080847, 2172791246, 1039943548, 2861412541, 1089268969, 3617397544, 1810068890, 4237460059, 1551662200, 3406662585, 2004083979, 3758232266, 174280350, 2635250015, 560781293, 3055362092, 4030863796, 1731456629, 3679289543, 1279031046, 2791123794, 825023635, 2371007009, 438519264, 32293137, 2526885584, 719542370, 3180507043, 1475605495, 3229746230, 2096917124, 3951926597, 2916263133, 983782172, 2262646190, 296536687, 4224554555, 1824285178, 3502378824, 1202976905, 2498987519, 58880574, 3220970636, 680389453, 3270293273, 1436369112, 3923979882, 2123553195, 953016371, 2948339698, 331512128, 2226359937, 1859310293, 4188218644, 1172130726, 3534535783, 3378722966, 1578291031, 3798770149, 1964856868, 2675706480, 135134641, 3027473155, 587359426, 1700617562, 4063013531, 1314047017, 3642962920, 859991996, 2754844797, 407762639, 2403074318, 802357037, 3097692396, 81618526, 2477560223, 2043530699, 4005313034, 1388467384, 3316884345, 2213321441, 345861408, 2833449874, 1066595411, 3589515271, 1115840454, 4277940596, 1770899125, 1916944964, 3845371269, 1498274615, 3460050166, 610103458, 3006039907, 257092049, 2552438288, 3732678536, 1225642057, 4118003451, 1644316986, 2288194926, 521331375, 2741799965, 874347484, ], [ 0, 829543472, 1659086944, 1402109008, 3318173888, 4105602288, 2804218016, 2522164368, 2388842353, 3205694273, 3967909649, 3723537185, 1269139377, 2060735361, 692465617, 406322145, 422172691, 676858915, 2076864627, 1253811267, 3706620115, 3986644195, 3188531379, 2407330947, 2538278754, 2788875090, 4121470722, 3302585138, 1384931234, 1677560722, 812644290, 18752498, 844345382, 52585494, 1353717830, 1640090742, 4153729254, 3336910038, 2507622534, 2751896758, 3157354327, 2369827687, 3738357559, 4020443911, 2046179223, 1216865191, 454402039, 711216071, 729442357, 436976645, 1234813013, 2028475493, 4005378293, 3754749125, 2355007637, 3173991589, 2769862468, 2489936756, 3355121444, 4136289044, 1625288580, 1370373044, 37504996, 860722132, 1688690764, 1440134268, 105170988, 926227484, 2707435660, 2417091772, 3280181484, 4075965660, 3938826045, 3686032141, 2284199773, 3109538669, 788719613, 510866381, 1306611613, 2089851821, 2106505311, 1291807855, 527241279, 773637135, 3091851423, 2302164143, 3668590847, 3957036239, 4092358446, 3265116958, 2433730382, 2692617086, 908804078, 123399134, 1422432142, 1706640318, 1458884714, 1736770650, 873953290, 90614842, 2469626026, 2722256026, 4056950986, 3231841530, 3633710875, 3924284203, 3128274811, 2332326731, 491870171, 740328427, 2142437307, 1321413515, 1339885689, 2125257801, 759078937, 474969129, 2316982457, 3144387721, 3908694233, 3649578217, 3250577160, 4040035128, 2740746088, 2452464472, 75009992, 889805816, 1721444264, 1475015576, 3377381528, 4164883624, 2880268536, 2598157512, 210341976, 1039680616, 1852454968, 1595665416, 1194072041, 1985856473, 634371977, 348023737, 2196457257, 3013251865, 3758681929, 3514383225, 3496417419, 3776367803, 2995040491, 2213897435, 362825803, 617716859, 2000937003, 1177695259, 1577439226, 1869880266, 1021732762, 228045738, 2613223226, 2863876874, 4179703642, 3360744298, 4213010622, 3396117646, 2583615710, 2827947246, 1054482558, 262927438, 1547274270, 1833458734, 1971300303, 1141797887, 396103599, 653122463, 2964911887, 2177442623, 3529203567, 3811216223, 3795101869, 3544546461, 2161574093, 2980500733, 670300269, 377629789, 1158696973, 1952547901, 1817608156, 1562881004, 246798268, 1069810572, 2844864284, 2564881196, 3413280636, 4194521932, 2917769428, 2627237092, 3473541300, 4269530244, 1747906580, 1499407396, 181229684, 1002212420, 596342693, 318415765, 1097392069, 1880689653, 3863750501, 3611161429, 2226097925, 3051248437, 3032512711, 2243013879, 3592671399, 3880912023, 1896294407, 1081539639, 333742183, 580211799, 983740342, 198409094, 1480656854, 1764807654, 4284874614, 3457428294, 2642827030, 2901902118, 2679771378, 2932589762, 4250515602, 3425201314, 1518157874, 1795986434, 949938258, 166673506, 299419523, 547951539, 1933275107, 1112194003, 3558840131, 3849208691, 3069984547, 2274224915, 2257832161, 3085049041, 3832569985, 3573658801, 1129617441, 1915046929, 565653569, 281470065, 150019984, 964742048, 1779611632, 1533240256, 3442888528, 4232551264, 2950031152, 2661561088, ], [ 0, 819083365, 1638166730, 1366706351, 3276333460, 4087011825, 2733412702, 2453580091, 2206053849, 3014626748, 3805922579, 3524001142, 1077236813, 1894214696, 563160199, 289610978, 51846467, 868558118, 1655926153, 1382110700, 3227516119, 4035822770, 2717617181, 2435429496, 2154473626, 2964885759, 3788429392, 3508330549, 1126320398, 1945137515, 579221956, 307495329, 103692934, 922485475, 1737116236, 1465414185, 3311852306, 4122272631, 2764221400, 2484114365, 2236845919, 3045177146, 3841458069, 3559245808, 1176202955, 1992906414, 666836481, 393029220, 87631813, 904601504, 1688033039, 1414492010, 3329345105, 4137942580, 2815800987, 2533854974, 2252640796, 3063327353, 3890275030, 3610434227, 1158443912, 1977502701, 614990658, 343554855, 207385868, 1015958889, 1844970950, 1563049379, 3474232472, 4291210493, 2930828370, 2657279031, 2401353941, 3220437168, 4001738783, 3730278522, 1281958209, 2092636452, 768430475, 488597998, 256600143, 1067012138, 1861163141, 1581064416, 3422783963, 4241600958, 2913466641, 2641740148, 2352405910, 3169117683, 3985812828, 3711997241, 1333672962, 2141979751, 786058440, 503870637, 175263626, 983594991, 1809203008, 1526990629, 3376066078, 4192769659, 2828984020, 2555176625, 2299525715, 3118318134, 3903556249, 3631854332, 1246174151, 2056594338, 736324365, 456217448, 157635273, 968321708, 1757487619, 1477646950, 3391992669, 4211051320, 2877932439, 2606496754, 2316887824, 3133857653, 3955005402, 3681464255, 1229981316, 2038578913, 687109710, 405163563, 414771736, 678089341, 2031917778, 1238278839, 3689941900, 3944887273, 3126098758, 2324054819, 2613403585, 2870437796, 4200673035, 3400734574, 1485684309, 1751090736, 959041183, 167507706, 464516955, 729665342, 2047575953, 1255784436, 3639023311, 3895799466, 3108201989, 2308005472, 2563916418, 2818603751, 4185272904, 3382970925, 1536860950, 1799920499, 977195996, 183299001, 513200286, 776268027, 2134024276, 1340119089, 3722326282, 3976989039, 3162128832, 2359851429, 2649449799, 2906217762, 4233041293, 3432852968, 1587774675, 1852947638, 1057485849, 265669756, 495046109, 760477112, 2082848023, 1291289970, 3737726025, 3994752044, 3211615363, 2411685094, 2667345924, 2922266721, 4283959502, 3481940139, 1572116880, 1835442677, 1007741274, 214094143, 350527252, 607561585, 1967189982, 1167251387, 3618406016, 3883812581, 3053981258, 2262447663, 2543397581, 2806715048, 4131215879, 3337577058, 1423035225, 1677980476, 896908179, 94864374, 401834583, 656521778, 1985475229, 1183173368, 3569050563, 3832109990, 3038712585, 2244815724, 2492348302, 2757496811, 4113188676, 3321397025, 1472648730, 1729425023, 912434896, 112238261, 315270546, 572038647, 1936643416, 1136454973, 3514975238, 3780148323, 2955293900, 2163477673, 2444693579, 2707761198, 4027801729, 3233896676, 1392505311, 1647167930, 861634837, 59357552, 299743441, 554664116, 1887029275, 1085010046, 3533003077, 3796328736, 3006343567, 2212696554, 2459962632, 2725393773, 4077157826, 3285599655, 1374219420, 1631245561, 810327126, 10396723, ], [ 0, 1409766726, 2819533452, 4228513738, 1441866729, 32929455, 4261382501, 2851658787, 2883733458, 4293202580, 65858910, 1475065880, 4262683707, 2853450109, 1444794039, 35298289, 1378416981, 103787539, 4196815833, 2921399967, 131717820, 1407094778, 2950131760, 4224722294, 4190813831, 2915953601, 1371804683, 96682317, 2889588078, 4164732968, 70596578, 1345479332, 2756833962, 4032210924, 207575078, 1482165600, 4053848387, 2779218949, 1504607183, 229191305, 263435640, 1538580542, 2814189556, 4089072306, 1514313361, 239453143, 4065082397, 2789960027, 4135128063, 2726190777, 1584898419, 175174709, 2743609366, 4153376080, 193364634, 1602344924, 1570458669, 161225067, 4120241825, 2710746087, 141193156, 1550662274, 2690958664, 4100165646, 1297060773, 424199907, 3846256937, 2974182511, 415150156, 1287251210, 2964331200, 3837218694, 3870151799, 2997518641, 1319337723, 446966717, 3009214366, 3881542360, 458382610, 1330972756, 526871280, 1264598966, 3077161084, 3815675194, 1251363097, 512826463, 3801670549, 3063920339, 3028626722, 3766646884, 478906286, 1217188584, 3782479563, 3044236173, 1232774215, 494792961, 3911082255, 3172545609, 1091619715, 353869509, 3169796838, 3907524512, 350349418, 1088863532, 1123821277, 385577883, 3941764177, 3203782935, 386729268, 1124749426, 3204689848, 3942972158, 3140917338, 4013018396, 322450134, 1195337616, 4006067123, 3133206261, 1187582271, 315507833, 282386312, 1154714318, 3101324548, 3973914690, 1160117345, 287484199, 3979040493, 3106669483, 2594121546, 3466097164, 848399814, 1721161856, 3480093859, 2607370725, 1734389295, 862452585, 830300312, 1702507998, 2574502420, 3446972242, 1686913905, 814421559, 3431131645, 2558901435, 3367493151, 2628815705, 1622766739, 884875733, 2638675446, 3376523440, 893933434, 1632567868, 1666554317, 928173195, 3411751745, 2673632775, 916765220, 1654910818, 2661945512, 3400353262, 1053742560, 1791590566, 2529197932, 3267832362, 1799353865, 1060676431, 3274792069, 2536901059, 2502726194, 3240871796, 1025652926, 1764060664, 3235754459, 2497373341, 1758665559, 1020546577, 1827024053, 954300915, 3303573049, 2431636351, 957812572, 1829788186, 2434377168, 3307139222, 3338955623, 2466463265, 1862975979, 990745773, 2465548430, 3337756104, 989585922, 1862055748, 3620779247, 2211963305, 2145263203, 735660837, 2183239430, 3592864320, 707739018, 2116577484, 2083713853, 674604667, 3560724913, 2151353591, 700698836, 2110031250, 2177727064, 3586797342, 2247642554, 3523156220, 771155766, 2045882992, 3490278995, 2215525141, 2013775071, 738234777, 773458536, 2048745262, 2249498852, 3524523426, 2079009153, 804027591, 3555033869, 2279790155, 1937851973, 663098115, 3683642569, 2408102287, 644900268, 1920413930, 2390675232, 3665402470, 3630367127, 2355385553, 1886235419, 610991709, 2375164542, 3650451256, 631015666, 1906040244, 564772624, 1974397526, 2309428636, 3718267098, 1951964409, 543148479, 3696637557, 2287035187, 2320234690, 3729567108, 574968398, 1984038664, 3753564971, 2344455789, 2008314279, 598942945, ], [ 0, 1737424129, 3474848258, 2828207875, 2614592245, 4233723892, 1422555383, 860128758, 843281179, 1439534618, 4250831129, 2597352472, 2845110766, 3457813743, 1720257516, 17299181, 1686562358, 50862903, 2879069236, 3423985973, 4283524291, 2564790722, 810327745, 1472357312, 1455789357, 827025452, 2581098287, 4267086382, 3440515032, 2862410457, 34598362, 1702957275, 3373124716, 2927833453, 101725806, 1637796719, 1390038681, 894743448, 2647110811, 4199106970, 4216242039, 2629845622, 877872501, 1407039604, 1620655490, 118997123, 2944714624, 3356113537, 2911578714, 3389380443, 1654050904, 85470553, 912042159, 1372738990, 4181807789, 2664411052, 2680707393, 4165379136, 1356178243, 928734786, 69196724, 1670457013, 3405914550, 2894912695, 2549607977, 4034466600, 1491735595, 1063577898, 203451612, 1806602717, 3275593438, 2763221983, 2780077362, 3258605619, 1789486896, 220699185, 1046683591, 1508762310, 4051625413, 2532317380, 4084271135, 2499802398, 1013772829, 1541541660, 1755745002, 254310379, 2814079208, 3224735209, 3241310980, 2797372933, 237994246, 1772190727, 1525021169, 1030423792, 2516059123, 4067884786, 1593451077, 963960644, 2447890503, 4134085958, 3308101808, 2728615345, 170941106, 1841211315, 1824084318, 188197983, 2745477980, 3291108957, 4151235499, 2430611114, 947071401, 1610470568, 981255283, 1576155506, 4116790897, 2465186672, 2712356486, 3324361607, 1857469572, 154681733, 138393448, 1873889897, 3340914026, 2695671915, 2481468829, 4100376732, 1559613343, 997929630, 704883363, 1301108642, 3843969185, 2190519712, 2983471190, 3596277079, 2127155796, 424095573, 406903224, 2144216249, 3613205434, 2966675131, 2207734605, 3826886220, 1284153679, 721706062, 1317358741, 688632212, 2174269079, 3860220822, 3578973792, 3000775521, 441398370, 2109852003, 2093367182, 457753231, 3017524620, 3562354829, 3876699515, 2157920378, 671893369, 1333967480, 3809371855, 2223021006, 739482829, 1268605388, 2027545658, 525801787, 3083083320, 3494568761, 3511490004, 3066292437, 508620758, 2044596951, 1251645217, 756312608, 2240245027, 3792277538, 2273870073, 3758521848, 1217755899, 790333434, 475988492, 2077359885, 3544381454, 3033269519, 3050042338, 3527741155, 2060847584, 492369121, 773615895, 1234340886, 3774974741, 2257548820, 3186902154, 3665475979, 1927921288, 359089033, 639878783, 1101871998, 3913170045, 2393948540, 2411149201, 3896101520, 1084935571, 656683154, 341882212, 1944995941, 3682422630, 3170087527, 3648168636, 3204210621, 376395966, 1910613439, 1118117961, 623631688, 2377701963, 3929417546, 3945910695, 2361339046, 606874533, 1134745252, 1894142802, 392736339, 3220941136, 3631567953, 1962510566, 326596071, 3152311012, 3697971173, 4012771859, 2292250386, 540274705, 1203571984, 1186659325, 557057788, 2309423615, 3995729150, 3714939144, 3135472649, 309363466, 1979612683, 276786896, 2012320721, 3747779794, 3102501331, 2343103525, 3961917732, 1152718375, 591129382, 574365131, 1169350858, 3978422217, 2326731464, 3119226686, 3731186239, 1995859260, 293115965, ], [ 0, 4060876286, 3790892301, 335044851, 3322195179, 872980757, 670089702, 3590114328, 2313498407, 2078876377, 1745961514, 2585612244, 1340179404, 3186462258, 2920672961, 1545200447, 371599551, 3827967297, 4157752754, 98453580, 3491923028, 573475242, 836168025, 3285902503, 2680358808, 1842285158, 2117560981, 2352703339, 1506257779, 2882250381, 3090400894, 1245694848, 743199102, 3728759936, 3451403379, 1068773773, 3930652053, 407171179, 196907160, 4189101414, 2779348569, 1470460839, 1146950484, 3058769578, 1672336050, 2443304780, 2186919871, 1884664385, 980056513, 3362157631, 3684570316, 697440562, 4235121962, 241359060, 496678951, 4019631577, 3012515558, 1099127576, 1383807979, 2692167189, 1972107789, 2273834995, 2491389696, 1718852350, 1486398204, 2861870850, 3110916081, 1264632335, 2661027351, 1821377513, 2137547546, 2372169444, 3514666459, 594640933, 814342358, 3263556904, 393814320, 3849661646, 4136455229, 75579843, 1321110083, 3165816765, 2940921678, 1564928688, 2293900968, 2058758998, 1766738853, 2604811867, 3344672100, 894937242, 649054313, 3567502743, 23005583, 4082304113, 3769328770, 312961404, 1960113026, 2262367868, 2501942927, 1730974577, 3000000361, 1088180887, 1394881124, 2703769498, 4247903397, 255709531, 482718120, 4006198358, 993357902, 3375988144, 3670089027, 684527805, 1660083005, 2432620227, 2198255152, 1896528846, 2767615958, 1459255848, 1157765851, 3071153957, 3944215578, 421263844, 182688023, 4176450793, 756242673, 3743372559, 3437704700, 1055602690, 2972796408, 1128089606, 1355098357, 2731091211, 2000021779, 2235163885, 2529264670, 1691191776, 953445087, 3403179809, 3642755026, 724306476, 4275095092, 215796682, 522496825, 3978864327, 2803330375, 1427857593, 1189281866, 3035565492, 1628684716, 2468334674, 2162666657, 1928044895, 787628640, 3707654046, 3473289069, 1024074387, 3908497035, 452649845, 151159686, 4212035192, 2642220166, 1869683064, 2089384331, 2391110773, 1534703725, 2843063699, 3129857376, 1216469150, 345521057, 3868472927, 4117517996, 123755346, 3533477706, 546347700, 862517831, 3244619705, 2338012217, 2035757511, 1789874484, 2560842954, 1298108626, 3209927980, 2896952799, 1588064289, 46011166, 4038205152, 3813309971, 289829869, 3300573173, 917942795, 625922808, 3611483910, 3920226052, 463858426, 140347913, 4199647223, 799886319, 3718333969, 3461949154, 1012214556, 1615644707, 2453718493, 2176361774, 1941219536, 2789762248, 1413769526, 1203505605, 3048211515, 4287614907, 226738757, 511419062, 3967266632, 965436240, 3414650542, 3632205405, 712180643, 1986715804, 2221337954, 2543750545, 1704099951, 2960018551, 1113735561, 1369055610, 2744528004, 3320166010, 938064772, 605150071, 3592279689, 65084049, 4058847087, 3793057692, 270105186, 1275107677, 3188495523, 2918511696, 1610152366, 2315531702, 2013804616, 1810913467, 2583450949, 3552812741, 567251771, 842527688, 3225157174, 365376046, 3888857040, 4097007395, 104813277, 1512485346, 2821372956, 3151158511, 1239339281, 2619481353, 1848512759, 2111205380, 2413460986, ], ]; jiff-static-0.2.16/src/shared/mod.rs000064400000000000000000000474751046102023000153200ustar 00000000000000// auto-generated by: jiff-cli generate shared /*! Defines data types shared between `jiff` and `jiff-static`. While this module exposes types that can be imported outside of `jiff` itself, there are *no* semver guarantees provided. That is, this module is _not_ part of Jiff's public API. The only guarantee of compatibility that is provided is that `jiff-static x.y.z` works with one and only one version of Jiff, corresponding to `jiff x.y.z` (i.e., the same version number). # Design This module is really accomplishing two different things at the same time. Firstly, it is a way to provide types that can be used to construct a static `TimeZone`. The proc macros in `jiff-static` generate code using these types (and a few routines). Secondly, it provides a way to parse TZif data without `jiff-static` depending on `jiff` via a Cargo dependency. This actually requires copying the code in this module (which is why it is kinda sectioned off from the rest of jiff) into the `jiff-static` crate. This can be done automatically with `jiff-cli`: ```text jiff-cli generate shared ``` The copying of code is pretty unfortunate, because it means both crates have to compile it. However, the alternatives aren't great either. One alternative is to have `jiff-static` explicitly depend on `jiff` in its `Cargo.toml`. Then Jiff could expose the parsing routines, as it does here, and `jiff-static` could use them directly. Unfortunately, this means that `jiff` cannot depend on `jiff-static`. And that in turn means that `jiff` cannot re-export the macros. Users will need to explicitly depend on and use `jiff-static`. Moreover, this could result in some potential surprises since `jiff-static` will need to have an `=x.y.z` dependency on Jiff for compatibility reasons. That in turn means that the version of Jiff actually used is not determine by the user's `jiff = "x.y.z"` line, but rather by the user's `jiff-static = "x'.y'.z'"` line. This is overall annoying and not a good user experience. Plus, it inverts the typical relationship between crates and their proc macros (e.g., `serde` and `serde_derive`) and thus could result in other unanticipated surprises. Another obvious alternative is to split this code out into a separate crate that both `jiff` and `jiff-static` depend on. However, the API exposed in this module does not provide a coherent user experience. It would either need a ton of work to turn it into a coherent user experience or it would need to be published as a `jiff-internal-use-only` crate that I find to be very annoying and confusing. Moreover, a separate crate introduces a new semver boundary beneath Jiff. I've found these sorts of things to overall increase maintenance burden (see ripgrep and regex for cases where I did this). I overall decided that the least bad choice was to copy a little code (under 2,000 source lines of code at present I believe). Since the copy is managed automatically via `jiff-cli generate shared`, we remove the downside of the code getting out of sync. The only downside is extra compile time. Since I generally only expect `jiff-static` to be used in niche circumstances, I prefer this trade-off over the other choices. More context on how I arrived at this design can be found here: # Particulars When this code is copied to `jiff-static`, the following transformations are done: * A header is added to indicate that the copied file is auto-generated. * All `#[cfg(feature = "alloc")]` annotations are removed. The `jiff-static` proc macro always runs in a context where the standard library is available. * Any code between `// only-jiff-start` and `// only-jiff-end` comments is removed. Nesting isn't supported. Otherwise, this module is specifically organized in a way that doesn't rely on any other part of Jiff. The one exception are routines to convert from these exposed types to other internal types inside of Jiff. This is necessary for building a static `TimeZone`. But these conversion routines are removed when this module is copied to `jiff-static`. */ /// An alias for TZif data whose backing storage has a `'static` lifetime. /// An alias for TZif data whose backing storage is on the heap. pub type TzifOwned = Tzif< alloc::string::String, self::util::array_str::Abbreviation, alloc::vec::Vec, alloc::vec::Vec, alloc::vec::Vec, alloc::vec::Vec, alloc::vec::Vec, >; /// An alias for TZif transition data whose backing storage is on the heap. pub type TzifTransitionsOwned = TzifTransitions< alloc::vec::Vec, alloc::vec::Vec, alloc::vec::Vec, alloc::vec::Vec, >; #[derive(Clone, Debug)] pub struct Tzif { pub fixed: TzifFixed, pub types: TYPES, pub transitions: TzifTransitions, } #[derive(Clone, Debug)] pub struct TzifFixed { pub name: Option, /// An ASCII byte corresponding to the version number. So, 0x50 is '2'. /// /// This is unused. It's only used in `test` compilation for emitting /// diagnostic data about TZif files. If we really need to use this, we /// should probably just convert it to an actual integer. pub version: u8, pub checksum: u32, pub designations: STR, pub posix_tz: Option>, } #[derive(Clone, Copy, Debug)] pub struct TzifLocalTimeType { pub offset: i32, pub is_dst: bool, pub designation: (u8, u8), // inclusive..exclusive pub indicator: TzifIndicator, } /// This enum corresponds to the possible indicator values for standard/wall /// and UT/local. /// /// Note that UT+Wall is not allowed. /// /// I honestly have no earthly clue what they mean. I've read the section about /// them in RFC 8536 several times and I can't make sense of it. I've even /// looked at data files that have these set and still can't make sense of /// them. I've even looked at what other datetime libraries do with these, and /// they all seem to just ignore them. Like, WTF. I've spent the last couple /// months of my life steeped in time, and I just cannot figure this out. Am I /// just dumb? /// /// Anyway, we parse them, but otherwise ignore them because that's what all /// the cool kids do. /// /// The default is `LocalWall`, which also occurs when no indicators are /// present. /// /// I tried again and still don't get it. Here's a dump for `Pacific/Honolulu`: /// /// ```text /// $ ./scripts/jiff-debug tzif /usr/share/zoneinfo/Pacific/Honolulu /// TIME ZONE NAME /// /usr/share/zoneinfo/Pacific/Honolulu /// LOCAL TIME TYPES /// 000: offset=-10:31:26, is_dst=false, designation=LMT, indicator=local/wall /// 001: offset=-10:30, is_dst=false, designation=HST, indicator=local/wall /// 002: offset=-09:30, is_dst=true, designation=HDT, indicator=local/wall /// 003: offset=-09:30, is_dst=true, designation=HWT, indicator=local/wall /// 004: offset=-09:30, is_dst=true, designation=HPT, indicator=ut/std /// 005: offset=-10, is_dst=false, designation=HST, indicator=local/wall /// TRANSITIONS /// 0000: -9999-01-02T01:59:59 :: -377705023201 :: type=0, -10:31:26, is_dst=false, LMT, local/wall /// 0001: 1896-01-13T22:31:26 :: -2334101314 :: type=1, -10:30, is_dst=false, HST, local/wall /// 0002: 1933-04-30T12:30:00 :: -1157283000 :: type=2, -09:30, is_dst=true, HDT, local/wall /// 0003: 1933-05-21T21:30:00 :: -1155436200 :: type=1, -10:30, is_dst=false, HST, local/wall /// 0004: 1942-02-09T12:30:00 :: -880198200 :: type=3, -09:30, is_dst=true, HWT, local/wall /// 0005: 1945-08-14T23:00:00 :: -769395600 :: type=4, -09:30, is_dst=true, HPT, ut/std /// 0006: 1945-09-30T11:30:00 :: -765376200 :: type=1, -10:30, is_dst=false, HST, local/wall /// 0007: 1947-06-08T12:30:00 :: -712150200 :: type=5, -10, is_dst=false, HST, local/wall /// POSIX TIME ZONE STRING /// HST10 /// ``` /// /// See how type 004 has a ut/std indicator? What the fuck does that mean? /// All transitions are defined in terms of UTC. I confirmed this with `zdump`: /// /// ```text /// $ zdump -v Pacific/Honolulu | rg 1945 /// Pacific/Honolulu Tue Aug 14 22:59:59 1945 UT = Tue Aug 14 13:29:59 1945 HWT isdst=1 gmtoff=-34200 /// Pacific/Honolulu Tue Aug 14 23:00:00 1945 UT = Tue Aug 14 13:30:00 1945 HPT isdst=1 gmtoff=-34200 /// Pacific/Honolulu Sun Sep 30 11:29:59 1945 UT = Sun Sep 30 01:59:59 1945 HPT isdst=1 gmtoff=-34200 /// Pacific/Honolulu Sun Sep 30 11:30:00 1945 UT = Sun Sep 30 01:00:00 1945 HST isdst=0 gmtoff=-37800 /// ``` /// /// The times match up. All of them. The indicators don't seem to make a /// difference. I'm clearly missing something. #[derive(Clone, Copy, Debug)] pub enum TzifIndicator { LocalWall, LocalStandard, UTStandard, } /// The set of transitions in TZif data, laid out in column orientation. /// /// The column orientation is used to make TZ lookups faster. Specifically, /// for finding an offset for a timestamp, we do a binary search on /// `timestamps`. For finding an offset for a local datetime, we do a binary /// search on `civil_starts`. By making these two distinct sequences with /// nothing else in them, we make them as small as possible and thus improve /// cache locality. /// /// All sequences in this type are in correspondence with one another. They /// are all guaranteed to have the same length. #[derive(Clone, Debug)] pub struct TzifTransitions { /// The timestamp at which this transition begins. pub timestamps: TIMESTAMPS, /// The wall clock time for when a transition begins. pub civil_starts: STARTS, /// The wall clock time for when a transition ends. /// /// This is only non-zero when the transition kind is a gap or a fold. pub civil_ends: ENDS, /// Any other relevant data about a transition, such as its local type /// index and the transition kind. pub infos: INFOS, } /// TZif transition info beyond the timestamp and civil datetime. /// /// For example, this contains a transition's "local type index," which in /// turn gives access to the offset (among other metadata) for that transition. #[derive(Clone, Copy, Debug)] pub struct TzifTransitionInfo { /// The index into the sequence of local time type records. This is what /// provides the correct offset (from UTC) that is active beginning at /// this transition. pub type_index: u8, /// The boundary condition for quickly determining if a given wall clock /// time is ambiguous (i.e., falls in a gap or a fold). pub kind: TzifTransitionKind, } /// The kind of a transition. /// /// This is used when trying to determine the offset for a local datetime. It /// indicates how the corresponding civil datetimes in `civil_starts` and /// `civil_ends` should be interpreted. That is, there are three possible /// cases: /// /// 1. The offset of this transition is equivalent to the offset of the /// previous transition. That means there are no ambiguous civil datetimes /// between the transitions. This can occur, e.g., when the time zone /// abbreviation changes. /// 2. The offset of the transition is greater than the offset of the previous /// transition. That means there is a "gap" in local time between the /// transitions. This typically corresponds to entering daylight saving time. /// It is usually, but not always, 1 hour. /// 3. The offset of the transition is less than the offset of the previous /// transition. That means there is a "fold" in local time where time is /// repeated. This typically corresponds to leaving daylight saving time. It /// is usually, but not always, 1 hour. /// /// # More explanation /// /// This, when combined with `civil_starts` and `civil_ends` in /// `TzifTransitions`, explicitly represents ambiguous wall clock times that /// occur at the boundaries of transitions. /// /// The start of the wall clock time is always the earlier possible wall clock /// time that could occur with this transition's corresponding offset. For a /// gap, it's the previous transition's offset. For a fold, it's the current /// transition's offset. /// /// For example, DST for `America/New_York` began on `2024-03-10T07:00:00+00`. /// The offset prior to this instant in time is `-05`, corresponding /// to standard time (EST). Thus, in wall clock time, DST began at /// `2024-03-10T02:00:00`. And since this is a DST transition that jumps ahead /// an hour, the start of DST also corresponds to the start of a gap. That is, /// the times `02:00:00` through `02:59:59` never appear on a clock for this /// hour. The question is thus: which offset should we apply to `02:00:00`? /// We could apply the offset from the earlier transition `-05` and get /// `2024-03-10T01:00:00-05` (that's `2024-03-10T06:00:00+00`), or we could /// apply the offset from the later transition `-04` and get /// `2024-03-10T03:00:00-04` (that's `2024-03-10T07:00:00+00`). /// /// So in the above, we would have a `Gap` variant where `start` (inclusive) is /// `2024-03-10T02:00:00` and `end` (exclusive) is `2024-03-10T03:00:00`. /// /// The fold case is the same idea, but where the same time is repeated. /// For example, in `America/New_York`, standard time began on /// `2024-11-03T06:00:00+00`. The offset prior to this instant in time /// is `-04`, corresponding to DST (EDT). Thus, in wall clock time, DST /// ended at `2024-11-03T02:00:00`. However, since this is a fold, the /// actual set of ambiguous times begins at `2024-11-03T01:00:00` and /// ends at `2024-11-03T01:59:59.999999999`. That is, the wall clock time /// `2024-11-03T02:00:00` is unambiguous. /// /// So in the fold case above, we would have a `Fold` variant where /// `start` (inclusive) is `2024-11-03T01:00:00` and `end` (exclusive) is /// `2024-11-03T02:00:00`. /// /// Since this gets bundled in with the sorted sequence of transitions, we'll /// use the "start" time in all three cases as our target of binary search. /// Once we land on a transition, we'll know our given wall clock time is /// greater than or equal to its start wall clock time. At that point, to /// determine if there is ambiguity, we merely need to determine if the given /// wall clock time is less than the corresponding `end` time. If it is, then /// it falls in a gap or fold. Otherwise, it's unambiguous. /// /// Note that we could compute these datetime values while searching for the /// correct transition, but there's a fair bit of math involved in going /// between timestamps (which is what TZif gives us) and calendar datetimes /// (which is what we're given as input). It is also necessary that we offset /// the timestamp given in TZif at some point, since it is in UTC and the /// datetime given is in wall clock time. So I decided it would be worth /// pre-computing what we need in terms of what the input is. This way, we /// don't need to do any conversions, or indeed, any arithmetic at all, for /// time zone lookups. We *could* store these as transitions, but then the /// input datetime would need to be converted to a timestamp before searching /// the transitions. #[derive(Clone, Copy, Debug)] pub enum TzifTransitionKind { /// This transition cannot possibly lead to an unambiguous offset because /// its offset is equivalent to the offset of the previous transition. /// /// Has an entry in `civil_starts`, but corresponding entry in `civil_ends` /// is always zeroes (i.e., meaningless). Unambiguous, /// This occurs when this transition's offset is strictly greater than the /// previous transition's offset. This effectively results in a "gap" of /// time equal to the difference in the offsets between the two /// transitions. /// /// Has an entry in `civil_starts` for when the gap starts (inclusive) in /// local time. Also has an entry in `civil_ends` for when the fold ends /// (exclusive) in local time. Gap, /// This occurs when this transition's offset is strictly less than the /// previous transition's offset. This results in a "fold" of time where /// the two transitions have an overlap where it is ambiguous which one /// applies given a wall clock time. In effect, a span of time equal to the /// difference in the offsets is repeated. /// /// Has an entry in `civil_starts` for when the fold starts (inclusive) in /// local time. Also has an entry in `civil_ends` for when the fold ends /// (exclusive) in local time. Fold, } /// The representation we use to represent a civil datetime. /// /// We don't use `shared::util::itime::IDateTime` here because we specifically /// do not need to represent fractional seconds. This lets us easily represent /// what we need in 8 bytes instead of the 12 bytes used by `IDateTime`. /// /// Moreover, we pack the fields into a single `i64` to make comparisons /// extremely cheap. This is especially useful since we do a binary search on /// `&[TzifDateTime]` when doing a TZ lookup for a civil datetime. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct TzifDateTime { bits: i64, } impl TzifDateTime { pub const ZERO: TzifDateTime = TzifDateTime::new(0, 0, 0, 0, 0, 0); pub const fn new( year: i16, month: i8, day: i8, hour: i8, minute: i8, second: i8, ) -> TzifDateTime { let mut bits = (year as u64) << 48; bits |= (month as u64) << 40; bits |= (day as u64) << 32; bits |= (hour as u64) << 24; bits |= (minute as u64) << 16; bits |= (second as u64) << 8; // The least significant 8 bits remain 0. TzifDateTime { bits: bits as i64 } } pub const fn year(self) -> i16 { (self.bits as u64 >> 48) as u16 as i16 } pub const fn month(self) -> i8 { (self.bits as u64 >> 40) as u8 as i8 } pub const fn day(self) -> i8 { (self.bits as u64 >> 32) as u8 as i8 } pub const fn hour(self) -> i8 { (self.bits as u64 >> 24) as u8 as i8 } pub const fn minute(self) -> i8 { (self.bits as u64 >> 16) as u8 as i8 } pub const fn second(self) -> i8 { (self.bits as u64 >> 8) as u8 as i8 } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PosixTimeZone { pub std_abbrev: ABBREV, pub std_offset: PosixOffset, pub dst: Option>, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PosixDst { pub abbrev: ABBREV, pub offset: PosixOffset, pub rule: PosixRule, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PosixRule { pub start: PosixDayTime, pub end: PosixDayTime, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PosixDayTime { pub date: PosixDay, pub time: PosixTime, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PosixDay { /// Julian day in a year, no counting for leap days. /// /// Valid range is `1..=365`. JulianOne(i16), /// Julian day in a year, counting for leap days. /// /// Valid range is `0..=365`. JulianZero(i16), /// The nth weekday of a month. WeekdayOfMonth { /// The month. /// /// Valid range is: `1..=12`. month: i8, /// The week. /// /// Valid range is `1..=5`. /// /// One interesting thing to note here (or my interpretation anyway), /// is that a week of `4` means the "4th weekday in a month" where as /// a week of `5` means the "last weekday in a month, even if it's the /// 4th weekday." week: i8, /// The weekday. /// /// Valid range is `0..=6`, with `0` corresponding to Sunday. weekday: i8, }, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PosixTime { pub second: i32, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PosixOffset { pub second: i32, } // Does not require `alloc`, but is only used when `alloc` is enabled. pub(crate) mod crc32; pub(crate) mod posix; pub(crate) mod tzif; pub(crate) mod util; jiff-static-0.2.16/src/shared/posix.rs000064400000000000000000003167711046102023000157010ustar 00000000000000// auto-generated by: jiff-cli generate shared use core::fmt::Debug; use super::{ util::{ array_str::Abbreviation, error::{err, Error}, escape::{Byte, Bytes}, itime::{ IAmbiguousOffset, IDate, IDateTime, IOffset, ITime, ITimeSecond, ITimestamp, IWeekday, }, }, PosixDay, PosixDayTime, PosixDst, PosixOffset, PosixRule, PosixTime, PosixTimeZone, }; impl PosixTimeZone { /// Parse a POSIX `TZ` environment variable, assuming it's a rule and not /// an implementation defined value, from the given bytes. pub fn parse(bytes: &[u8]) -> Result, Error> { // We enable the IANA v3+ extensions here. (Namely, that the time // specification hour value has the range `-167..=167` instead of // `0..=24`.) Requiring strict POSIX rules doesn't seem necessary // since the extension is a strict superset. Plus, GNU tooling // seems to accept the extension. let parser = Parser { ianav3plus: true, ..Parser::new(bytes) }; parser.parse() } } impl + Debug> PosixTimeZone { /// Returns the appropriate time zone offset to use for the given /// timestamp. /// /// If you need information like whether the offset is in DST or not, or /// the time zone abbreviation, then use `PosixTimeZone::to_offset_info`. /// But that API may be more expensive to use, so only use it if you need /// the additional data. pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset { let std_offset = self.std_offset.to_ioffset(); if self.dst.is_none() { return std_offset; } let dt = timestamp.to_datetime(IOffset::UTC); self.dst_info_utc(dt.date.year) .filter(|dst_info| dst_info.in_dst(dt)) .map(|dst_info| dst_info.offset().to_ioffset()) .unwrap_or_else(|| std_offset) } /// Returns the appropriate time zone offset to use for the given /// timestamp. /// /// This also includes whether the offset returned should be considered /// to be "DST" or not, along with the time zone abbreviation (e.g., EST /// for standard time in New York, and EDT for DST in New York). pub(crate) fn to_offset_info( &self, timestamp: ITimestamp, ) -> (IOffset, &'_ str, bool) { let std_offset = self.std_offset.to_ioffset(); if self.dst.is_none() { return (std_offset, self.std_abbrev.as_ref(), false); } let dt = timestamp.to_datetime(IOffset::UTC); self.dst_info_utc(dt.date.year) .filter(|dst_info| dst_info.in_dst(dt)) .map(|dst_info| { ( dst_info.offset().to_ioffset(), dst_info.dst.abbrev.as_ref(), true, ) }) .unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false)) } /// Returns a possibly ambiguous timestamp for the given civil datetime. /// /// The given datetime should correspond to the "wall" clock time of what /// humans use to tell time for this time zone. /// /// Note that "ambiguous timestamp" is represented by the possible /// selection of offsets that could be applied to the given datetime. In /// general, it is only ambiguous around transitions to-and-from DST. The /// ambiguity can arise as a "fold" (when a particular wall clock time is /// repeated) or as a "gap" (when a particular wall clock time is skipped /// entirely). pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset { let year = dt.date.year; let std_offset = self.std_offset.to_ioffset(); let Some(dst_info) = self.dst_info_wall(year) else { return IAmbiguousOffset::Unambiguous { offset: std_offset }; }; let dst_offset = dst_info.offset().to_ioffset(); let diff = dst_offset.second - std_offset.second; // When the difference between DST and standard is positive, that means // STD->DST results in a gap while DST->STD results in a fold. However, // when the difference is negative, that means STD->DST results in a // fold while DST->STD results in a gap. The former is by far the most // common. The latter is a bit weird, but real cases do exist. For // example, Dublin has DST in winter (UTC+01) and STD in the summer // (UTC+00). // // When the difference is zero, then we have a weird POSIX time zone // where a DST transition rule was specified, but was set to explicitly // be the same as STD. In this case, there can be no ambiguity. (The // zero case is strictly redundant. Both the diff < 0 and diff > 0 // cases handle the zero case correctly. But we write it out for // clarity.) if diff == 0 { debug_assert_eq!(std_offset, dst_offset); IAmbiguousOffset::Unambiguous { offset: std_offset } } else if diff.is_negative() { // For DST transitions that always move behind one hour, ambiguous // timestamps only occur when the given civil datetime falls in the // standard time range. if dst_info.in_dst(dt) { IAmbiguousOffset::Unambiguous { offset: dst_offset } } else { let fold_start = dst_info.start.saturating_add_seconds(diff); let gap_end = dst_info.end.saturating_add_seconds(diff.saturating_neg()); if fold_start <= dt && dt < dst_info.start { IAmbiguousOffset::Fold { before: std_offset, after: dst_offset, } } else if dst_info.end <= dt && dt < gap_end { IAmbiguousOffset::Gap { before: dst_offset, after: std_offset, } } else { IAmbiguousOffset::Unambiguous { offset: std_offset } } } } else { // For DST transitions that always move ahead one hour, ambiguous // timestamps only occur when the given civil datetime falls in the // DST range. if !dst_info.in_dst(dt) { IAmbiguousOffset::Unambiguous { offset: std_offset } } else { // PERF: I wonder if it makes sense to pre-compute these? // Probably not, because we have to do it based on year of // datetime given. But if we ever add a "caching" layer for // POSIX time zones, then it might be worth adding these to it. let gap_end = dst_info.start.saturating_add_seconds(diff); let fold_start = dst_info.end.saturating_add_seconds(diff.saturating_neg()); if dst_info.start <= dt && dt < gap_end { IAmbiguousOffset::Gap { before: std_offset, after: dst_offset, } } else if fold_start <= dt && dt < dst_info.end { IAmbiguousOffset::Fold { before: dst_offset, after: std_offset, } } else { IAmbiguousOffset::Unambiguous { offset: dst_offset } } } } } /// Returns the timestamp of the most recent time zone transition prior /// to the timestamp given. If one doesn't exist, `None` is returned. pub(crate) fn previous_transition( &self, timestamp: ITimestamp, ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> { let dt = timestamp.to_datetime(IOffset::UTC); let dst_info = self.dst_info_utc(dt.date.year)?; let (earlier, later) = dst_info.ordered(); let (prev, dst_info) = if dt > later { (later, dst_info) } else if dt > earlier { (earlier, dst_info) } else { let prev_year = dt.date.prev_year().ok()?; let dst_info = self.dst_info_utc(prev_year)?; let (_, later) = dst_info.ordered(); (later, dst_info) }; let timestamp = prev.to_timestamp_checked(IOffset::UTC)?; let dt = timestamp.to_datetime(IOffset::UTC); let (offset, abbrev, dst) = if dst_info.in_dst(dt) { (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true) } else { (&self.std_offset, self.std_abbrev.as_ref(), false) }; Some((timestamp, offset.to_ioffset(), abbrev, dst)) } /// Returns the timestamp of the soonest time zone transition after the /// timestamp given. If one doesn't exist, `None` is returned. pub(crate) fn next_transition( &self, timestamp: ITimestamp, ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> { let dt = timestamp.to_datetime(IOffset::UTC); let dst_info = self.dst_info_utc(dt.date.year)?; let (earlier, later) = dst_info.ordered(); let (next, dst_info) = if dt < earlier { (earlier, dst_info) } else if dt < later { (later, dst_info) } else { let next_year = dt.date.next_year().ok()?; let dst_info = self.dst_info_utc(next_year)?; let (earlier, _) = dst_info.ordered(); (earlier, dst_info) }; let timestamp = next.to_timestamp_checked(IOffset::UTC)?; let dt = timestamp.to_datetime(IOffset::UTC); let (offset, abbrev, dst) = if dst_info.in_dst(dt) { (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true) } else { (&self.std_offset, self.std_abbrev.as_ref(), false) }; Some((timestamp, offset.to_ioffset(), abbrev, dst)) } /// Returns the range in which DST occurs. /// /// The civil datetimes returned are in UTC. This is useful for determining /// whether a timestamp is in DST or not. fn dst_info_utc(&self, year: i16) -> Option> { let dst = self.dst.as_ref()?; // DST time starts with respect to standard time, so offset it by the // standard offset. let start = dst.rule.start.to_datetime(year, self.std_offset.to_ioffset()); // DST time ends with respect to DST time, so offset it by the DST // offset. let mut end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset()); // This is a whacky special case when DST is permanent, but the math // using to calculate the start/end datetimes ends up leaving a gap // for standard time to appear. In which case, it's possible for a // timestamp at the end of a calendar year to get standard time when // it really should be DST. // // We detect this case by re-interpreting the end of the boundary using // the standard offset. If we get a datetime that is in a different // year, then it follows that standard time is actually impossible to // occur. // // These weird POSIX time zones can occur as the TZ strings in // a TZif file compiled using rearguard semantics. For example, // `Africa/Casablanca` has: // // XXX-2<+01>-1,0/0,J365/23 // // Notice here that DST is actually one hour *behind* (it is usually // one hour *ahead*) _and_ it ends at 23:00:00 on the last day of the // year. But if it ends at 23:00, then jumping to standard time moves // the clocks *forward*. Which would bring us to 00:00:00 on the first // of the next year... but that is when DST begins! Hence, DST is // permanent. // // Ideally, this could just be handled by our math automatically. But // I couldn't figure out how to make it work. In particular, in the // above example for year 2087, we get // // start == 2087-01-01T00:00:00Z // end == 2087-12-31T22:00:00Z // // Which leaves a two hour gap for a timestamp to get erroneously // categorized as standard time. // // ... so we special case this. We could pre-compute whether a POSIX // time zone is in permanent DST at construction time, but it's not // obvious to me that it's worth it. Especially since this is an // exceptionally rare case. // // Note that I did try to consult tzcode's (incredibly inscrutable) // `localtime` implementation to figure out how they deal with it. At // first, it looks like they don't have any special handling for this // case. But looking more closely, they skip any time zone transitions // generated by POSIX time zones whose rule spans more than 1 year: // // https://github.com/eggert/tz/blob/8d65db9786753f3b263087e31c59d191561d63e3/localtime.c#L1717-L1735 // // By just ignoring them, I think it achieves the desired effect of // permanent DST. But I'm not 100% confident in my understanding of // the code. if start.date.month == 1 && start.date.day == 1 && start.time == ITime::MIN // NOTE: This should come last because it is potentially expensive. && year != end.saturating_add_seconds(self.std_offset.second).date.year { end = IDateTime { date: IDate { year, month: 12, day: 31 }, time: ITime::MAX, }; } Some(DstInfo { dst, start, end }) } /// Returns the range in which DST occurs. /// /// The civil datetimes returned are in "wall clock time." That is, they /// represent the transitions as they are seen from humans reading a clock /// within the geographic location of that time zone. fn dst_info_wall(&self, year: i16) -> Option> { let dst = self.dst.as_ref()?; // POSIX time zones express their DST transitions in terms of wall // clock time. Since this method specifically is returning wall // clock times, we don't want to offset our datetimes at all. let start = dst.rule.start.to_datetime(year, IOffset::UTC); let end = dst.rule.end.to_datetime(year, IOffset::UTC); Some(DstInfo { dst, start, end }) } /// Returns the DST transition rule. This panics if this time zone doesn't /// have DST. #[cfg(test)] fn rule(&self) -> &PosixRule { &self.dst.as_ref().unwrap().rule } } impl> core::fmt::Display for PosixTimeZone { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!( f, "{}{}", AbbreviationDisplay(self.std_abbrev.as_ref()), self.std_offset )?; if let Some(ref dst) = self.dst { dst.display(&self.std_offset, f)?; } Ok(()) } } impl> PosixDst { fn display( &self, std_offset: &PosixOffset, f: &mut core::fmt::Formatter, ) -> core::fmt::Result { write!(f, "{}", AbbreviationDisplay(self.abbrev.as_ref()))?; // The overwhelming common case is that DST is exactly one hour ahead // of standard time. So common that this is the default. So don't write // the offset if we don't need to. let default = PosixOffset { second: std_offset.second + 3600 }; if self.offset != default { write!(f, "{}", self.offset)?; } write!(f, ",{}", self.rule)?; Ok(()) } } impl core::fmt::Display for PosixRule { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{},{}", self.start, self.end) } } impl PosixDayTime { /// Turns this POSIX datetime spec into a civil datetime in the year given /// with the given offset. The datetimes returned are offset by the given /// offset. For wall clock time, an offset of `0` should be given. For /// UTC time, the offset (standard or DST) corresponding to this time /// spec should be given. /// /// The datetime returned is guaranteed to have a year component equal /// to the year given. This guarantee is upheld even when the datetime /// specification (combined with the offset) would extend past the end of /// the year (or before the start of the year). In this case, the maximal /// (or minimal) datetime for the given year is returned. pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime { let mkmin = || IDateTime { date: IDate { year, month: 1, day: 1 }, time: ITime::MIN, }; let mkmax = || IDateTime { date: IDate { year, month: 12, day: 31 }, time: ITime::MAX, }; let Some(date) = self.date.to_date(year) else { return mkmax() }; // The range on `self.time` is `-604799..=604799`, and the range // on `offset.second` is `-93599..=93599`. Therefore, subtracting // them can never overflow an `i32`. let offset = self.time.second - offset.second; // If the time goes negative or above 86400, then we might have // to adjust our date. let days = offset.div_euclid(86400); let second = offset.rem_euclid(86400); let Ok(date) = date.checked_add_days(days) else { return if offset < 0 { mkmin() } else { mkmax() }; }; if date.year < year { mkmin() } else if date.year > year { mkmax() } else { let time = ITimeSecond { second }.to_time(); IDateTime { date, time } } } } impl core::fmt::Display for PosixDayTime { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", self.date)?; // This is the default time, so don't write it if we // don't need to. if self.time != PosixTime::DEFAULT { write!(f, "/{}", self.time)?; } Ok(()) } } impl PosixDay { /// Convert this date specification to a civil date in the year given. /// /// If this date specification couldn't be turned into a date in the year /// given, then `None` is returned. This happens when `366` is given as /// a day, but the year given is not a leap year. In this case, callers may /// want to assume a datetime that is maximal for the year given. fn to_date(&self, year: i16) -> Option { match *self { PosixDay::JulianOne(day) => { // Parsing validates that our day is 1-365 which will always // succeed for all possible year values. That is, every valid // year has a December 31. Some( IDate::from_day_of_year_no_leap(year, day) .expect("Julian `J day` should be in bounds"), ) } PosixDay::JulianZero(day) => { // OK because our value for `day` is validated to be `0..=365`, // and since it is an `i16`, it is always valid to add 1. // // Also, while `day+1` is guaranteed to be in `1..=366`, it is // possible that `366` is invalid, for when `year` is not a // leap year. In this case, we throw our hands up, and ask the // caller to make a decision for how to deal with it. Why does // POSIX go out of its way to specifically not specify behavior // in error cases? IDate::from_day_of_year(year, day + 1).ok() } PosixDay::WeekdayOfMonth { month, week, weekday } => { let weekday = IWeekday::from_sunday_zero_offset(weekday); let first = IDate { year, month, day: 1 }; let week = if week == 5 { -1 } else { week }; debug_assert!(week == -1 || (1..=4).contains(&week)); // This is maybe non-obvious, but this will always succeed // because it can only fail when the week number is one of // {-5, 0, 5}. Since we've validated that 'week' is in 1..=5, // we know it can't be 0. Moreover, because of the conditional // above and since `5` actually means "last weekday of month," // that case will always translate to `-1`. // // Also, I looked at how other libraries deal with this case, // and almost all of them just do a bunch of inline hairy // arithmetic here. I suppose I could be reduced to such // things if perf called for it, but we have a nice civil date // abstraction. So use it, god damn it. (Well, we did, and now // we have a lower level IDate abstraction. But it's still // an abstraction!) Some( first .nth_weekday_of_month(week, weekday) .expect("nth weekday always exists"), ) } } } } impl core::fmt::Display for PosixDay { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match *self { PosixDay::JulianOne(n) => write!(f, "J{n}"), PosixDay::JulianZero(n) => write!(f, "{n}"), PosixDay::WeekdayOfMonth { month, week, weekday } => { write!(f, "M{month}.{week}.{weekday}") } } } } impl PosixTime { const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 }; } impl core::fmt::Display for PosixTime { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { if self.second.is_negative() { write!(f, "-")?; // The default is positive, so when // positive, we write nothing. } let second = self.second.unsigned_abs(); let h = second / 3600; let m = (second / 60) % 60; let s = second % 60; write!(f, "{h}")?; if m != 0 || s != 0 { write!(f, ":{m:02}")?; if s != 0 { write!(f, ":{s:02}")?; } } Ok(()) } } impl PosixOffset { fn to_ioffset(&self) -> IOffset { IOffset { second: self.second } } } impl core::fmt::Display for PosixOffset { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { // Yes, this is backwards. Blame POSIX. // N.B. `+` is the default, so we don't // need to write that out. if self.second > 0 { write!(f, "-")?; } let second = self.second.unsigned_abs(); let h = second / 3600; let m = (second / 60) % 60; let s = second % 60; write!(f, "{h}")?; if m != 0 || s != 0 { write!(f, ":{m:02}")?; if s != 0 { write!(f, ":{s:02}")?; } } Ok(()) } } /// A helper type for formatting a time zone abbreviation. /// /// Basically, this will write the `<` and `>` quotes if necessary, and /// otherwise write out the abbreviation in its unquoted form. #[derive(Debug)] struct AbbreviationDisplay(S); impl> core::fmt::Display for AbbreviationDisplay { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let s = self.0.as_ref(); if s.chars().any(|ch| ch == '+' || ch == '-') { write!(f, "<{s}>") } else { write!(f, "{s}") } } } /// The daylight saving time (DST) info for a POSIX time zone in a particular /// year. #[derive(Debug, Eq, PartialEq)] struct DstInfo<'a, ABBREV> { /// The DST transition rule that generated this info. dst: &'a PosixDst, /// The start time (inclusive) that DST begins. /// /// Note that this may be greater than `end`. This tends to happen in the /// southern hemisphere. /// /// Note also that this may be in UTC or in wall clock civil /// time. It depends on whether `PosixTimeZone::dst_info_utc` or /// `PosixTimeZone::dst_info_wall` was used. start: IDateTime, /// The end time (exclusive) that DST ends. /// /// Note that this may be less than `start`. This tends to happen in the /// southern hemisphere. /// /// Note also that this may be in UTC or in wall clock civil /// time. It depends on whether `PosixTimeZone::dst_info_utc` or /// `PosixTimeZone::dst_info_wall` was used. end: IDateTime, } impl<'a, ABBREV> DstInfo<'a, ABBREV> { /// Returns true if and only if the given civil datetime ought to be /// considered in DST. fn in_dst(&self, utc_dt: IDateTime) -> bool { if self.start <= self.end { self.start <= utc_dt && utc_dt < self.end } else { !(self.end <= utc_dt && utc_dt < self.start) } } /// Returns the earlier and later times for this DST info. fn ordered(&self) -> (IDateTime, IDateTime) { if self.start <= self.end { (self.start, self.end) } else { (self.end, self.start) } } /// Returns the DST offset. fn offset(&self) -> &PosixOffset { &self.dst.offset } } /// A parser for POSIX time zones. #[derive(Debug)] struct Parser<'s> { /// The `TZ` string that we're parsing. tz: &'s [u8], /// The parser's current position in `tz`. pos: core::cell::Cell, /// Whether to use IANA rules, i.e., when parsing a TZ string in a TZif /// file of version 3 or greater. From `tzfile(5)`: /// /// > First, the hours part of its transition times may be signed and range /// > from `-167` through `167` instead of the POSIX-required unsigned /// > values from `0` through `24`. Second, DST is in effect all year if /// > it starts January 1 at 00:00 and ends December 31 at 24:00 plus the /// > difference between daylight saving and standard time. /// /// At time of writing, I don't think I understand the significance of /// the second part above. (RFC 8536 elaborates that it is meant to be an /// explicit clarification of something that POSIX itself implies.) But the /// first part is clear: it permits the hours to be a bigger range. ianav3plus: bool, } impl<'s> Parser<'s> { /// Create a new parser for extracting a POSIX time zone from the given /// bytes. fn new>(tz: &'s B) -> Parser<'s> { Parser { tz: tz.as_ref(), pos: core::cell::Cell::new(0), ianav3plus: false, } } /// Parses a POSIX time zone from the current position of the parser and /// ensures that the entire TZ string corresponds to a single valid POSIX /// time zone. fn parse(&self) -> Result, Error> { let (time_zone, remaining) = self.parse_prefix()?; if !remaining.is_empty() { return Err(err!( "expected entire TZ string to be a valid POSIX \ time zone, but found `{}` after what would otherwise \ be a valid POSIX TZ string", Bytes(remaining), )); } Ok(time_zone) } /// Parses a POSIX time zone from the current position of the parser and /// returns the remaining input. fn parse_prefix( &self, ) -> Result<(PosixTimeZone, &'s [u8]), Error> { let time_zone = self.parse_posix_time_zone()?; Ok((time_zone, self.remaining())) } /// Parse a POSIX time zone from the current position of the parser. /// /// Upon success, the parser will be positioned immediately following the /// TZ string. fn parse_posix_time_zone( &self, ) -> Result, Error> { if self.is_done() { return Err(err!( "an empty string is not a valid POSIX time zone" )); } let std_abbrev = self .parse_abbreviation() .map_err(|e| err!("failed to parse standard abbreviation: {e}"))?; let std_offset = self .parse_posix_offset() .map_err(|e| err!("failed to parse standard offset: {e}"))?; let mut dst = None; if !self.is_done() && (self.byte().is_ascii_alphabetic() || self.byte() == b'<') { dst = Some(self.parse_posix_dst(&std_offset)?); } Ok(PosixTimeZone { std_abbrev, std_offset, dst }) } /// Parse a DST zone with an optional explicit transition rule. /// /// This assumes the parser is positioned at the first byte of the DST /// abbreviation. /// /// Upon success, the parser will be positioned immediately after the end /// of the DST transition rule (which might just be the abbreviation, but /// might also include explicit start/end datetime specifications). fn parse_posix_dst( &self, std_offset: &PosixOffset, ) -> Result, Error> { let abbrev = self .parse_abbreviation() .map_err(|e| err!("failed to parse DST abbreviation: {e}"))?; if self.is_done() { return Err(err!( "found DST abbreviation `{abbrev}`, but no transition \ rule (this is technically allowed by POSIX, but has \ unspecified behavior)", )); } // This is the default: one hour ahead of standard time. We may // override this if the DST portion specifies an offset. (But it // usually doesn't.) let mut offset = PosixOffset { second: std_offset.second + 3600 }; if self.byte() != b',' { offset = self .parse_posix_offset() .map_err(|e| err!("failed to parse DST offset: {e}"))?; if self.is_done() { return Err(err!( "found DST abbreviation `{abbrev}` and offset \ `{offset}s`, but no transition rule (this is \ technically allowed by POSIX, but has \ unspecified behavior)", offset = offset.second, )); } } if self.byte() != b',' { return Err(err!( "after parsing DST offset in POSIX time zone string, \ found `{}` but expected a ','", Byte(self.byte()), )); } if !self.bump() { return Err(err!( "after parsing DST offset in POSIX time zone string, \ found end of string after a trailing ','", )); } let rule = self.parse_rule()?; Ok(PosixDst { abbrev, offset, rule }) } /// Parse a time zone abbreviation. /// /// This assumes the parser is positioned at the first byte of /// the abbreviation. This is either the first character in the /// abbreviation, or the opening quote of a quoted abbreviation. /// /// Upon success, the parser will be positioned immediately following /// the abbreviation name. /// /// The string returned is guaranteed to be no more than 30 bytes. /// (This restriction is somewhat arbitrary, but it's so we can put /// the abbreviation in a fixed capacity array.) fn parse_abbreviation(&self) -> Result { if self.byte() == b'<' { if !self.bump() { return Err(err!( "found opening '<' quote for abbreviation in \ POSIX time zone string, and expected a name \ following it, but found the end of string instead" )); } self.parse_quoted_abbreviation() } else { self.parse_unquoted_abbreviation() } } /// Parses an unquoted time zone abbreviation. /// /// This assumes the parser is position at the first byte in the /// abbreviation. /// /// Upon success, the parser will be positioned immediately after the /// last byte in the abbreviation. /// /// The string returned is guaranteed to be no more than 30 bytes. /// (This restriction is somewhat arbitrary, but it's so we can put /// the abbreviation in a fixed capacity array.) fn parse_unquoted_abbreviation(&self) -> Result { let start = self.pos(); for i in 0.. { if !self.byte().is_ascii_alphabetic() { break; } if i >= Abbreviation::capacity() { return Err(err!( "expected abbreviation with at most {} bytes, \ but found a longer abbreviation beginning with `{}`", Abbreviation::capacity(), Bytes(&self.tz[start..][..i]), )); } if !self.bump() { break; } } let end = self.pos(); let abbrev = core::str::from_utf8(&self.tz[start..end]).map_err(|_| { // NOTE: I believe this error is technically impossible // since the loop above restricts letters in an // abbreviation to ASCII. So everything from `start` to // `end` is ASCII and thus should be UTF-8. But it doesn't // cost us anything to report an error here in case the // code above evolves somehow. err!( "found abbreviation `{}`, but it is not valid UTF-8", Bytes(&self.tz[start..end]), ) })?; if abbrev.len() < 3 { return Err(err!( "expected abbreviation with 3 or more bytes, but found \ abbreviation {:?} with {} bytes", abbrev, abbrev.len(), )); } // OK because we verified above that the abbreviation // does not exceed `Abbreviation::capacity`. Ok(Abbreviation::new(abbrev).unwrap()) } /// Parses a quoted time zone abbreviation. /// /// This assumes the parser is positioned immediately after the opening /// `<` quote. That is, at the first byte in the abbreviation. /// /// Upon success, the parser will be positioned immediately after the /// closing `>` quote. /// /// The string returned is guaranteed to be no more than 30 bytes. /// (This restriction is somewhat arbitrary, but it's so we can put /// the abbreviation in a fixed capacity array.) fn parse_quoted_abbreviation(&self) -> Result { let start = self.pos(); for i in 0.. { if !self.byte().is_ascii_alphanumeric() && self.byte() != b'+' && self.byte() != b'-' { break; } if i >= Abbreviation::capacity() { return Err(err!( "expected abbreviation with at most {} bytes, \ but found a longer abbreviation beginning with `{}`", Abbreviation::capacity(), Bytes(&self.tz[start..][..i]), )); } if !self.bump() { break; } } let end = self.pos(); let abbrev = core::str::from_utf8(&self.tz[start..end]).map_err(|_| { // NOTE: I believe this error is technically impossible // since the loop above restricts letters in an // abbreviation to ASCII. So everything from `start` to // `end` is ASCII and thus should be UTF-8. But it doesn't // cost us anything to report an error here in case the // code above evolves somehow. err!( "found abbreviation `{}`, but it is not valid UTF-8", Bytes(&self.tz[start..end]), ) })?; if self.is_done() { return Err(err!( "found non-empty quoted abbreviation {abbrev:?}, but \ did not find expected end-of-quoted abbreviation \ '>' character", )); } if self.byte() != b'>' { return Err(err!( "found non-empty quoted abbreviation {abbrev:?}, but \ found `{}` instead of end-of-quoted abbreviation '>' \ character", Byte(self.byte()), )); } self.bump(); if abbrev.len() < 3 { return Err(err!( "expected abbreviation with 3 or more bytes, but found \ abbreviation {abbrev:?} with {} bytes", abbrev.len(), )); } // OK because we verified above that the abbreviation // does not exceed `Abbreviation::capacity`. Ok(Abbreviation::new(abbrev).unwrap()) } /// Parse a POSIX time offset. /// /// This assumes the parser is positioned at the first byte of the /// offset. This can either be a digit (for a positive offset) or the /// sign of the offset (which must be either `-` or `+`). /// /// Upon success, the parser will be positioned immediately after the /// end of the offset. fn parse_posix_offset(&self) -> Result { let sign = self .parse_optional_sign() .map_err(|e| { err!( "failed to parse sign for time offset \ in POSIX time zone string: {e}", ) })? .unwrap_or(1); let hour = self.parse_hour_posix()?; let (mut minute, mut second) = (0, 0); if self.maybe_byte() == Some(b':') { if !self.bump() { return Err(err!( "incomplete time in POSIX timezone (missing minutes)", )); } minute = self.parse_minute()?; if self.maybe_byte() == Some(b':') { if !self.bump() { return Err(err!( "incomplete time in POSIX timezone (missing seconds)", )); } second = self.parse_second()?; } } let mut offset = PosixOffset { second: i32::from(hour) * 3600 }; offset.second += i32::from(minute) * 60; offset.second += i32::from(second); // Yes, we flip the sign, because POSIX is backwards. // For example, `EST5` corresponds to `-05:00`. offset.second *= i32::from(-sign); // Must be true because the parsing routines for hours, minutes // and seconds enforce they are in the ranges -24..=24, 0..=59 // and 0..=59, respectively. assert!( -89999 <= offset.second && offset.second <= 89999, "POSIX offset seconds {} is out of range", offset.second ); Ok(offset) } /// Parses a POSIX DST transition rule. /// /// This assumes the parser is positioned at the first byte in the /// rule. That is, it comes immediately after the DST abbreviation or /// its optional offset. /// /// Upon success, the parser will be positioned immediately after the /// DST transition rule. In typical cases, this corresponds to the end /// of the TZ string. fn parse_rule(&self) -> Result { let start = self.parse_posix_datetime().map_err(|e| { err!("failed to parse start of DST transition rule: {e}") })?; if self.maybe_byte() != Some(b',') || !self.bump() { return Err(err!( "expected end of DST rule after parsing the start \ of the DST rule" )); } let end = self.parse_posix_datetime().map_err(|e| { err!("failed to parse end of DST transition rule: {e}") })?; Ok(PosixRule { start, end }) } /// Parses a POSIX datetime specification. /// /// This assumes the parser is position at the first byte where a /// datetime specification is expected to occur. /// /// Upon success, the parser will be positioned after the datetime /// specification. This will either be immediately after the date, or /// if it's present, the time part of the specification. fn parse_posix_datetime(&self) -> Result { let mut daytime = PosixDayTime { date: self.parse_posix_date()?, time: PosixTime::DEFAULT, }; if self.maybe_byte() != Some(b'/') { return Ok(daytime); } if !self.bump() { return Err(err!( "expected time specification after '/' following a date specification in a POSIX time zone DST transition rule", )); } daytime.time = self.parse_posix_time()?; Ok(daytime) } /// Parses a POSIX date specification. /// /// This assumes the parser is positioned at the first byte of the date /// specification. This can be `J` (for one based Julian day without /// leap days), `M` (for "weekday of month") or a digit starting the /// zero based Julian day with leap days. This routine will validate /// that the position points to one of these possible values. That is, /// the caller doesn't need to parse the `M` or the `J` or the leading /// digit. The caller should just call this routine when it *expect* a /// date specification to follow. /// /// Upon success, the parser will be positioned immediately after the /// date specification. fn parse_posix_date(&self) -> Result { match self.byte() { b'J' => { if !self.bump() { return Err(err!( "expected one-based Julian day after 'J' in date \ specification of a POSIX time zone DST \ transition rule, but got the end of the string \ instead" )); } Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?)) } b'0'..=b'9' => Ok(PosixDay::JulianZero( self.parse_posix_julian_day_with_leap()?, )), b'M' => { if !self.bump() { return Err(err!( "expected month-week-weekday after 'M' in date \ specification of a POSIX time zone DST \ transition rule, but got the end of the string \ instead" )); } let (month, week, weekday) = self.parse_weekday_of_month()?; Ok(PosixDay::WeekdayOfMonth { month, week, weekday }) } _ => Err(err!( "expected 'J', a digit or 'M' at the beginning of a date \ specification of a POSIX time zone DST transition rule, \ but got `{}` instead", Byte(self.byte()), )), } } /// Parses a POSIX Julian day that does not include leap days /// (`1 <= n <= 365`). /// /// This assumes the parser is positioned just after the `J` and at the /// first digit of the Julian day. Upon success, the parser will be /// positioned immediately following the day number. fn parse_posix_julian_day_no_leap(&self) -> Result { let number = self .parse_number_with_upto_n_digits(3) .map_err(|e| err!("invalid one based Julian day: {e}"))?; let number = i16::try_from(number).map_err(|_| { err!( "one based Julian day `{number}` in POSIX time zone \ does not fit into 16-bit integer" ) })?; if !(1 <= number && number <= 365) { return Err(err!( "parsed one based Julian day `{number}`, \ but one based Julian day in POSIX time zone \ must be in range 1..=365", )); } Ok(number) } /// Parses a POSIX Julian day that includes leap days (`0 <= n <= /// 365`). /// /// This assumes the parser is positioned at the first digit of the /// Julian day. Upon success, the parser will be positioned immediately /// following the day number. fn parse_posix_julian_day_with_leap(&self) -> Result { let number = self .parse_number_with_upto_n_digits(3) .map_err(|e| err!("invalid zero based Julian day: {e}"))?; let number = i16::try_from(number).map_err(|_| { err!( "zero based Julian day `{number}` in POSIX time zone \ does not fit into 16-bit integer" ) })?; if !(0 <= number && number <= 365) { return Err(err!( "parsed zero based Julian day `{number}`, \ but zero based Julian day in POSIX time zone \ must be in range 0..=365", )); } Ok(number) } /// Parses a POSIX "weekday of month" specification. /// /// This assumes the parser is positioned just after the `M` byte and /// at the first digit of the month. Upon success, the parser will be /// positioned immediately following the "weekday of the month" that /// was parsed. /// /// The tuple returned is month (1..=12), week (1..=5) and weekday /// (0..=6 with 0=Sunday). fn parse_weekday_of_month(&self) -> Result<(i8, i8, i8), Error> { let month = self.parse_month()?; if self.maybe_byte() != Some(b'.') { return Err(err!( "expected '.' after month `{month}` in \ POSIX time zone rule" )); } if !self.bump() { return Err(err!( "expected week after month `{month}` in \ POSIX time zone rule" )); } let week = self.parse_week()?; if self.maybe_byte() != Some(b'.') { return Err(err!( "expected '.' after week `{week}` in POSIX time zone rule" )); } if !self.bump() { return Err(err!( "expected day-of-week after week `{week}` in \ POSIX time zone rule" )); } let weekday = self.parse_weekday()?; Ok((month, week, weekday)) } /// This parses a POSIX time specification in the format /// `[+/-]hh?[:mm[:ss]]`. /// /// This assumes the parser is positioned at the first `h` (or the /// sign, if present). Upon success, the parser will be positioned /// immediately following the end of the time specification. fn parse_posix_time(&self) -> Result { let (sign, hour) = if self.ianav3plus { let sign = self .parse_optional_sign() .map_err(|e| { err!( "failed to parse sign for transition time \ in POSIX time zone string: {e}", ) })? .unwrap_or(1); let hour = self.parse_hour_ianav3plus()?; (sign, hour) } else { (1, i16::from(self.parse_hour_posix()?)) }; let (mut minute, mut second) = (0, 0); if self.maybe_byte() == Some(b':') { if !self.bump() { return Err(err!( "incomplete transition time in \ POSIX time zone string (missing minutes)", )); } minute = self.parse_minute()?; if self.maybe_byte() == Some(b':') { if !self.bump() { return Err(err!( "incomplete transition time in \ POSIX time zone string (missing seconds)", )); } second = self.parse_second()?; } } let mut time = PosixTime { second: i32::from(hour) * 3600 }; time.second += i32::from(minute) * 60; time.second += i32::from(second); time.second *= i32::from(sign); // Must be true because the parsing routines for hours, minutes // and seconds enforce they are in the ranges -167..=167, 0..=59 // and 0..=59, respectively. assert!( -604799 <= time.second && time.second <= 604799, "POSIX time seconds {} is out of range", time.second ); Ok(time) } /// Parses a month. /// /// This is expected to be positioned at the first digit. Upon success, /// the parser will be positioned after the month (which may contain /// two digits). fn parse_month(&self) -> Result { let number = self.parse_number_with_upto_n_digits(2)?; let number = i8::try_from(number).map_err(|_| { err!( "month `{number}` in POSIX time zone \ does not fit into 8-bit integer" ) })?; if !(1 <= number && number <= 12) { return Err(err!( "parsed month `{number}`, but month in \ POSIX time zone must be in range 1..=12", )); } Ok(number) } /// Parses a week-of-month number. /// /// This is expected to be positioned at the first digit. Upon success, /// the parser will be positioned after the week digit. fn parse_week(&self) -> Result { let number = self.parse_number_with_exactly_n_digits(1)?; let number = i8::try_from(number).map_err(|_| { err!( "week `{number}` in POSIX time zone \ does not fit into 8-bit integer" ) })?; if !(1 <= number && number <= 5) { return Err(err!( "parsed week `{number}`, but week in \ POSIX time zone must be in range 1..=5" )); } Ok(number) } /// Parses a weekday number. /// /// This is expected to be positioned at the first digit. Upon success, /// the parser will be positioned after the week digit. /// /// The weekday returned is guaranteed to be in the range `0..=6`, with /// `0` corresponding to Sunday. fn parse_weekday(&self) -> Result { let number = self.parse_number_with_exactly_n_digits(1)?; let number = i8::try_from(number).map_err(|_| { err!( "weekday `{number}` in POSIX time zone \ does not fit into 8-bit integer" ) })?; if !(0 <= number && number <= 6) { return Err(err!( "parsed weekday `{number}`, but weekday in \ POSIX time zone must be in range `0..=6` \ (with `0` corresponding to Sunday)", )); } Ok(number) } /// Parses an hour from a POSIX time specification with the IANA /// v3+ extension. That is, the hour may be in the range `0..=167`. /// (Callers should parse an optional sign preceding the hour digits /// when IANA V3+ parsing is enabled.) /// /// The hour is allowed to be a single digit (unlike minutes or /// seconds). /// /// This assumes the parser is positioned at the position where the /// first hour digit should occur. Upon success, the parser will be /// positioned immediately after the last hour digit. fn parse_hour_ianav3plus(&self) -> Result { // Callers should only be using this method when IANA v3+ parsing // is enabled. assert!(self.ianav3plus); let number = self .parse_number_with_upto_n_digits(3) .map_err(|e| err!("invalid hour digits: {e}"))?; let number = i16::try_from(number).map_err(|_| { err!( "hour `{number}` in POSIX time zone \ does not fit into 16-bit integer" ) })?; if !(0 <= number && number <= 167) { // The error message says -167 but the check above uses 0. // This is because the caller is responsible for parsing // the sign. return Err(err!( "parsed hour `{number}`, but hour in IANA v3+ \ POSIX time zone must be in range `-167..=167`", )); } Ok(number) } /// Parses an hour from a POSIX time specification, with the allowed /// range being `0..=24`. /// /// The hour is allowed to be a single digit (unlike minutes or /// seconds). /// /// This assumes the parser is positioned at the position where the /// first hour digit should occur. Upon success, the parser will be /// positioned immediately after the last hour digit. fn parse_hour_posix(&self) -> Result { let number = self .parse_number_with_upto_n_digits(2) .map_err(|e| err!("invalid hour digits: {e}"))?; let number = i8::try_from(number).map_err(|_| { err!( "hour `{number}` in POSIX time zone \ does not fit into 8-bit integer" ) })?; if !(0 <= number && number <= 24) { return Err(err!( "parsed hour `{number}`, but hour in \ POSIX time zone must be in range `0..=24`", )); } Ok(number) } /// Parses a minute from a POSIX time specification. /// /// The minute must be exactly two digits. /// /// This assumes the parser is positioned at the position where the /// first minute digit should occur. Upon success, the parser will be /// positioned immediately after the second minute digit. fn parse_minute(&self) -> Result { let number = self .parse_number_with_exactly_n_digits(2) .map_err(|e| err!("invalid minute digits: {e}"))?; let number = i8::try_from(number).map_err(|_| { err!( "minute `{number}` in POSIX time zone \ does not fit into 8-bit integer" ) })?; if !(0 <= number && number <= 59) { return Err(err!( "parsed minute `{number}`, but minute in \ POSIX time zone must be in range `0..=59`", )); } Ok(number) } /// Parses a second from a POSIX time specification. /// /// The second must be exactly two digits. /// /// This assumes the parser is positioned at the position where the /// first second digit should occur. Upon success, the parser will be /// positioned immediately after the second second digit. fn parse_second(&self) -> Result { let number = self .parse_number_with_exactly_n_digits(2) .map_err(|e| err!("invalid second digits: {e}"))?; let number = i8::try_from(number).map_err(|_| { err!( "second `{number}` in POSIX time zone \ does not fit into 8-bit integer" ) })?; if !(0 <= number && number <= 59) { return Err(err!( "parsed second `{number}`, but second in \ POSIX time zone must be in range `0..=59`", )); } Ok(number) } /// Parses a signed 64-bit integer expressed in exactly `n` digits. /// /// If `n` digits could not be found (or if the `TZ` string ends before /// `n` digits could be found), then this returns an error. /// /// This assumes that `n >= 1` and that the parser is positioned at the /// first digit. Upon success, the parser is positioned immediately /// after the `n`th digit. fn parse_number_with_exactly_n_digits( &self, n: usize, ) -> Result { assert!(n >= 1, "numbers must have at least 1 digit"); let start = self.pos(); let mut number: i32 = 0; for i in 0..n { if self.is_done() { return Err(err!("expected {n} digits, but found {i}")); } let byte = self.byte(); let digit = match byte.checked_sub(b'0') { None => { return Err(err!( "invalid digit, expected 0-9 but got {}", Byte(byte), )); } Some(digit) if digit > 9 => { return Err(err!( "invalid digit, expected 0-9 but got {}", Byte(byte), )) } Some(digit) => { debug_assert!((0..=9).contains(&digit)); i32::from(digit) } }; number = number .checked_mul(10) .and_then(|n| n.checked_add(digit)) .ok_or_else(|| { err!( "number `{}` too big to parse into 64-bit integer", Bytes(&self.tz[start..][..i]), ) })?; self.bump(); } Ok(number) } /// Parses a signed 64-bit integer expressed with up to `n` digits and /// at least 1 digit. /// /// This assumes that `n >= 1` and that the parser is positioned at the /// first digit. Upon success, the parser is position immediately after /// the last digit (which can be at most `n`). fn parse_number_with_upto_n_digits(&self, n: usize) -> Result { assert!(n >= 1, "numbers must have at least 1 digit"); let start = self.pos(); let mut number: i32 = 0; for i in 0..n { if self.is_done() || !self.byte().is_ascii_digit() { if i == 0 { return Err(err!("invalid number, no digits found")); } break; } let digit = i32::from(self.byte() - b'0'); number = number .checked_mul(10) .and_then(|n| n.checked_add(digit)) .ok_or_else(|| { err!( "number `{}` too big to parse into 64-bit integer", Bytes(&self.tz[start..][..i]), ) })?; self.bump(); } Ok(number) } /// Parses an optional sign. /// /// This assumes the parser is positioned at the position where a /// positive or negative sign is permitted. If one exists, then it /// is consumed and returned. Moreover, if one exists, then this /// guarantees that it is not the last byte in the input. That is, upon /// success, it is valid to call `self.byte()`. fn parse_optional_sign(&self) -> Result, Error> { if self.is_done() { return Ok(None); } Ok(match self.byte() { b'-' => { if !self.bump() { return Err(err!( "expected digit after '-' sign, \ but got end of input", )); } Some(-1) } b'+' => { if !self.bump() { return Err(err!( "expected digit after '+' sign, \ but got end of input", )); } Some(1) } _ => None, }) } } /// Helper routines for parsing a POSIX `TZ` string. impl<'s> Parser<'s> { /// Bump the parser to the next byte. /// /// If the end of the input has been reached, then `false` is returned. fn bump(&self) -> bool { if self.is_done() { return false; } self.pos.set( self.pos().checked_add(1).expect("pos cannot overflow usize"), ); !self.is_done() } /// Returns true if the next call to `bump` would return false. fn is_done(&self) -> bool { self.pos() == self.tz.len() } /// Return the byte at the current position of the parser. /// /// This panics if the parser is positioned at the end of the TZ /// string. fn byte(&self) -> u8 { self.tz[self.pos()] } /// Return the byte at the current position of the parser. If the TZ /// string has been exhausted, then this returns `None`. fn maybe_byte(&self) -> Option { self.tz.get(self.pos()).copied() } /// Return the current byte offset of the parser. /// /// The offset starts at `0` from the beginning of the TZ string. fn pos(&self) -> usize { self.pos.get() } /// Returns the remaining bytes of the TZ string. /// /// This includes `self.byte()`. It may be empty. fn remaining(&self) -> &'s [u8] { &self.tz[self.pos()..] } } // Tests require parsing, and parsing requires alloc. #[cfg(test)] mod tests { use alloc::string::ToString; use super::*; fn posix_time_zone( input: impl AsRef<[u8]>, ) -> PosixTimeZone { let input = input.as_ref(); let tz = PosixTimeZone::parse(input).unwrap(); // While we're here, assert that converting the TZ back // to a string matches what we got. In the original version // of the POSIX TZ parser, we were very meticulous about // capturing the exact AST of the time zone. But I've // since simplified the data structure considerably such // that it is lossy in terms of what was actually parsed // (but of course, not lossy in terms of the semantic // meaning of the time zone). // // So to account for this, we serialize to a string and // then parse it back. We should get what we started with. let reparsed = PosixTimeZone::parse(tz.to_string().as_bytes()).unwrap(); assert_eq!(tz, reparsed); assert_eq!(tz.to_string(), reparsed.to_string()); tz } fn parser(s: &str) -> Parser<'_> { Parser::new(s.as_bytes()) } fn date(year: i16, month: i8, day: i8) -> IDate { IDate { year, month, day } } #[test] fn parse() { let p = parser("NZST-12NZDT,J60,J300"); assert_eq!( p.parse().unwrap(), PosixTimeZone { std_abbrev: "NZST".into(), std_offset: PosixOffset { second: 12 * 60 * 60 }, dst: Some(PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 13 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(60), time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianOne(300), time: PosixTime { second: 2 * 60 * 60 }, }, }, }), }, ); let p = Parser::new("NZST-12NZDT,J60,J300WAT"); assert!(p.parse().is_err()); } #[test] fn parse_posix_time_zone() { let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3"); assert_eq!( p.parse_posix_time_zone().unwrap(), PosixTimeZone { std_abbrev: "NZST".into(), std_offset: PosixOffset { second: 12 * 60 * 60 }, dst: Some(PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 13 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0, }, time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 4, week: 1, weekday: 0, }, time: PosixTime { second: 3 * 60 * 60 }, }, }, }), }, ); let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT"); assert_eq!( p.parse_posix_time_zone().unwrap(), PosixTimeZone { std_abbrev: "NZST".into(), std_offset: PosixOffset { second: 12 * 60 * 60 }, dst: Some(PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 13 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0, }, time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 4, week: 1, weekday: 0, }, time: PosixTime { second: 3 * 60 * 60 }, }, }, }), }, ); let p = Parser::new("NZST-12NZDT,J60,J300"); assert_eq!( p.parse_posix_time_zone().unwrap(), PosixTimeZone { std_abbrev: "NZST".into(), std_offset: PosixOffset { second: 12 * 60 * 60 }, dst: Some(PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 13 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(60), time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianOne(300), time: PosixTime { second: 2 * 60 * 60 }, }, }, }), }, ); let p = Parser::new("NZST-12NZDT,J60,J300WAT"); assert_eq!( p.parse_posix_time_zone().unwrap(), PosixTimeZone { std_abbrev: "NZST".into(), std_offset: PosixOffset { second: 12 * 60 * 60 }, dst: Some(PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 13 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(60), time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianOne(300), time: PosixTime { second: 2 * 60 * 60 }, }, }, }), }, ); } #[test] fn parse_posix_dst() { let p = Parser::new("NZDT,M9.5.0,M4.1.0/3"); assert_eq!( p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 13 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0, }, time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 4, week: 1, weekday: 0, }, time: PosixTime { second: 3 * 60 * 60 }, }, }, }, ); let p = Parser::new("NZDT,J60,J300"); assert_eq!( p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 13 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(60), time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianOne(300), time: PosixTime { second: 2 * 60 * 60 }, }, }, }, ); let p = Parser::new("NZDT-7,J60,J300"); assert_eq!( p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: 7 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(60), time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianOne(300), time: PosixTime { second: 2 * 60 * 60 }, }, }, }, ); let p = Parser::new("NZDT+7,J60,J300"); assert_eq!( p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: -7 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(60), time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianOne(300), time: PosixTime { second: 2 * 60 * 60 }, }, }, }, ); let p = Parser::new("NZDT7,J60,J300"); assert_eq!( p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), PosixDst { abbrev: "NZDT".into(), offset: PosixOffset { second: -7 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(60), time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianOne(300), time: PosixTime { second: 2 * 60 * 60 }, }, }, }, ); let p = Parser::new("NZDT7,"); assert!(p .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }) .is_err()); let p = Parser::new("NZDT7!"); assert!(p .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }) .is_err()); } #[test] fn parse_abbreviation() { let p = Parser::new("ABC"); assert_eq!(p.parse_abbreviation().unwrap(), "ABC"); let p = Parser::new(""); assert_eq!(p.parse_abbreviation().unwrap(), "ABC"); let p = Parser::new("<+09>"); assert_eq!(p.parse_abbreviation().unwrap(), "+09"); let p = Parser::new("+09"); assert!(p.parse_abbreviation().is_err()); } #[test] fn parse_unquoted_abbreviation() { let p = Parser::new("ABC"); assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC"); let p = Parser::new("ABCXYZ"); assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ"); let p = Parser::new("ABC123"); assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC"); let tz = "a".repeat(30); let p = Parser::new(&tz); assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz); let p = Parser::new("a"); assert!(p.parse_unquoted_abbreviation().is_err()); let p = Parser::new("ab"); assert!(p.parse_unquoted_abbreviation().is_err()); let p = Parser::new("ab1"); assert!(p.parse_unquoted_abbreviation().is_err()); let tz = "a".repeat(31); let p = Parser::new(&tz); assert!(p.parse_unquoted_abbreviation().is_err()); let p = Parser::new(b"ab\xFFcd"); assert!(p.parse_unquoted_abbreviation().is_err()); } #[test] fn parse_quoted_abbreviation() { // The inputs look a little funny here, but that's because // 'parse_quoted_abbreviation' starts after the opening quote // has been parsed. let p = Parser::new("ABC>"); assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC"); let p = Parser::new("ABCXYZ>"); assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ"); let p = Parser::new("ABC>123"); assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC"); let p = Parser::new("ABC123>"); assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123"); let p = Parser::new("ab1>"); assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1"); let p = Parser::new("+09>"); assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09"); let p = Parser::new("-09>"); assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09"); let tz = alloc::format!("{}>", "a".repeat(30)); let p = Parser::new(&tz); assert_eq!( p.parse_quoted_abbreviation().unwrap(), tz.trim_end_matches(">") ); let p = Parser::new("a>"); assert!(p.parse_quoted_abbreviation().is_err()); let p = Parser::new("ab>"); assert!(p.parse_quoted_abbreviation().is_err()); let tz = alloc::format!("{}>", "a".repeat(31)); let p = Parser::new(&tz); assert!(p.parse_quoted_abbreviation().is_err()); let p = Parser::new(b"ab\xFFcd>"); assert!(p.parse_quoted_abbreviation().is_err()); let p = Parser::new("ABC"); assert!(p.parse_quoted_abbreviation().is_err()); let p = Parser::new("ABC!>"); assert!(p.parse_quoted_abbreviation().is_err()); } #[test] fn parse_posix_offset() { let p = Parser::new("5"); assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60); let p = Parser::new("+5"); assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60); let p = Parser::new("-5"); assert_eq!(p.parse_posix_offset().unwrap().second, 5 * 60 * 60); let p = Parser::new("-12:34:56"); assert_eq!( p.parse_posix_offset().unwrap().second, 12 * 60 * 60 + 34 * 60 + 56, ); let p = Parser::new("a"); assert!(p.parse_posix_offset().is_err()); let p = Parser::new("-"); assert!(p.parse_posix_offset().is_err()); let p = Parser::new("+"); assert!(p.parse_posix_offset().is_err()); let p = Parser::new("-a"); assert!(p.parse_posix_offset().is_err()); let p = Parser::new("+a"); assert!(p.parse_posix_offset().is_err()); let p = Parser::new("-25"); assert!(p.parse_posix_offset().is_err()); let p = Parser::new("+25"); assert!(p.parse_posix_offset().is_err()); // This checks that we don't accidentally permit IANA rules for // offset parsing. Namely, the IANA tzfile v3+ extension only applies // to transition times. But since POSIX says that the "time" for the // offset and transition is the same format, it would be an easy // implementation mistake to implement the more flexible rule for // IANA and have it accidentally also apply to the offset. So we check // that it doesn't here. let p = Parser { ianav3plus: true, ..Parser::new("25") }; assert!(p.parse_posix_offset().is_err()); let p = Parser { ianav3plus: true, ..Parser::new("+25") }; assert!(p.parse_posix_offset().is_err()); let p = Parser { ianav3plus: true, ..Parser::new("-25") }; assert!(p.parse_posix_offset().is_err()); } #[test] fn parse_rule() { let p = Parser::new("M9.5.0,M4.1.0/3"); assert_eq!( p.parse_rule().unwrap(), PosixRule { start: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0, }, time: PosixTime { second: 2 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 4, week: 1, weekday: 0, }, time: PosixTime { second: 3 * 60 * 60 }, }, }, ); let p = Parser::new("M9.5.0"); assert!(p.parse_rule().is_err()); let p = Parser::new(",M9.5.0,M4.1.0/3"); assert!(p.parse_rule().is_err()); let p = Parser::new("M9.5.0/"); assert!(p.parse_rule().is_err()); let p = Parser::new("M9.5.0,M4.1.0/"); assert!(p.parse_rule().is_err()); } #[test] fn parse_posix_datetime() { let p = Parser::new("J1"); assert_eq!( p.parse_posix_datetime().unwrap(), PosixDayTime { date: PosixDay::JulianOne(1), time: PosixTime { second: 2 * 60 * 60 } }, ); let p = Parser::new("J1/3"); assert_eq!( p.parse_posix_datetime().unwrap(), PosixDayTime { date: PosixDay::JulianOne(1), time: PosixTime { second: 3 * 60 * 60 } }, ); let p = Parser::new("M4.1.0/3"); assert_eq!( p.parse_posix_datetime().unwrap(), PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 4, week: 1, weekday: 0, }, time: PosixTime { second: 3 * 60 * 60 }, }, ); let p = Parser::new("1/3:45:05"); assert_eq!( p.parse_posix_datetime().unwrap(), PosixDayTime { date: PosixDay::JulianZero(1), time: PosixTime { second: 3 * 60 * 60 + 45 * 60 + 5 }, }, ); let p = Parser::new("a"); assert!(p.parse_posix_datetime().is_err()); let p = Parser::new("J1/"); assert!(p.parse_posix_datetime().is_err()); let p = Parser::new("1/"); assert!(p.parse_posix_datetime().is_err()); let p = Parser::new("M4.1.0/"); assert!(p.parse_posix_datetime().is_err()); } #[test] fn parse_posix_date() { let p = Parser::new("J1"); assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(1)); let p = Parser::new("J365"); assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(365)); let p = Parser::new("0"); assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(0)); let p = Parser::new("1"); assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(1)); let p = Parser::new("365"); assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(365)); let p = Parser::new("M9.5.0"); assert_eq!( p.parse_posix_date().unwrap(), PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0 }, ); let p = Parser::new("M9.5.6"); assert_eq!( p.parse_posix_date().unwrap(), PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 }, ); let p = Parser::new("M09.5.6"); assert_eq!( p.parse_posix_date().unwrap(), PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 }, ); let p = Parser::new("M12.1.1"); assert_eq!( p.parse_posix_date().unwrap(), PosixDay::WeekdayOfMonth { month: 12, week: 1, weekday: 1 }, ); let p = Parser::new("a"); assert!(p.parse_posix_date().is_err()); let p = Parser::new("j"); assert!(p.parse_posix_date().is_err()); let p = Parser::new("m"); assert!(p.parse_posix_date().is_err()); let p = Parser::new("n"); assert!(p.parse_posix_date().is_err()); let p = Parser::new("J366"); assert!(p.parse_posix_date().is_err()); let p = Parser::new("366"); assert!(p.parse_posix_date().is_err()); } #[test] fn parse_posix_julian_day_no_leap() { let p = Parser::new("1"); assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1); let p = Parser::new("001"); assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1); let p = Parser::new("365"); assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365); let p = Parser::new("3655"); assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365); let p = Parser::new("0"); assert!(p.parse_posix_julian_day_no_leap().is_err()); let p = Parser::new("366"); assert!(p.parse_posix_julian_day_no_leap().is_err()); } #[test] fn parse_posix_julian_day_with_leap() { let p = Parser::new("0"); assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0); let p = Parser::new("1"); assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1); let p = Parser::new("001"); assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1); let p = Parser::new("365"); assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365); let p = Parser::new("3655"); assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365); let p = Parser::new("366"); assert!(p.parse_posix_julian_day_with_leap().is_err()); } #[test] fn parse_weekday_of_month() { let p = Parser::new("9.5.0"); assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 5, 0)); let p = Parser::new("9.1.6"); assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6)); let p = Parser::new("09.1.6"); assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6)); let p = Parser::new("9"); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("9."); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("9.5"); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("9.5."); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("0.5.0"); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("13.5.0"); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("9.0.0"); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("9.6.0"); assert!(p.parse_weekday_of_month().is_err()); let p = Parser::new("9.5.7"); assert!(p.parse_weekday_of_month().is_err()); } #[test] fn parse_posix_time() { let p = Parser::new("5"); assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60); let p = Parser::new("22"); assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60); let p = Parser::new("02"); assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60); let p = Parser::new("5:45"); assert_eq!( p.parse_posix_time().unwrap().second, 5 * 60 * 60 + 45 * 60 ); let p = Parser::new("5:45:12"); assert_eq!( p.parse_posix_time().unwrap().second, 5 * 60 * 60 + 45 * 60 + 12 ); let p = Parser::new("5:45:129"); assert_eq!( p.parse_posix_time().unwrap().second, 5 * 60 * 60 + 45 * 60 + 12 ); let p = Parser::new("5:45:12:"); assert_eq!( p.parse_posix_time().unwrap().second, 5 * 60 * 60 + 45 * 60 + 12 ); let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") }; assert_eq!( p.parse_posix_time().unwrap().second, 5 * 60 * 60 + 45 * 60 + 12 ); let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") }; assert_eq!( p.parse_posix_time().unwrap().second, -(5 * 60 * 60 + 45 * 60 + 12) ); let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") }; assert_eq!( p.parse_posix_time().unwrap().second, -(167 * 60 * 60 + 45 * 60 + 12), ); let p = Parser::new("25"); assert!(p.parse_posix_time().is_err()); let p = Parser::new("12:2"); assert!(p.parse_posix_time().is_err()); let p = Parser::new("12:"); assert!(p.parse_posix_time().is_err()); let p = Parser::new("12:23:5"); assert!(p.parse_posix_time().is_err()); let p = Parser::new("12:23:"); assert!(p.parse_posix_time().is_err()); let p = Parser { ianav3plus: true, ..Parser::new("168") }; assert!(p.parse_posix_time().is_err()); let p = Parser { ianav3plus: true, ..Parser::new("-168") }; assert!(p.parse_posix_time().is_err()); let p = Parser { ianav3plus: true, ..Parser::new("+168") }; assert!(p.parse_posix_time().is_err()); } #[test] fn parse_month() { let p = Parser::new("1"); assert_eq!(p.parse_month().unwrap(), 1); // Should this be allowed? POSIX spec is unclear. // We allow it because our parse does stop at 2 // digits, so this seems harmless. Namely, '001' // results in an error. let p = Parser::new("01"); assert_eq!(p.parse_month().unwrap(), 1); let p = Parser::new("12"); assert_eq!(p.parse_month().unwrap(), 12); let p = Parser::new("0"); assert!(p.parse_month().is_err()); let p = Parser::new("00"); assert!(p.parse_month().is_err()); let p = Parser::new("001"); assert!(p.parse_month().is_err()); let p = Parser::new("13"); assert!(p.parse_month().is_err()); } #[test] fn parse_week() { let p = Parser::new("1"); assert_eq!(p.parse_week().unwrap(), 1); let p = Parser::new("5"); assert_eq!(p.parse_week().unwrap(), 5); let p = Parser::new("55"); assert_eq!(p.parse_week().unwrap(), 5); let p = Parser::new("0"); assert!(p.parse_week().is_err()); let p = Parser::new("6"); assert!(p.parse_week().is_err()); let p = Parser::new("00"); assert!(p.parse_week().is_err()); let p = Parser::new("01"); assert!(p.parse_week().is_err()); let p = Parser::new("05"); assert!(p.parse_week().is_err()); } #[test] fn parse_weekday() { let p = Parser::new("0"); assert_eq!(p.parse_weekday().unwrap(), 0); let p = Parser::new("1"); assert_eq!(p.parse_weekday().unwrap(), 1); let p = Parser::new("6"); assert_eq!(p.parse_weekday().unwrap(), 6); let p = Parser::new("00"); assert_eq!(p.parse_weekday().unwrap(), 0); let p = Parser::new("06"); assert_eq!(p.parse_weekday().unwrap(), 0); let p = Parser::new("60"); assert_eq!(p.parse_weekday().unwrap(), 6); let p = Parser::new("7"); assert!(p.parse_weekday().is_err()); } #[test] fn parse_hour_posix() { let p = Parser::new("5"); assert_eq!(p.parse_hour_posix().unwrap(), 5); let p = Parser::new("0"); assert_eq!(p.parse_hour_posix().unwrap(), 0); let p = Parser::new("00"); assert_eq!(p.parse_hour_posix().unwrap(), 0); let p = Parser::new("24"); assert_eq!(p.parse_hour_posix().unwrap(), 24); let p = Parser::new("100"); assert_eq!(p.parse_hour_posix().unwrap(), 10); let p = Parser::new("25"); assert!(p.parse_hour_posix().is_err()); let p = Parser::new("99"); assert!(p.parse_hour_posix().is_err()); } #[test] fn parse_hour_ianav3plus() { let new = |input| Parser { ianav3plus: true, ..Parser::new(input) }; let p = new("5"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5); let p = new("0"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0); let p = new("00"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0); let p = new("000"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0); let p = new("24"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24); let p = new("100"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100); let p = new("1000"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100); let p = new("167"); assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167); let p = new("168"); assert!(p.parse_hour_ianav3plus().is_err()); let p = new("999"); assert!(p.parse_hour_ianav3plus().is_err()); } #[test] fn parse_minute() { let p = Parser::new("00"); assert_eq!(p.parse_minute().unwrap(), 0); let p = Parser::new("24"); assert_eq!(p.parse_minute().unwrap(), 24); let p = Parser::new("59"); assert_eq!(p.parse_minute().unwrap(), 59); let p = Parser::new("599"); assert_eq!(p.parse_minute().unwrap(), 59); let p = Parser::new("0"); assert!(p.parse_minute().is_err()); let p = Parser::new("1"); assert!(p.parse_minute().is_err()); let p = Parser::new("9"); assert!(p.parse_minute().is_err()); let p = Parser::new("60"); assert!(p.parse_minute().is_err()); } #[test] fn parse_second() { let p = Parser::new("00"); assert_eq!(p.parse_second().unwrap(), 0); let p = Parser::new("24"); assert_eq!(p.parse_second().unwrap(), 24); let p = Parser::new("59"); assert_eq!(p.parse_second().unwrap(), 59); let p = Parser::new("599"); assert_eq!(p.parse_second().unwrap(), 59); let p = Parser::new("0"); assert!(p.parse_second().is_err()); let p = Parser::new("1"); assert!(p.parse_second().is_err()); let p = Parser::new("9"); assert!(p.parse_second().is_err()); let p = Parser::new("60"); assert!(p.parse_second().is_err()); } #[test] fn parse_number_with_exactly_n_digits() { let p = Parser::new("1"); assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1); let p = Parser::new("12"); assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12); let p = Parser::new("123"); assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12); let p = Parser::new(""); assert!(p.parse_number_with_exactly_n_digits(1).is_err()); let p = Parser::new("1"); assert!(p.parse_number_with_exactly_n_digits(2).is_err()); let p = Parser::new("12"); assert!(p.parse_number_with_exactly_n_digits(3).is_err()); } #[test] fn parse_number_with_upto_n_digits() { let p = Parser::new("1"); assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1); let p = Parser::new("1"); assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1); let p = Parser::new("12"); assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12); let p = Parser::new("12"); assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12); let p = Parser::new("123"); assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12); let p = Parser::new(""); assert!(p.parse_number_with_upto_n_digits(1).is_err()); let p = Parser::new("a"); assert!(p.parse_number_with_upto_n_digits(1).is_err()); } #[test] fn to_dst_civil_datetime_utc_range() { let tz = posix_time_zone("WART4WARST,J1/-3,J365/20"); let dst_info = DstInfo { // We test this in other places. It's too annoying to write this // out here, and I didn't adopt snapshot testing until I had // written out these tests by hand. ¯\_(ツ)_/¯ dst: tz.dst.as_ref().unwrap(), start: date(2024, 1, 1).at(1, 0, 0, 0), end: date(2024, 12, 31).at(23, 0, 0, 0), }; assert_eq!(tz.dst_info_utc(2024), Some(dst_info)); let tz = posix_time_zone("WART4WARST,J1/-4,J365/21"); let dst_info = DstInfo { dst: tz.dst.as_ref().unwrap(), start: date(2024, 1, 1).at(0, 0, 0, 0), end: date(2024, 12, 31).at(23, 59, 59, 999_999_999), }; assert_eq!(tz.dst_info_utc(2024), Some(dst_info)); let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0"); let dst_info = DstInfo { dst: tz.dst.as_ref().unwrap(), start: date(2024, 3, 10).at(7, 0, 0, 0), end: date(2024, 11, 3).at(6, 0, 0, 0), }; assert_eq!(tz.dst_info_utc(2024), Some(dst_info)); } // See: https://github.com/BurntSushi/jiff/issues/386 #[test] fn regression_permanent_dst() { let tz = posix_time_zone("XXX-2<+01>-1,0/0,J365/23"); let dst_info = DstInfo { dst: tz.dst.as_ref().unwrap(), start: date(2087, 1, 1).at(0, 0, 0, 0), end: date(2087, 12, 31).at(23, 59, 59, 999_999_999), }; assert_eq!(tz.dst_info_utc(2087), Some(dst_info)); } #[test] fn reasonable() { assert!(PosixTimeZone::parse(b"EST5").is_ok()); assert!(PosixTimeZone::parse(b"EST5EDT").is_err()); assert!(PosixTimeZone::parse(b"EST5EDT,J1,J365").is_ok()); let tz = posix_time_zone("EST24EDT,J1,J365"); assert_eq!( tz, PosixTimeZone { std_abbrev: "EST".into(), std_offset: PosixOffset { second: -24 * 60 * 60 }, dst: Some(PosixDst { abbrev: "EDT".into(), offset: PosixOffset { second: -23 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(1), time: PosixTime::DEFAULT, }, end: PosixDayTime { date: PosixDay::JulianOne(365), time: PosixTime::DEFAULT, }, }, }), }, ); let tz = posix_time_zone("EST-24EDT,J1,J365"); assert_eq!( tz, PosixTimeZone { std_abbrev: "EST".into(), std_offset: PosixOffset { second: 24 * 60 * 60 }, dst: Some(PosixDst { abbrev: "EDT".into(), offset: PosixOffset { second: 25 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::JulianOne(1), time: PosixTime::DEFAULT, }, end: PosixDayTime { date: PosixDay::JulianOne(365), time: PosixTime::DEFAULT, }, }, }), }, ); } #[test] fn posix_date_time_spec_to_datetime() { // For this test, we just keep the offset to zero to simplify things // a bit. We get coverage for non-zero offsets in higher level tests. let to_datetime = |daytime: &PosixDayTime, year: i16| { daytime.to_datetime(year, IOffset::UTC) }; let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34"); assert_eq!( to_datetime(&tz.rule().start, 2023), date(2023, 1, 1).at(2, 0, 0, 0), ); assert_eq!( to_datetime(&tz.rule().end, 2023), date(2023, 12, 31).at(5, 12, 34, 0), ); let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2"); assert_eq!( to_datetime(&tz.rule().start, 2024), date(2024, 3, 10).at(2, 0, 0, 0), ); assert_eq!( to_datetime(&tz.rule().end, 2024), date(2024, 11, 3).at(2, 0, 0, 0), ); let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2"); assert_eq!( to_datetime(&tz.rule().start, 2024), date(2024, 1, 1).at(2, 0, 0, 0), ); assert_eq!( to_datetime(&tz.rule().end, 2024), date(2024, 12, 31).at(2, 0, 0, 0), ); let tz = posix_time_zone("EST5EDT,0/0,J365/25"); assert_eq!( to_datetime(&tz.rule().start, 2024), date(2024, 1, 1).at(0, 0, 0, 0), ); assert_eq!( to_datetime(&tz.rule().end, 2024), date(2024, 12, 31).at(23, 59, 59, 999_999_999), ); let tz = posix_time_zone("XXX3EDT4,0/0,J365/23"); assert_eq!( to_datetime(&tz.rule().start, 2024), date(2024, 1, 1).at(0, 0, 0, 0), ); assert_eq!( to_datetime(&tz.rule().end, 2024), date(2024, 12, 31).at(23, 0, 0, 0), ); let tz = posix_time_zone("XXX3EDT4,0/0,365"); assert_eq!( to_datetime(&tz.rule().end, 2023), date(2023, 12, 31).at(23, 59, 59, 999_999_999), ); assert_eq!( to_datetime(&tz.rule().end, 2024), date(2024, 12, 31).at(2, 0, 0, 0), ); let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59"); assert_eq!( to_datetime(&tz.rule().start, 2024), date(2024, 1, 1).at(0, 0, 0, 0), ); assert_eq!( to_datetime(&tz.rule().end, 2024), date(2024, 12, 31).at(23, 59, 59, 999_999_999), ); } #[test] fn posix_date_time_spec_time() { let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34"); assert_eq!(tz.rule().start.time, PosixTime::DEFAULT); assert_eq!( tz.rule().end.time, PosixTime { second: 5 * 60 * 60 + 12 * 60 + 34 }, ); } #[test] fn posix_date_spec_to_date() { let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2"); let start = tz.rule().start.date.to_date(2023); assert_eq!(start, Some(date(2023, 3, 12))); let end = tz.rule().end.date.to_date(2023); assert_eq!(end, Some(date(2023, 11, 5))); let start = tz.rule().start.date.to_date(2024); assert_eq!(start, Some(date(2024, 3, 10))); let end = tz.rule().end.date.to_date(2024); assert_eq!(end, Some(date(2024, 11, 3))); let tz = posix_time_zone("EST+5EDT,J60,J365"); let start = tz.rule().start.date.to_date(2023); assert_eq!(start, Some(date(2023, 3, 1))); let end = tz.rule().end.date.to_date(2023); assert_eq!(end, Some(date(2023, 12, 31))); let start = tz.rule().start.date.to_date(2024); assert_eq!(start, Some(date(2024, 3, 1))); let end = tz.rule().end.date.to_date(2024); assert_eq!(end, Some(date(2024, 12, 31))); let tz = posix_time_zone("EST+5EDT,59,365"); let start = tz.rule().start.date.to_date(2023); assert_eq!(start, Some(date(2023, 3, 1))); let end = tz.rule().end.date.to_date(2023); assert_eq!(end, None); let start = tz.rule().start.date.to_date(2024); assert_eq!(start, Some(date(2024, 2, 29))); let end = tz.rule().end.date.to_date(2024); assert_eq!(end, Some(date(2024, 12, 31))); let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2"); let start = tz.rule().start.date.to_date(2024); assert_eq!(start, Some(date(2024, 1, 1))); let end = tz.rule().end.date.to_date(2024); assert_eq!(end, Some(date(2024, 12, 31))); } #[test] fn posix_time_spec_to_civil_time() { let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34"); assert_eq!( tz.dst.as_ref().unwrap().rule.start.time.second, 2 * 60 * 60, ); assert_eq!( tz.dst.as_ref().unwrap().rule.end.time.second, 5 * 60 * 60 + 12 * 60 + 34, ); let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00"); assert_eq!( tz.dst.as_ref().unwrap().rule.start.time.second, 23 * 60 * 60 + 59 * 60 + 59, ); assert_eq!( tz.dst.as_ref().unwrap().rule.end.time.second, 24 * 60 * 60, ); let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00"); assert_eq!( tz.dst.as_ref().unwrap().rule.start.time.second, -1 * 60 * 60, ); assert_eq!( tz.dst.as_ref().unwrap().rule.end.time.second, 167 * 60 * 60, ); } #[test] fn parse_iana() { // Ref: https://github.com/chronotope/chrono/issues/1153 let p = PosixTimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap(); assert_eq!( p, PosixTimeZone { std_abbrev: "CRAZY".into(), std_offset: PosixOffset { second: -5 * 60 * 60 }, dst: Some(PosixDst { abbrev: "SHORT".into(), offset: PosixOffset { second: -4 * 60 * 60 }, rule: PosixRule { start: PosixDayTime { date: PosixDay::WeekdayOfMonth { month: 12, week: 5, weekday: 0, }, time: PosixTime { second: 50 * 60 * 60 }, }, end: PosixDayTime { date: PosixDay::JulianZero(0), time: PosixTime { second: 2 * 60 * 60 }, }, }, }), }, ); assert!(PosixTimeZone::parse(b"America/New_York").is_err()); assert!(PosixTimeZone::parse(b":America/New_York").is_err()); } // See: https://github.com/BurntSushi/jiff/issues/407 #[test] fn parse_empty_is_err() { assert!(PosixTimeZone::parse(b"").is_err()); } // See: https://github.com/BurntSushi/jiff/issues/407 #[test] fn parse_weird_is_err() { let s = b"AAAAAAAAAAAAAAACAAAAAAAAAAAAQA8AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA"; assert!(PosixTimeZone::parse(s).is_err()); let s = b"8"; assert!(PosixTimeZone::parse(s).is_err()); let s = b"PPPPPPPPPPPPPPPPPPPPnoofPPPAAA6DaPPPPPPPPPPPPPPPPPPPPPnoofPPPPP,n"; assert!(PosixTimeZone::parse(s).is_err()); let s = b"oooooooooovooooooooooooooooool9, bytes: &[u8], ) -> Result { let original = bytes; let name = name.into(); let (header32, rest) = Header::parse(4, bytes) .map_err(|e| err!("failed to parse 32-bit header: {e}"))?; let (mut tzif, rest) = if header32.version == 0 { TzifOwned::parse32(name, header32, rest)? } else { TzifOwned::parse64(name, header32, rest)? }; tzif.fatten(); // This should come after fattening, because fattening may add new // transitions and we want to add civil datetimes to those. tzif.add_civil_datetimes_to_transitions(); tzif.verify_posix_time_zone_consistency()?; // Compute the checksum using the entire contents of the TZif data. let tzif_raw_len = (rest.as_ptr() as usize) .checked_sub(original.as_ptr() as usize) .unwrap(); let tzif_raw_bytes = &original[..tzif_raw_len]; tzif.fixed.checksum = super::crc32::sum(tzif_raw_bytes); // Shrink all of our allocs so we don't keep excess capacity around. tzif.fixed.designations.shrink_to_fit(); tzif.types.shrink_to_fit(); tzif.transitions.timestamps.shrink_to_fit(); tzif.transitions.civil_starts.shrink_to_fit(); tzif.transitions.civil_ends.shrink_to_fit(); tzif.transitions.infos.shrink_to_fit(); Ok(tzif) } fn parse32<'b>( name: Option, header32: Header, bytes: &'b [u8], ) -> Result<(TzifOwned, &'b [u8]), Error> { let mut tzif = TzifOwned { fixed: TzifFixed { name, version: header32.version, // filled in later checksum: 0, designations: String::new(), posix_tz: None, }, types: vec![], transitions: TzifTransitions { timestamps: vec![], civil_starts: vec![], civil_ends: vec![], infos: vec![], }, }; let rest = tzif.parse_transitions(&header32, bytes)?; let rest = tzif.parse_transition_types(&header32, rest)?; let rest = tzif.parse_local_time_types(&header32, rest)?; let rest = tzif.parse_time_zone_designations(&header32, rest)?; let rest = tzif.parse_leap_seconds(&header32, rest)?; let rest = tzif.parse_indicators(&header32, rest)?; Ok((tzif, rest)) } fn parse64<'b>( name: Option, header32: Header, bytes: &'b [u8], ) -> Result<(TzifOwned, &'b [u8]), Error> { let (_, rest) = try_split_at( "V1 TZif data block", bytes, header32.data_block_len()?, )?; let (header64, rest) = Header::parse(8, rest) .map_err(|e| err!("failed to parse 64-bit header: {e}"))?; let mut tzif = TzifOwned { fixed: TzifFixed { name, version: header64.version, // filled in later checksum: 0, designations: String::new(), posix_tz: None, }, types: vec![], transitions: TzifTransitions { timestamps: vec![], civil_starts: vec![], civil_ends: vec![], infos: vec![], }, }; let rest = tzif.parse_transitions(&header64, rest)?; let rest = tzif.parse_transition_types(&header64, rest)?; let rest = tzif.parse_local_time_types(&header64, rest)?; let rest = tzif.parse_time_zone_designations(&header64, rest)?; let rest = tzif.parse_leap_seconds(&header64, rest)?; let rest = tzif.parse_indicators(&header64, rest)?; let rest = tzif.parse_footer(&header64, rest)?; // Note that we don't check that the TZif data is fully valid. It is // possible for it to contain superfluous information. For example, a // non-zero local time type that is never referenced by a transition. Ok((tzif, rest)) } fn parse_transitions<'b>( &mut self, header: &Header, bytes: &'b [u8], ) -> Result<&'b [u8], Error> { let (bytes, rest) = try_split_at( "transition times data block", bytes, header.transition_times_len()?, )?; let mut it = bytes.chunks_exact(header.time_size); // RFC 8536 says: "If there are no transitions, local time for all // timestamps is specified by the TZ string in the footer if present // and nonempty; otherwise, it is specified by time type 0." // // RFC 8536 also says: "Local time for timestamps before the first // transition is specified by the first time type (time type // 0)." // // So if there are no transitions, pushing this dummy one will result // in the desired behavior even when it's the only transition. // Similarly, since this is the minimum timestamp value, it will // trigger for any times before the first transition found in the TZif // data. self.transitions.add_with_type_index(TIMESTAMP_MIN, 0); while let Some(chunk) = it.next() { let mut timestamp = if header.is_32bit() { i64::from(from_be_bytes_i32(chunk)) } else { from_be_bytes_i64(chunk) }; if !(TIMESTAMP_MIN <= timestamp && timestamp <= TIMESTAMP_MAX) { // We really shouldn't error here just because the Unix // timestamp is outside what Jiff supports. Since what Jiff // supports is _somewhat_ arbitrary. But Jiff's supported // range is good enough for all realistic purposes, so we // just clamp an out-of-range Unix timestamp to the Jiff // min or max value. // // This can't result in the sorting order being wrong, but // it can result in a transition that is duplicative with // the dummy transition we inserted above. This should be // fine. let clamped = timestamp.clamp(TIMESTAMP_MIN, TIMESTAMP_MAX); timestamp = clamped; } self.transitions.add(timestamp); } assert!(it.remainder().is_empty()); Ok(rest) } fn parse_transition_types<'b>( &mut self, header: &Header, bytes: &'b [u8], ) -> Result<&'b [u8], Error> { let (bytes, rest) = try_split_at( "transition types data block", bytes, header.transition_types_len()?, )?; // We skip the first transition because it is our minimum dummy // transition. for (transition_index, &type_index) in (1..).zip(bytes) { if usize::from(type_index) >= header.tzh_typecnt { return Err(err!( "found transition type index {type_index}, but there are only {} local time types", header.tzh_typecnt, )); } self.transitions.infos[transition_index].type_index = type_index; } Ok(rest) } fn parse_local_time_types<'b>( &mut self, header: &Header, bytes: &'b [u8], ) -> Result<&'b [u8], Error> { let (bytes, rest) = try_split_at( "local time types data block", bytes, header.local_time_types_len()?, )?; let mut it = bytes.chunks_exact(6); while let Some(chunk) = it.next() { let offset = from_be_bytes_i32(&chunk[..4]); if !(OFFSET_MIN <= offset && offset <= OFFSET_MAX) { return Err(err!( "found local time type with out-of-bounds offset: {offset}" )); } let is_dst = chunk[4] == 1; let designation = (chunk[5], chunk[5]); self.types.push(TzifLocalTimeType { offset, is_dst, designation, indicator: TzifIndicator::LocalWall, }); } assert!(it.remainder().is_empty()); Ok(rest) } fn parse_time_zone_designations<'b>( &mut self, header: &Header, bytes: &'b [u8], ) -> Result<&'b [u8], Error> { let (bytes, rest) = try_split_at( "time zone designations data block", bytes, header.time_zone_designations_len()?, )?; self.fixed.designations = String::from_utf8(bytes.to_vec()).map_err(|_| { err!( "time zone designations are not valid UTF-8: {:?}", Bytes(bytes), ) })?; // Holy hell, this is brutal. The boundary conditions are crazy. for (i, typ) in self.types.iter_mut().enumerate() { let start = usize::from(typ.designation.0); let Some(suffix) = self.fixed.designations.get(start..) else { return Err(err!( "local time type {i} has designation index of {start}, \ but cannot be more than {}", self.fixed.designations.len(), )); }; let Some(len) = suffix.find('\x00') else { return Err(err!( "local time type {i} has designation index of {start}, \ but could not find NUL terminator after it in \ designations: {:?}", self.fixed.designations, )); }; let Some(end) = start.checked_add(len) else { return Err(err!( "local time type {i} has designation index of {start}, \ but its length {len} is too big", )); }; typ.designation.1 = u8::try_from(end).map_err(|_| { err!( "local time type {i} has designation range of \ {start}..{end}, but end is too big", ) })?; } Ok(rest) } /// This parses the leap second corrections in the TZif data. /// /// Note that we only parse and verify them. We don't actually use them. /// Jiff effectively ignores leap seconds. fn parse_leap_seconds<'b>( &mut self, header: &Header, bytes: &'b [u8], ) -> Result<&'b [u8], Error> { let (bytes, rest) = try_split_at( "leap seconds data block", bytes, header.leap_second_len()?, )?; let chunk_len = header .time_size .checked_add(4) .expect("time_size plus 4 fits in usize"); let mut it = bytes.chunks_exact(chunk_len); while let Some(chunk) = it.next() { let (occur_bytes, _corr_bytes) = chunk.split_at(header.time_size); let occur = if header.is_32bit() { i64::from(from_be_bytes_i32(occur_bytes)) } else { from_be_bytes_i64(occur_bytes) }; if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) {} } assert!(it.remainder().is_empty()); Ok(rest) } fn parse_indicators<'b>( &mut self, header: &Header, bytes: &'b [u8], ) -> Result<&'b [u8], Error> { let (std_wall_bytes, rest) = try_split_at( "standard/wall indicators data block", bytes, header.standard_wall_len()?, )?; let (ut_local_bytes, rest) = try_split_at( "UT/local indicators data block", rest, header.ut_local_len()?, )?; if std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() { // This is a weird case, but technically possible only if all // UT/local indicators are 0. If any are 1, then it's an error, // because it would require the corresponding std/wall indicator // to be 1 too. Which it can't be, because there aren't any. So // we just check that they're all zeros. for (i, &byte) in ut_local_bytes.iter().enumerate() { if byte != 0 { return Err(err!( "found UT/local indicator '{byte}' for local time \ type {i}, but it must be 0 since all std/wall \ indicators are 0", )); } } } else if !std_wall_bytes.is_empty() && ut_local_bytes.is_empty() { for (i, &byte) in std_wall_bytes.iter().enumerate() { // Indexing is OK because Header guarantees that the number of // indicators is 0 or equal to the number of types. self.types[i].indicator = if byte == 0 { TzifIndicator::LocalWall } else if byte == 1 { TzifIndicator::LocalStandard } else { return Err(err!( "found invalid std/wall indicator '{byte}' for \ local time type {i}, it must be 0 or 1", )); }; } } else if !std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() { assert_eq!(std_wall_bytes.len(), ut_local_bytes.len()); let it = std_wall_bytes.iter().zip(ut_local_bytes); for (i, (&stdwall, &utlocal)) in it.enumerate() { // Indexing is OK because Header guarantees that the number of // indicators is 0 or equal to the number of types. self.types[i].indicator = match (stdwall, utlocal) { (0, 0) => TzifIndicator::LocalWall, (1, 0) => TzifIndicator::LocalStandard, (1, 1) => TzifIndicator::UTStandard, (0, 1) => { return Err(err!( "found illegal ut-wall combination for \ local time type {i}, only local-wall, \ local-standard and ut-standard are allowed", )) } _ => { return Err(err!( "found illegal std/wall or ut/local value for \ local time type {i}, each must be 0 or 1", )) } }; } } else { // If they're both empty then we don't need to do anything. Every // local time type record already has the correct default for this // case set. debug_assert!(std_wall_bytes.is_empty()); debug_assert!(ut_local_bytes.is_empty()); } Ok(rest) } fn parse_footer<'b>( &mut self, _header: &Header, bytes: &'b [u8], ) -> Result<&'b [u8], Error> { if bytes.is_empty() { return Err(err!( "invalid V2+ TZif footer, expected \\n, \ but found unexpected end of data", )); } if bytes[0] != b'\n' { return Err(err!( "invalid V2+ TZif footer, expected {:?}, but found {:?}", Byte(b'\n'), Byte(bytes[0]), )); } let bytes = &bytes[1..]; // Only scan up to 1KB for a NUL terminator in case we somehow got // passed a huge block of bytes. let toscan = &bytes[..bytes.len().min(1024)]; let Some(nlat) = toscan.iter().position(|&b| b == b'\n') else { return Err(err!( "invalid V2 TZif footer, could not find {:?} \ terminator in: {:?}", Byte(b'\n'), Bytes(toscan), )); }; let (bytes, rest) = bytes.split_at(nlat); if !bytes.is_empty() { // We could in theory limit TZ strings to their strict POSIX // definition here for TZif V2, but I don't think there is any // harm in allowing the extensions in V2 formatted TZif data. Note // that the GNU tooling allow it via the `TZ` environment variable // even though POSIX doesn't specify it. This all seems okay to me // because the V3+ extension is a strict superset of functionality. let posix_tz = PosixTimeZone::parse(bytes).map_err(|e| err!("{e}"))?; self.fixed.posix_tz = Some(posix_tz); } Ok(&rest[1..]) } /// Validates that the POSIX TZ string we parsed (if one exists) is /// consistent with the last transition in this time zone. This is /// required by RFC 8536. /// /// RFC 8536 says, "If the string is nonempty and one or more /// transitions appear in the version 2+ data, the string MUST be /// consistent with the last version 2+ transition." fn verify_posix_time_zone_consistency(&self) -> Result<(), Error> { // We need to be a little careful, since we always have at least one // transition (accounting for the dummy `Timestamp::MIN` transition). // So if we only have 1 transition and a POSIX TZ string, then we // should not validate it since it's equivalent to the case of 0 // transitions and a POSIX TZ string. if self.transitions.timestamps.len() <= 1 { return Ok(()); } let Some(ref tz) = self.fixed.posix_tz else { return Ok(()); }; let last = self .transitions .timestamps .last() .expect("last transition timestamp"); let type_index = self .transitions .infos .last() .expect("last transition info") .type_index; let typ = &self.types[usize::from(type_index)]; let (ioff, abbrev, is_dst) = tz.to_offset_info(ITimestamp::from_second(*last)); if ioff.second != typ.offset { return Err(err!( "expected last transition to have DST offset \ of {expected_offset}, but got {got_offset} \ according to POSIX TZ string {tz}", expected_offset = typ.offset, got_offset = ioff.second, tz = tz, )); } if is_dst != typ.is_dst { return Err(err!( "expected last transition to have is_dst={expected_dst}, \ but got is_dst={got_dst} according to POSIX TZ \ string {tz}", expected_dst = typ.is_dst, got_dst = is_dst, tz = tz, )); } if abbrev != self.designation(&typ) { return Err(err!( "expected last transition to have \ designation={expected_abbrev}, \ but got designation={got_abbrev} according to POSIX TZ \ string {tz}", expected_abbrev = self.designation(&typ), got_abbrev = abbrev, tz = tz, )); } Ok(()) } /// Add civil datetimes to our transitions. /// /// This isn't strictly necessary, but it speeds up time zone lookups when /// the input is a civil datetime. It lets us do comparisons directly on /// the civil datetime as given, instead of needing to convert the civil /// datetime given to a timestamp first. (Even if we didn't do this, I /// believe we'd still need at least one additional timestamp that is /// offset, because TZ lookups for a civil datetime are done in local time, /// and the timestamps in TZif data are, of course, all in UTC.) fn add_civil_datetimes_to_transitions(&mut self) { fn to_datetime(timestamp: i64, offset: i32) -> TzifDateTime { use crate::shared::util::itime::{IOffset, ITimestamp}; let its = ITimestamp { second: timestamp, nanosecond: 0 }; let ioff = IOffset { second: offset }; let dt = its.to_datetime(ioff); TzifDateTime::new( dt.date.year, dt.date.month, dt.date.day, dt.time.hour, dt.time.minute, dt.time.second, ) } let trans = &mut self.transitions; for i in 0..trans.timestamps.len() { let timestamp = trans.timestamps[i]; let offset = { let type_index = trans.infos[i].type_index; self.types[usize::from(type_index)].offset }; let prev_offset = { let type_index = trans.infos[i.saturating_sub(1)].type_index; self.types[usize::from(type_index)].offset }; if prev_offset == offset { // Equivalent offsets means there can never be any ambiguity. let start = to_datetime(timestamp, prev_offset); trans.infos[i].kind = TzifTransitionKind::Unambiguous; trans.civil_starts[i] = start; } else if prev_offset < offset { // When the offset of the previous transition is less, that // means there is some non-zero amount of time that is // "skipped" when moving to the next transition. Thus, we have // a gap. The start of the gap is the offset which gets us the // earliest time, i.e., the smaller of the two offsets. trans.infos[i].kind = TzifTransitionKind::Gap; trans.civil_starts[i] = to_datetime(timestamp, prev_offset); trans.civil_ends[i] = to_datetime(timestamp, offset); } else { // When the offset of the previous transition is greater, that // means there is some non-zero amount of time that will be // replayed on a wall clock in this time zone. Thus, we have // a fold. The start of the gold is the offset which gets us // the earliest time, i.e., the smaller of the two offsets. assert!(prev_offset > offset); trans.infos[i].kind = TzifTransitionKind::Fold; trans.civil_starts[i] = to_datetime(timestamp, offset); trans.civil_ends[i] = to_datetime(timestamp, prev_offset); } } } /// Fatten up this TZif data with additional transitions. /// /// These additional transitions often make time zone lookups faster, and /// they smooth out the performance difference between using "slim" and /// "fat" tzdbs. fn fatten(&mut self) { // Note that this is a crate feature for *both* `jiff` and // `jiff-static`. if !cfg!(feature = "tz-fat") { return; } let Some(posix_tz) = self.fixed.posix_tz.clone() else { return }; let last = self.transitions.timestamps.last().expect("last transition"); let mut i = 0; let mut prev = ITimestamp::from_second(*last); loop { if i > FATTEN_MAX_TRANSITIONS { return; } i += 1; prev = match self.add_transition(&posix_tz, prev) { None => break, Some(next) => next, }; } } /// If there's a transition strictly after the given timestamp for the /// given POSIX time zone, then add it to this TZif data. fn add_transition( &mut self, posix_tz: &PosixTimeZone, prev: ITimestamp, ) -> Option { let (its, ioff, abbrev, is_dst) = posix_tz.next_transition(prev)?; if its.to_datetime(IOffset::UTC).date.year >= FATTEN_UP_TO_YEAR { return None; } let type_index = self.find_or_create_local_time_type(ioff, abbrev, is_dst)?; self.transitions.add_with_type_index(its.second, type_index); Some(its) } /// Look for a local time type matching the data given. /// /// If one could not be found, then one is created and its index is /// returned. /// /// If one could not be found and one could not be created (e.g., the index /// would overflow `u8`), then `None` is returned. fn find_or_create_local_time_type( &mut self, offset: IOffset, abbrev: &str, is_dst: bool, ) -> Option { for (i, typ) in self.types.iter().enumerate() { if offset.second == typ.offset && abbrev == self.designation(typ) && is_dst == typ.is_dst { return u8::try_from(i).ok(); } } let i = u8::try_from(self.types.len()).ok()?; let designation = self.find_or_create_designation(abbrev)?; self.types.push(TzifLocalTimeType { offset: offset.second, is_dst, designation, // Not really clear if this is correct, but Jiff // ignores this anyway, so ¯\_(ツ)_/¯. indicator: TzifIndicator::LocalWall, }); Some(i) } /// Look for a designation (i.e., time zone abbreviation) matching the data /// given, and return its range into `self.fixed.designations`. /// /// If one could not be found, then one is created and its range is /// returned. /// /// If one could not be found and one could not be created (e.g., the range /// would overflow `u8`), then `None` is returned. fn find_or_create_designation( &mut self, needle: &str, ) -> Option<(u8, u8)> { let mut start = 0; while let Some(offset) = self.fixed.designations[start..].find('\0') { let end = start + offset; let abbrev = &self.fixed.designations[start..end]; if needle == abbrev { return Some((start.try_into().ok()?, end.try_into().ok()?)); } start = end + 1; } // Now we need to add a new abbreviation. This // should generally only happen for malformed TZif // data. i.e., TZif data with a POSIX time zone that // contains an TZ abbreviation that isn't found in // the TZif's designation list. // // And since we're guarding against malformed data, // the designation list might not end with NUL. If // not, add one. if !self.fixed.designations.ends_with('\0') { self.fixed.designations.push('\0'); } let start = self.fixed.designations.len(); self.fixed.designations.push_str(needle); self.fixed.designations.push('\0'); let end = self.fixed.designations.len(); Some((start.try_into().ok()?, end.try_into().ok()?)) } fn designation(&self, typ: &TzifLocalTimeType) -> &str { let range = usize::from(typ.designation.0)..usize::from(typ.designation.1); // OK because we verify that the designation range on every local // time type is a valid range into `self.designations`. &self.fixed.designations[range] } } impl TzifTransitionsOwned { /// Add a single transition with the given timestamp. /// /// This also fills in the other columns (civil starts, civil ends and /// infos) with sensible default values. It is expected that callers will /// later fill them in. fn add(&mut self, timestamp: i64) { self.add_with_type_index(timestamp, 0); } /// Like `TzifTransitionsOwned::add`, but let's the caller provide a type /// index if it is known. fn add_with_type_index(&mut self, timestamp: i64, type_index: u8) { self.timestamps.push(timestamp); self.civil_starts.push(TzifDateTime::ZERO); self.civil_ends.push(TzifDateTime::ZERO); self.infos.push(TzifTransitionInfo { type_index, kind: TzifTransitionKind::Unambiguous, }); } } /// The header for a TZif formatted file. /// /// V2+ TZif format have two headers: one for V1 data, and then a second /// following the V1 data block that describes another data block which uses /// 64-bit timestamps. The two headers both have the same format and both /// use 32-bit big-endian encoded integers. #[derive(Debug)] struct Header { /// The size of the timestamps encoded in the data block. /// /// This is guaranteed to be either 4 (for V1) or 8 (for the 64-bit header /// block in V2+). time_size: usize, /// The file format version. /// /// Note that this is either a NUL byte (for version 1), or an ASCII byte /// corresponding to the version number. That is, `0x32` for `2`, `0x33` /// for `3` or `0x34` for `4`. Note also that just because zoneinfo might /// have been recently generated does not mean it uses the latest format /// version. It seems like newer versions are only compiled by `zic` when /// they are needed. For example, `America/New_York` on my system (as of /// `2024-03-25`) has version `0x32`, but `Asia/Jerusalem` has version /// `0x33`. version: u8, /// Number of UT/local indicators stored in the file. /// /// This is checked to be either equal to `0` or equal to `tzh_typecnt`. tzh_ttisutcnt: usize, /// The number of standard/wall indicators stored in the file. /// /// This is checked to be either equal to `0` or equal to `tzh_typecnt`. tzh_ttisstdcnt: usize, /// The number of leap seconds for which data entries are stored in the /// file. tzh_leapcnt: usize, /// The number of transition times for which data entries are stored in /// the file. tzh_timecnt: usize, /// The number of local time types for which data entries are stored in the /// file. /// /// This is checked to be at least `1`. tzh_typecnt: usize, /// The number of bytes of time zone abbreviation strings stored in the /// file. /// /// This is checked to be at least `1`. tzh_charcnt: usize, } impl Header { /// Parse the header record from the given bytes. /// /// Upon success, return the header and all bytes after the header. /// /// The given `time_size` must be 4 or 8, corresponding to either the /// V1 header block or the V2+ header block, respectively. fn parse( time_size: usize, bytes: &[u8], ) -> Result<(Header, &[u8]), Error> { assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8"); if bytes.len() < 44 { return Err(err!("invalid header: too short")); } let (magic, rest) = bytes.split_at(4); if magic != b"TZif" { return Err(err!("invalid header: magic bytes mismatch")); } let (version, rest) = rest.split_at(1); let (_reserved, rest) = rest.split_at(15); let (tzh_ttisutcnt_bytes, rest) = rest.split_at(4); let (tzh_ttisstdcnt_bytes, rest) = rest.split_at(4); let (tzh_leapcnt_bytes, rest) = rest.split_at(4); let (tzh_timecnt_bytes, rest) = rest.split_at(4); let (tzh_typecnt_bytes, rest) = rest.split_at(4); let (tzh_charcnt_bytes, rest) = rest.split_at(4); let tzh_ttisutcnt = from_be_bytes_u32_to_usize(tzh_ttisutcnt_bytes) .map_err(|e| err!("failed to parse tzh_ttisutcnt: {e}"))?; let tzh_ttisstdcnt = from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes) .map_err(|e| err!("failed to parse tzh_ttisstdcnt: {e}"))?; let tzh_leapcnt = from_be_bytes_u32_to_usize(tzh_leapcnt_bytes) .map_err(|e| err!("failed to parse tzh_leapcnt: {e}"))?; let tzh_timecnt = from_be_bytes_u32_to_usize(tzh_timecnt_bytes) .map_err(|e| err!("failed to parse tzh_timecnt: {e}"))?; let tzh_typecnt = from_be_bytes_u32_to_usize(tzh_typecnt_bytes) .map_err(|e| err!("failed to parse tzh_typecnt: {e}"))?; let tzh_charcnt = from_be_bytes_u32_to_usize(tzh_charcnt_bytes) .map_err(|e| err!("failed to parse tzh_charcnt: {e}"))?; if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt { return Err(err!( "expected tzh_ttisutcnt={tzh_ttisutcnt} to be zero \ or equal to tzh_typecnt={tzh_typecnt}", )); } if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt { return Err(err!( "expected tzh_ttisstdcnt={tzh_ttisstdcnt} to be zero \ or equal to tzh_typecnt={tzh_typecnt}", )); } if tzh_typecnt < 1 { return Err(err!( "expected tzh_typecnt={tzh_typecnt} to be at least 1", )); } if tzh_charcnt < 1 { return Err(err!( "expected tzh_charcnt={tzh_charcnt} to be at least 1", )); } let header = Header { time_size, version: version[0], tzh_ttisutcnt, tzh_ttisstdcnt, tzh_leapcnt, tzh_timecnt, tzh_typecnt, tzh_charcnt, }; Ok((header, rest)) } /// Returns true if this header is for a 32-bit data block. /// /// When false, it is guaranteed that this header is for a 64-bit data /// block. fn is_32bit(&self) -> bool { self.time_size == 4 } /// Returns the size of the data block, in bytes, for this header. /// /// This returns an error if the arithmetic required to compute the /// length would overflow. /// /// This is useful for, e.g., skipping over the 32-bit V1 data block in /// V2+ TZif formatted files. fn data_block_len(&self) -> Result { let a = self.transition_times_len()?; let b = self.transition_types_len()?; let c = self.local_time_types_len()?; let d = self.time_zone_designations_len()?; let e = self.leap_second_len()?; let f = self.standard_wall_len()?; let g = self.ut_local_len()?; a.checked_add(b) .and_then(|z| z.checked_add(c)) .and_then(|z| z.checked_add(d)) .and_then(|z| z.checked_add(e)) .and_then(|z| z.checked_add(f)) .and_then(|z| z.checked_add(g)) .ok_or_else(|| { err!( "length of data block in V{} tzfile is too big", self.version ) }) } fn transition_times_len(&self) -> Result { self.tzh_timecnt.checked_mul(self.time_size).ok_or_else(|| { err!("tzh_timecnt value {} is too big", self.tzh_timecnt) }) } fn transition_types_len(&self) -> Result { Ok(self.tzh_timecnt) } fn local_time_types_len(&self) -> Result { self.tzh_typecnt.checked_mul(6).ok_or_else(|| { err!("tzh_typecnt value {} is too big", self.tzh_typecnt) }) } fn time_zone_designations_len(&self) -> Result { Ok(self.tzh_charcnt) } fn leap_second_len(&self) -> Result { let record_len = self .time_size .checked_add(4) .expect("4-or-8 plus 4 always fits in usize"); self.tzh_leapcnt.checked_mul(record_len).ok_or_else(|| { err!("tzh_leapcnt value {} is too big", self.tzh_leapcnt) }) } fn standard_wall_len(&self) -> Result { Ok(self.tzh_ttisstdcnt) } fn ut_local_len(&self) -> Result { Ok(self.tzh_ttisutcnt) } } /// Splits the given slice of bytes at the index given. /// /// If the index is out of range (greater than `bytes.len()`) then an error is /// returned. The error message will include the `what` string given, which is /// meant to describe the thing being split. fn try_split_at<'b>( what: &'static str, bytes: &'b [u8], at: usize, ) -> Result<(&'b [u8], &'b [u8]), Error> { if at > bytes.len() { Err(err!( "expected at least {at} bytes for {what}, \ but found only {} bytes", bytes.len(), )) } else { Ok(bytes.split_at(at)) } } /// Interprets the given slice as an unsigned 32-bit big endian integer, /// attempts to convert it to a `usize` and returns it. /// /// # Panics /// /// When `bytes.len() != 4`. /// /// # Errors /// /// This errors if the `u32` parsed from the given bytes cannot fit in a /// `usize`. fn from_be_bytes_u32_to_usize(bytes: &[u8]) -> Result { let n = from_be_bytes_u32(bytes); usize::try_from(n).map_err(|_| { err!( "failed to parse integer {n} (too big, max allowed is {}", usize::MAX ) }) } /// Interprets the given slice as an unsigned 32-bit big endian integer and /// returns it. /// /// # Panics /// /// When `bytes.len() != 4`. fn from_be_bytes_u32(bytes: &[u8]) -> u32 { u32::from_be_bytes(bytes.try_into().unwrap()) } /// Interprets the given slice as a signed 32-bit big endian integer and /// returns it. /// /// # Panics /// /// When `bytes.len() != 4`. fn from_be_bytes_i32(bytes: &[u8]) -> i32 { i32::from_be_bytes(bytes.try_into().unwrap()) } /// Interprets the given slice as a signed 64-bit big endian integer and /// returns it. /// /// # Panics /// /// When `bytes.len() != 8`. fn from_be_bytes_i64(bytes: &[u8]) -> i64 { i64::from_be_bytes(bytes.try_into().unwrap()) } jiff-static-0.2.16/src/shared/util/array_str.rs000064400000000000000000000162411046102023000175070ustar 00000000000000// auto-generated by: jiff-cli generate shared /// A simple and not the most-efficient fixed size string on the stack. /// /// This supplanted some uses of `Box` for storing tiny strings in an /// effort to reduce our dependence on dynamic memory allocation. /// /// Also, since it isn't needed and it lets us save on storage requirements, /// `N` must be less than `256` (so that the length can fit in a `u8`). #[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] #[doc(hidden)] // not part of Jiff's public API pub struct ArrayStr { /// The UTF-8 bytes that make up the string. /// /// This array---the entire array---is always valid UTF-8. And /// the `0..self.len` sub-slice is also always valid UTF-8. bytes: [u8; N], /// The number of bytes used by the string in `bytes`. /// /// (We could technically save this byte in some cases and use a NUL /// terminator. For example, since we don't permit NUL bytes in POSIX time /// zone abbreviation strings, but this is simpler and only one byte and /// generalizes. And we're not really trying to micro-optimize the storage /// requirements when we use these array strings. Or at least, I don't know /// of a reason to.) len: u8, } impl ArrayStr { /// Creates a new fixed capacity string. /// /// If the given string exceeds `N` bytes, then this returns /// `None`. pub(crate) const fn new(s: &str) -> Option> { let len = s.len(); if len > N { return None; } let mut bytes = [0; N]; let mut i = 0; while i < s.as_bytes().len() { bytes[i] = s.as_bytes()[i]; i += 1; } // OK because we don't ever use anything bigger than u8::MAX for `N`. // And we probably shouldn't, because that would be a pretty chunky // array. If such a thing is needed, please file an issue to discuss. debug_assert!(N <= u8::MAX as usize, "size of ArrayStr is too big"); Some(ArrayStr { bytes, len: len as u8 }) } /// Returns the capacity of this array string. pub(crate) fn capacity() -> usize { N } /// Append the bytes given to the end of this string. /// /// If the capacity would be exceeded, then this is a no-op and `false` /// is returned. pub(crate) fn push_str(&mut self, s: &str) -> bool { let len = usize::from(self.len); let Some(new_len) = len.checked_add(s.len()) else { return false }; if new_len > N { return false; } self.bytes[len..new_len].copy_from_slice(s.as_bytes()); // OK because we don't ever use anything bigger than u8::MAX for `N`. // And we probably shouldn't, because that would be a pretty chunky // array. If such a thing is needed, please file an issue to discuss. debug_assert!( N <= usize::from(u8::MAX), "size of ArrayStr is too big" ); self.len = u8::try_from(new_len).unwrap(); true } /// Returns this array string as a string slice. pub(crate) fn as_str(&self) -> &str { // OK because construction guarantees valid UTF-8. // // This is bullet proof enough to use unchecked `str` construction // here, but I can't dream up of a benchmark where it matters. core::str::from_utf8(&self.bytes[..usize::from(self.len)]).unwrap() } } /// Easy construction of `ArrayStr` from `&'static str`. /// /// We specifically limit to `&'static str` to approximate string literals. /// This prevents most cases of accidentally creating a non-string literal /// that panics if the string is too big. /// /// This impl primarily exists to make writing tests more convenient. impl From<&'static str> for ArrayStr { fn from(s: &'static str) -> ArrayStr { ArrayStr::new(s).unwrap() } } impl PartialEq for ArrayStr { fn eq(&self, rhs: &str) -> bool { self.as_str() == rhs } } impl PartialEq<&str> for ArrayStr { fn eq(&self, rhs: &&str) -> bool { self.as_str() == *rhs } } impl PartialEq> for str { fn eq(&self, rhs: &ArrayStr) -> bool { self == rhs.as_str() } } impl core::fmt::Debug for ArrayStr { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Debug::fmt(self.as_str(), f) } } impl core::fmt::Display for ArrayStr { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Display::fmt(self.as_str(), f) } } impl core::fmt::Write for ArrayStr { fn write_str(&mut self, s: &str) -> core::fmt::Result { if self.push_str(s) { Ok(()) } else { Err(core::fmt::Error) } } } impl AsRef for ArrayStr { fn as_ref(&self) -> &str { self.as_str() } } /// A self-imposed limit on the size of a time zone abbreviation, in bytes. /// /// POSIX says this: /// /// > Indicate no less than three, nor more than {TZNAME_MAX}, bytes that are /// > the designation for the standard (std) or the alternative (dst -such as /// > Daylight Savings Time) timezone. /// /// But it doesn't seem worth the trouble to query `TZNAME_MAX`. Interestingly, /// IANA says: /// /// > are 3 or more characters specifying the standard and daylight saving time /// > (DST) zone abbreviations /// /// Which implies that IANA thinks there is no limit. But that seems unwise. /// Moreover, in practice, it seems like the `date` utility supports fairly /// long abbreviations. On my mac (so, BSD `date` as I understand it): /// /// ```text /// $ TZ=ZZZ5YYYYYYYYYYYYYYYYYYYYY date /// Sun Mar 17 20:05:58 YYYYYYYYYYYYYYYYYYYYY 2024 /// ``` /// /// And on my Linux machine (so, GNU `date`): /// /// ```text /// $ TZ=ZZZ5YYYYYYYYYYYYYYYYYYYYY date /// Sun Mar 17 08:05:36 PM YYYYYYYYYYYYYYYYYYYYY 2024 /// ``` /// /// I don't know exactly what limit these programs use, but 30 seems good /// enough? /// /// (Previously, I had been using 255 and stuffing the string in a `Box`. /// But as part of work on [#168], I was looking to remove allocation from as /// many places as possible. And this was one candidate. But making room on the /// stack for 255 byte abbreviations seemed gratuitous. So I picked something /// smaller. If we come across an abbreviation bigger than this max, then we'll /// error.) /// /// [#168]: https://github.com/BurntSushi/jiff/issues/168 const ABBREVIATION_MAX: usize = 30; /// A type alias for centralizing the definition of a time zone abbreviation. /// /// Basically, this creates one single coherent place where we control the /// length of a time zone abbreviation. #[doc(hidden)] // not part of Jiff's public API pub type Abbreviation = ArrayStr; #[cfg(test)] mod tests { use core::fmt::Write; use super::*; #[test] fn fmt_write() { let mut dst = ArrayStr::<5>::new("").unwrap(); assert!(write!(&mut dst, "abcd").is_ok()); assert!(write!(&mut dst, "e").is_ok()); assert!(write!(&mut dst, "f").is_err()); } } jiff-static-0.2.16/src/shared/util/error.rs000064400000000000000000000013551046102023000166320ustar 00000000000000// auto-generated by: jiff-cli generate shared macro_rules! err { ($($tt:tt)*) => {{ crate::shared::util::error::Error::from_args(format_args!($($tt)*)) }} } pub(crate) use err; /// An error that can be returned when parsing. #[derive(Clone, Debug)] pub struct Error { message: alloc::boxed::Box, } impl Error { pub(crate) fn from_args<'a>(message: core::fmt::Arguments<'a>) -> Error { { use alloc::string::ToString; let message = message.to_string().into_boxed_str(); Error { message } } } } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Display::fmt(&self.message, f) } } jiff-static-0.2.16/src/shared/util/escape.rs000064400000000000000000000055031046102023000167400ustar 00000000000000// auto-generated by: jiff-cli generate shared /*! Provides convenience routines for escaping raw bytes. This was copied from `regex-automata` with a few light edits. */ use super::utf8; /// Provides a convenient `Debug` implementation for a `u8`. /// /// The `Debug` impl treats the byte as an ASCII, and emits a human /// readable representation of it. If the byte isn't ASCII, then it's /// emitted as a hex escape sequence. #[derive(Clone, Copy)] pub(crate) struct Byte(pub u8); impl core::fmt::Display for Byte { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { if self.0 == b' ' { return write!(f, " "); } // 10 bytes is enough for any output from ascii::escape_default. let mut bytes = [0u8; 10]; let mut len = 0; for (i, mut b) in core::ascii::escape_default(self.0).enumerate() { // capitalize \xab to \xAB if i >= 2 && b'a' <= b && b <= b'f' { b -= 32; } bytes[len] = b; len += 1; } write!(f, "{}", core::str::from_utf8(&bytes[..len]).unwrap()) } } impl core::fmt::Debug for Byte { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "\"")?; core::fmt::Display::fmt(self, f)?; write!(f, "\"")?; Ok(()) } } /// Provides a convenient `Debug` implementation for `&[u8]`. /// /// This generally works best when the bytes are presumed to be mostly /// UTF-8, but will work for anything. For any bytes that aren't UTF-8, /// they are emitted as hex escape sequences. #[derive(Clone, Copy)] pub(crate) struct Bytes<'a>(pub &'a [u8]); impl<'a> core::fmt::Display for Bytes<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { // This is a sad re-implementation of a similar impl found in bstr. let mut bytes = self.0; while let Some(result) = utf8::decode(bytes) { let ch = match result { Ok(ch) => ch, Err(errant_bytes) => { // The decode API guarantees `errant_bytes` is non-empty. write!(f, r"\x{:02x}", errant_bytes[0])?; bytes = &bytes[1..]; continue; } }; bytes = &bytes[ch.len_utf8()..]; match ch { '\0' => write!(f, "\\0")?, '\x01'..='\x7f' => { write!(f, "{}", (ch as u8).escape_ascii())?; } _ => write!(f, "{}", ch.escape_debug())?, } } Ok(()) } } impl<'a> core::fmt::Debug for Bytes<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "\"")?; core::fmt::Display::fmt(self, f)?; write!(f, "\"")?; Ok(()) } } jiff-static-0.2.16/src/shared/util/itime.rs000064400000000000000000000753461046102023000166230ustar 00000000000000// auto-generated by: jiff-cli generate shared /*! This module defines the internal core time data types. This includes physical time (i.e., a timestamp) and civil time. These types exist to provide a home for the core algorithms in a datetime crate. For example, converting from a timestamp to a Gregorian calendar date and clock time. These routines are specifically implemented on simple primitive integer types and implicitly assume that the inputs are valid (i.e., within Jiff's minimum and maximum ranges). These exist to provide `const` capabilities, and also to provide a small reusable core of important algorithms that can be shared between `jiff` and `jiff-static`. # Naming The types in this module are prefixed with letter `I` to make it clear that they are internal types. Specifically, to distinguish them from Jiff's public types. For example, `Date` versus `IDate`. */ use super::error::{err, Error}; #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct ITimestamp { pub(crate) second: i64, pub(crate) nanosecond: i32, } impl ITimestamp { const MIN: ITimestamp = ITimestamp { second: -377705023201, nanosecond: 0 }; const MAX: ITimestamp = ITimestamp { second: 253402207200, nanosecond: 999_999_999 }; /// Creates an `ITimestamp` from a Unix timestamp in seconds. #[inline] pub(crate) const fn from_second(second: i64) -> ITimestamp { ITimestamp { second, nanosecond: 0 } } /// Converts a Unix timestamp with an offset to a Gregorian datetime. /// /// The offset should correspond to the number of seconds required to /// add to this timestamp to get the local time. #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) const fn to_datetime(&self, offset: IOffset) -> IDateTime { let ITimestamp { mut second, mut nanosecond } = *self; second += offset.second as i64; let mut epoch_day = second.div_euclid(86_400) as i32; second = second.rem_euclid(86_400); if nanosecond < 0 { if second > 0 { second -= 1; nanosecond += 1_000_000_000; } else { epoch_day -= 1; second += 86_399; nanosecond += 1_000_000_000; } } let date = IEpochDay { epoch_day }.to_date(); let mut time = ITimeSecond { second: second as i32 }.to_time(); time.subsec_nanosecond = nanosecond; IDateTime { date, time } } } #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct IOffset { pub(crate) second: i32, } impl IOffset { pub(crate) const UTC: IOffset = IOffset { second: 0 }; } #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct IDateTime { pub(crate) date: IDate, pub(crate) time: ITime, } impl IDateTime { const MIN: IDateTime = IDateTime { date: IDate::MIN, time: ITime::MIN }; const MAX: IDateTime = IDateTime { date: IDate::MAX, time: ITime::MAX }; /// Converts a Gregorian datetime and its offset to a Unix timestamp. /// /// The offset should correspond to the number of seconds required to /// subtract from this datetime in order to get to UTC. #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) fn to_timestamp(&self, offset: IOffset) -> ITimestamp { let epoch_day = self.date.to_epoch_day().epoch_day; let mut second = (epoch_day as i64) * 86_400 + (self.time.to_second().second as i64); let mut nanosecond = self.time.subsec_nanosecond; second -= offset.second as i64; if epoch_day < 0 && nanosecond != 0 { second += 1; nanosecond -= 1_000_000_000; } ITimestamp { second, nanosecond } } /// Converts a Gregorian datetime and its offset to a Unix timestamp. /// /// If the timestamp would overflow Jiff's timestamp range, then this /// returns `None`. /// /// The offset should correspond to the number of seconds required to /// subtract from this datetime in order to get to UTC. #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) fn to_timestamp_checked( &self, offset: IOffset, ) -> Option { let ts = self.to_timestamp(offset); if !(ITimestamp::MIN <= ts && ts <= ITimestamp::MAX) { return None; } Some(ts) } #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) fn saturating_add_seconds(&self, seconds: i32) -> IDateTime { self.checked_add_seconds(seconds).unwrap_or_else(|_| { if seconds < 0 { IDateTime::MIN } else { IDateTime::MAX } }) } #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) fn checked_add_seconds( &self, seconds: i32, ) -> Result { let day_second = self.time.to_second().second.checked_add(seconds).ok_or_else( || err!("adding `{seconds}s` to datetime overflowed"), )?; let days = day_second.div_euclid(86400); let second = day_second.rem_euclid(86400); let date = self.date.checked_add_days(days)?; let time = ITimeSecond { second }.to_time(); Ok(IDateTime { date, time }) } } #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct IEpochDay { pub(crate) epoch_day: i32, } impl IEpochDay { const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 }; const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 }; /// Converts days since the Unix epoch to a Gregorian date. /// /// This is Neri-Schneider. There's no branching or divisions. /// /// Ref: #[cfg_attr(feature = "perf-inline", inline(always))] #[allow(non_upper_case_globals, non_snake_case)] // to mimic source pub(crate) const fn to_date(&self) -> IDate { const s: u32 = 82; const K: u32 = 719468 + 146097 * s; const L: u32 = 400 * s; let N_U = self.epoch_day as u32; let N = N_U.wrapping_add(K); let N_1 = 4 * N + 3; let C = N_1 / 146097; let N_C = (N_1 % 146097) / 4; let N_2 = 4 * N_C + 3; let P_2 = 2939745 * (N_2 as u64); let Z = (P_2 / 4294967296) as u32; let N_Y = (P_2 % 4294967296) as u32 / 2939745 / 4; let Y = 100 * C + Z; let N_3 = 2141 * N_Y + 197913; let M = N_3 / 65536; let D = (N_3 % 65536) / 2141; let J = N_Y >= 306; let year = Y.wrapping_sub(L).wrapping_add(J as u32) as i16; let month = (if J { M - 12 } else { M }) as i8; let day = (D + 1) as i8; IDate { year, month, day } } /// Returns the day of the week for this epoch day. #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) const fn weekday(&self) -> IWeekday { // Based on Hinnant's approach here, although we use ISO weekday // numbering by default. Basically, this works by using the knowledge // that 1970-01-01 was a Thursday. // // Ref: http://howardhinnant.github.io/date_algorithms.html IWeekday::from_monday_zero_offset( (self.epoch_day + 3).rem_euclid(7) as i8 ) } /// Add the given number of days to this epoch day. /// /// If this would overflow an `i32` or result in an out-of-bounds epoch /// day, then this returns an error. #[inline] pub(crate) fn checked_add(&self, amount: i32) -> Result { let epoch_day = self.epoch_day; let sum = epoch_day.checked_add(amount).ok_or_else(|| { err!("adding `{amount}` to epoch day `{epoch_day}` overflowed i32") })?; let ret = IEpochDay { epoch_day: sum }; if !(IEpochDay::MIN <= ret && ret <= IEpochDay::MAX) { return Err(err!( "adding `{amount}` to epoch day `{epoch_day}` \ resulted in `{sum}`, which is not in the required \ epoch day range of `{min}..={max}`", min = IEpochDay::MIN.epoch_day, max = IEpochDay::MAX.epoch_day, )); } Ok(ret) } } #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct IDate { pub(crate) year: i16, pub(crate) month: i8, pub(crate) day: i8, } impl IDate { const MIN: IDate = IDate { year: -9999, month: 1, day: 1 }; const MAX: IDate = IDate { year: 9999, month: 12, day: 31 }; /// Fallibly builds a new date. /// /// This checks that the given day is valid for the given year/month. /// /// No other conditions are checked. This assumes `year` and `month` are /// valid, and that `day >= 1`. #[inline] pub(crate) fn try_new( year: i16, month: i8, day: i8, ) -> Result { if day > 28 { let max_day = days_in_month(year, month); if day > max_day { return Err(err!( "day={day} is out of range for year={year} \ and month={month}, must be in range 1..={max_day}", )); } } Ok(IDate { year, month, day }) } /// Returns the date corresponding to the day of the given year. The day /// of the year should be a value in `1..=366`, with `366` only being valid /// if `year` is a leap year. /// /// This assumes that `year` is valid, but returns an error if `day` is /// not in the range `1..=366`. #[inline] pub(crate) fn from_day_of_year( year: i16, day: i16, ) -> Result { if !(1 <= day && day <= 366) { return Err(err!( "day-of-year={day} is out of range for year={year}, \ must be in range 1..={max_day}", max_day = days_in_year(year), )); } let start = IDate { year, month: 1, day: 1 }.to_epoch_day(); let end = start .checked_add(i32::from(day) - 1) .map_err(|_| { err!( "failed to find date for \ year={year} and day-of-year={day}: \ adding `{day}` to `{start}` overflows \ Jiff's range", start = start.epoch_day, ) })? .to_date(); // If we overflowed into the next year, then `day` is too big. if year != end.year { // Can only happen given day=366 and this is a leap year. debug_assert_eq!(day, 366); debug_assert!(!is_leap_year(year)); return Err(err!( "day-of-year={day} is out of range for year={year}, \ must be in range 1..={max_day}", max_day = days_in_year(year), )); } Ok(end) } /// Returns the date corresponding to the day of the given year. The day /// of the year should be a value in `1..=365`, with February 29 being /// completely ignored. That is, it is guaranteed that February 29 will /// never be returned by this function. It is impossible. /// /// This assumes that `year` is valid, but returns an error if `day` is /// not in the range `1..=365`. #[inline] pub(crate) fn from_day_of_year_no_leap( year: i16, mut day: i16, ) -> Result { if !(1 <= day && day <= 365) { return Err(err!( "day-of-year={day} is out of range for year={year}, \ must be in range 1..=365", )); } if day >= 60 && is_leap_year(year) { day += 1; } // The boundary check above guarantees this always succeeds. Ok(IDate::from_day_of_year(year, day).unwrap()) } /// Converts a Gregorian date to days since the Unix epoch. /// /// This is Neri-Schneider. There's no branching or divisions. /// /// Ref: https://github.com/cassioneri/eaf/blob/684d3cc32d14eee371d0abe4f683d6d6a49ed5c1/algorithms/neri_schneider.hpp#L83 #[cfg_attr(feature = "perf-inline", inline(always))] #[allow(non_upper_case_globals, non_snake_case)] // to mimic source pub(crate) const fn to_epoch_day(&self) -> IEpochDay { const s: u32 = 82; const K: u32 = 719468 + 146097 * s; const L: u32 = 400 * s; let year = self.year as u32; let month = self.month as u32; let day = self.day as u32; let J = month <= 2; let Y = year.wrapping_add(L).wrapping_sub(J as u32); let M = if J { month + 12 } else { month }; let D = day - 1; let C = Y / 100; let y_star = 1461 * Y / 4 - C + C / 4; let m_star = (979 * M - 2919) / 32; let N = y_star + m_star + D; let N_U = N.wrapping_sub(K); let epoch_day = N_U as i32; IEpochDay { epoch_day } } /// Returns the day of the week for this date. #[inline] pub(crate) const fn weekday(&self) -> IWeekday { self.to_epoch_day().weekday() } /// Returns the `nth` weekday of the month represented by this date. /// /// `nth` must be non-zero and otherwise in the range `-5..=5`. If it /// isn't, an error is returned. /// /// This also returns an error if `abs(nth)==5` and there is no "5th" /// weekday of this month. #[inline] pub(crate) fn nth_weekday_of_month( &self, nth: i8, weekday: IWeekday, ) -> Result { if nth == 0 || !(-5 <= nth && nth <= 5) { return Err(err!( "got nth weekday of `{nth}`, but \ must be non-zero and in range `-5..=5`", )); } if nth > 0 { let first_weekday = self.first_of_month().weekday(); let diff = weekday.since(first_weekday); let day = diff + 1 + (nth - 1) * 7; IDate::try_new(self.year, self.month, day) } else { let last = self.last_of_month(); let last_weekday = last.weekday(); let diff = last_weekday.since(weekday); let day = last.day - diff - (nth.abs() - 1) * 7; // Our math can go below 1 when nth is -5 and there is no "5th from // last" weekday in this month. Since this is outside the bounds // of `Day`, we can't let this boundary condition escape. So we // check it here. if day < 1 { return Err(err!( "day={day} is out of range for year={year} \ and month={month}, must be in range 1..={max_day}", year = self.year, month = self.month, max_day = days_in_month(self.year, self.month), )); } IDate::try_new(self.year, self.month, day) } } /// Returns the day before this date. #[inline] pub(crate) fn yesterday(self) -> Result { if self.day == 1 { if self.month == 1 { let year = self.year - 1; if year <= -10000 { return Err(err!( "returning yesterday for -9999-01-01 is not \ possible because it is less than Jiff's supported minimum date", )); } return Ok(IDate { year, month: 12, day: 31 }); } let month = self.month - 1; let day = days_in_month(self.year, month); return Ok(IDate { month, day, ..self }); } Ok(IDate { day: self.day - 1, ..self }) } /// Returns the day after this date. #[inline] pub(crate) fn tomorrow(self) -> Result { if self.day >= 28 && self.day == days_in_month(self.year, self.month) { if self.month == 12 { let year = self.year + 1; if year >= 10000 { return Err(err!( "returning tomorrow for 9999-12-31 is not \ possible because it is greater than Jiff's supported maximum date", )); } return Ok(IDate { year, month: 1, day: 1 }); } let month = self.month + 1; return Ok(IDate { month, day: 1, ..self }); } Ok(IDate { day: self.day + 1, ..self }) } /// Returns the year one year before this date. #[inline] pub(crate) fn prev_year(self) -> Result { let year = self.year - 1; if year <= -10_000 { return Err(err!( "returning previous year for {year:04}-{month:02}-{day:02} is \ not possible because it is less than Jiff's supported \ minimum date", year = self.year, month = self.month, day = self.day, )); } Ok(year) } /// Returns the year one year from this date. #[inline] pub(crate) fn next_year(self) -> Result { let year = self.year + 1; if year >= 10_000 { return Err(err!( "returning next year for {year:04}-{month:02}-{day:02} is \ not possible because it is greater than Jiff's supported \ maximum date", year = self.year, month = self.month, day = self.day, )); } Ok(year) } /// Add the number of days to this date. #[inline] pub(crate) fn checked_add_days( &self, amount: i32, ) -> Result { match amount { 0 => Ok(*self), -1 => self.yesterday(), 1 => self.tomorrow(), n => self.to_epoch_day().checked_add(n).map(|d| d.to_date()), } } #[inline] fn first_of_month(&self) -> IDate { IDate { day: 1, ..*self } } #[inline] fn last_of_month(&self) -> IDate { IDate { day: days_in_month(self.year, self.month), ..*self } } #[cfg(test)] pub(crate) fn at( &self, hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> IDateTime { let time = ITime { hour, minute, second, subsec_nanosecond }; IDateTime { date: *self, time } } } /// Represents a clock time. /// /// This uses units of hours, minutes, seconds and fractional seconds (to /// nanosecond precision). #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct ITime { pub(crate) hour: i8, pub(crate) minute: i8, pub(crate) second: i8, pub(crate) subsec_nanosecond: i32, } impl ITime { pub(crate) const ZERO: ITime = ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 }; pub(crate) const MIN: ITime = ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 }; pub(crate) const MAX: ITime = ITime { hour: 23, minute: 59, second: 59, subsec_nanosecond: 999_999_999, }; #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) const fn to_second(&self) -> ITimeSecond { let mut second: i32 = 0; second += (self.hour as i32) * 3600; second += (self.minute as i32) * 60; second += self.second as i32; ITimeSecond { second } } #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) const fn to_nanosecond(&self) -> ITimeNanosecond { let mut nanosecond: i64 = 0; nanosecond += (self.hour as i64) * 3_600_000_000_000; nanosecond += (self.minute as i64) * 60_000_000_000; nanosecond += (self.second as i64) * 1_000_000_000; nanosecond += self.subsec_nanosecond as i64; ITimeNanosecond { nanosecond } } } /// Represents a single point in the day, to second precision. #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct ITimeSecond { pub(crate) second: i32, } impl ITimeSecond { #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) const fn to_time(&self) -> ITime { let mut second = self.second; let mut time = ITime::ZERO; if second != 0 { time.hour = (second / 3600) as i8; second %= 3600; if second != 0 { time.minute = (second / 60) as i8; time.second = (second % 60) as i8; } } time } } /// Represents a single point in the day, to nanosecond precision. #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct ITimeNanosecond { pub(crate) nanosecond: i64, } impl ITimeNanosecond { #[cfg_attr(feature = "perf-inline", inline(always))] pub(crate) const fn to_time(&self) -> ITime { let mut nanosecond = self.nanosecond; let mut time = ITime::ZERO; if nanosecond != 0 { time.hour = (nanosecond / 3_600_000_000_000) as i8; nanosecond %= 3_600_000_000_000; if nanosecond != 0 { time.minute = (nanosecond / 60_000_000_000) as i8; nanosecond %= 60_000_000_000; if nanosecond != 0 { time.second = (nanosecond / 1_000_000_000) as i8; time.subsec_nanosecond = (nanosecond % 1_000_000_000) as i32; } } } time } } /// Represents a weekday. #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct IWeekday { /// Range is `1..=6` with `1=Monday`. offset: i8, } impl IWeekday { /// Creates a weekday assuming the week starts on Monday and Monday is at /// offset `0`. #[inline] pub(crate) const fn from_monday_zero_offset(offset: i8) -> IWeekday { assert!(0 <= offset && offset <= 6); IWeekday::from_monday_one_offset(offset + 1) } /// Creates a weekday assuming the week starts on Monday and Monday is at /// offset `1`. #[inline] pub(crate) const fn from_monday_one_offset(offset: i8) -> IWeekday { assert!(1 <= offset && offset <= 7); IWeekday { offset } } /// Creates a weekday assuming the week starts on Sunday and Sunday is at /// offset `0`. #[inline] pub(crate) const fn from_sunday_zero_offset(offset: i8) -> IWeekday { assert!(0 <= offset && offset <= 6); IWeekday::from_monday_zero_offset((offset - 1).rem_euclid(7)) } /// Creates a weekday assuming the week starts on Sunday and Sunday is at /// offset `1`. #[cfg(test)] // currently dead code #[inline] pub(crate) const fn from_sunday_one_offset(offset: i8) -> IWeekday { assert!(1 <= offset && offset <= 7); IWeekday::from_sunday_zero_offset(offset - 1) } /// Returns this weekday as an offset in the range `0..=6` where /// `0=Monday`. #[inline] pub(crate) const fn to_monday_zero_offset(self) -> i8 { self.to_monday_one_offset() - 1 } /// Returns this weekday as an offset in the range `1..=7` where /// `1=Monday`. #[inline] pub(crate) const fn to_monday_one_offset(self) -> i8 { self.offset } /// Returns this weekday as an offset in the range `0..=6` where /// `0=Sunday`. #[cfg(test)] // currently dead code #[inline] pub(crate) const fn to_sunday_zero_offset(self) -> i8 { (self.to_monday_zero_offset() + 1) % 7 } /// Returns this weekday as an offset in the range `1..=7` where /// `1=Sunday`. #[cfg(test)] // currently dead code #[inline] pub(crate) const fn to_sunday_one_offset(self) -> i8 { self.to_sunday_zero_offset() + 1 } #[inline] pub(crate) const fn since(self, other: IWeekday) -> i8 { (self.to_monday_zero_offset() - other.to_monday_zero_offset()) .rem_euclid(7) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum IAmbiguousOffset { Unambiguous { offset: IOffset }, Gap { before: IOffset, after: IOffset }, Fold { before: IOffset, after: IOffset }, } /// Returns true if and only if the given year is a leap year. /// /// A leap year is a year with 366 days. Typical years have 365 days. #[inline] pub(crate) const fn is_leap_year(year: i16) -> bool { // From: https://github.com/BurntSushi/jiff/pull/23 let d = if year % 25 != 0 { 4 } else { 16 }; (year % d) == 0 } /// Return the number of days in the given year. #[inline] pub(crate) const fn days_in_year(year: i16) -> i16 { if is_leap_year(year) { 366 } else { 365 } } /// Return the number of days in the given month. #[inline] pub(crate) const fn days_in_month(year: i16, month: i8) -> i8 { // From: https://github.com/BurntSushi/jiff/pull/23 if month == 2 { if is_leap_year(year) { 29 } else { 28 } } else { 30 | (month ^ month >> 3) } } #[cfg(test)] mod tests { use super::*; #[test] fn roundtrip_epochday_date() { for year in -9999..=9999 { for month in 1..=12 { for day in 1..=days_in_month(year, month) { let date = IDate { year, month, day }; let epoch_day = date.to_epoch_day(); let date_roundtrip = epoch_day.to_date(); assert_eq!(date, date_roundtrip); } } } } #[test] fn roundtrip_second_time() { for second in 0..=86_399 { let second = ITimeSecond { second }; let time = second.to_time(); let second_roundtrip = time.to_second(); assert_eq!(second, second_roundtrip); } } #[test] fn roundtrip_nanosecond_time() { for second in 0..=86_399 { for nanosecond in [0, 250_000_000, 500_000_000, 750_000_000, 900_000_000] { let nanosecond = ITimeNanosecond { nanosecond: (second * 1_000_000_000 + nanosecond), }; let time = nanosecond.to_time(); let nanosecond_roundtrip = time.to_nanosecond(); assert_eq!(nanosecond, nanosecond_roundtrip); } } } #[test] fn nth_weekday() { let d1 = IDate { year: 2017, month: 3, day: 1 }; let wday = IWeekday::from_sunday_zero_offset(5); let d2 = d1.nth_weekday_of_month(2, wday).unwrap(); assert_eq!(d2, IDate { year: 2017, month: 3, day: 10 }); let d1 = IDate { year: 2024, month: 3, day: 1 }; let wday = IWeekday::from_sunday_zero_offset(4); let d2 = d1.nth_weekday_of_month(-1, wday).unwrap(); assert_eq!(d2, IDate { year: 2024, month: 3, day: 28 }); let d1 = IDate { year: 2024, month: 3, day: 25 }; let wday = IWeekday::from_sunday_zero_offset(1); assert!(d1.nth_weekday_of_month(5, wday).is_err()); assert!(d1.nth_weekday_of_month(-5, wday).is_err()); let d1 = IDate { year: 1998, month: 1, day: 1 }; let wday = IWeekday::from_sunday_zero_offset(6); let d2 = d1.nth_weekday_of_month(5, wday).unwrap(); assert_eq!(d2, IDate { year: 1998, month: 1, day: 31 }); } #[test] fn weekday() { let wday = IWeekday::from_sunday_zero_offset(0); assert_eq!(wday.to_monday_one_offset(), 7); let wday = IWeekday::from_monday_one_offset(7); assert_eq!(wday.to_sunday_zero_offset(), 0); let wday = IWeekday::from_sunday_one_offset(1); assert_eq!(wday.to_monday_zero_offset(), 6); let wday = IWeekday::from_monday_zero_offset(6); assert_eq!(wday.to_sunday_one_offset(), 1); } #[test] fn weekday_since() { let wday1 = IWeekday::from_sunday_zero_offset(0); let wday2 = IWeekday::from_sunday_zero_offset(6); assert_eq!(wday2.since(wday1), 6); assert_eq!(wday1.since(wday2), 1); } #[test] fn leap_year() { assert!(!is_leap_year(1900)); assert!(is_leap_year(2000)); assert!(!is_leap_year(2001)); assert!(!is_leap_year(2002)); assert!(!is_leap_year(2003)); assert!(is_leap_year(2004)); } #[test] fn number_of_days_in_month() { assert_eq!(days_in_month(2024, 1), 31); assert_eq!(days_in_month(2024, 2), 29); assert_eq!(days_in_month(2024, 3), 31); assert_eq!(days_in_month(2024, 4), 30); assert_eq!(days_in_month(2024, 5), 31); assert_eq!(days_in_month(2024, 6), 30); assert_eq!(days_in_month(2024, 7), 31); assert_eq!(days_in_month(2024, 8), 31); assert_eq!(days_in_month(2024, 9), 30); assert_eq!(days_in_month(2024, 10), 31); assert_eq!(days_in_month(2024, 11), 30); assert_eq!(days_in_month(2024, 12), 31); assert_eq!(days_in_month(2025, 1), 31); assert_eq!(days_in_month(2025, 2), 28); assert_eq!(days_in_month(2025, 3), 31); assert_eq!(days_in_month(2025, 4), 30); assert_eq!(days_in_month(2025, 5), 31); assert_eq!(days_in_month(2025, 6), 30); assert_eq!(days_in_month(2025, 7), 31); assert_eq!(days_in_month(2025, 8), 31); assert_eq!(days_in_month(2025, 9), 30); assert_eq!(days_in_month(2025, 10), 31); assert_eq!(days_in_month(2025, 11), 30); assert_eq!(days_in_month(2025, 12), 31); assert_eq!(days_in_month(1900, 2), 28); assert_eq!(days_in_month(2000, 2), 29); } #[test] fn yesterday() { let d1 = IDate { year: 2025, month: 4, day: 7 }; let d2 = d1.yesterday().unwrap(); assert_eq!(d2, IDate { year: 2025, month: 4, day: 6 }); let d1 = IDate { year: 2025, month: 4, day: 1 }; let d2 = d1.yesterday().unwrap(); assert_eq!(d2, IDate { year: 2025, month: 3, day: 31 }); let d1 = IDate { year: 2025, month: 1, day: 1 }; let d2 = d1.yesterday().unwrap(); assert_eq!(d2, IDate { year: 2024, month: 12, day: 31 }); let d1 = IDate { year: -9999, month: 1, day: 1 }; assert_eq!(d1.yesterday().ok(), None); } #[test] fn tomorrow() { let d1 = IDate { year: 2025, month: 4, day: 7 }; let d2 = d1.tomorrow().unwrap(); assert_eq!(d2, IDate { year: 2025, month: 4, day: 8 }); let d1 = IDate { year: 2025, month: 3, day: 31 }; let d2 = d1.tomorrow().unwrap(); assert_eq!(d2, IDate { year: 2025, month: 4, day: 1 }); let d1 = IDate { year: 2025, month: 12, day: 31 }; let d2 = d1.tomorrow().unwrap(); assert_eq!(d2, IDate { year: 2026, month: 1, day: 1 }); let d1 = IDate { year: 9999, month: 12, day: 31 }; assert_eq!(d1.tomorrow().ok(), None); } } jiff-static-0.2.16/src/shared/util/mod.rs000064400000000000000000000002421046102023000162520ustar 00000000000000// auto-generated by: jiff-cli generate shared pub(crate) mod array_str; pub(crate) mod error; pub(crate) mod escape; pub(crate) mod itime; pub(crate) mod utf8; jiff-static-0.2.16/src/shared/util/utf8.rs000064400000000000000000000031421046102023000163630ustar 00000000000000// auto-generated by: jiff-cli generate shared /// Decodes the next UTF-8 encoded codepoint from the given byte slice. /// /// If no valid encoding of a codepoint exists at the beginning of the /// given byte slice, then a 1-3 byte slice is returned (which is guaranteed /// to be a prefix of `bytes`). That byte slice corresponds either to a single /// invalid byte, or to a prefix of a valid UTF-8 encoding of a Unicode scalar /// value (but which ultimately did not lead to a valid encoding). /// /// This returns `None` if and only if `bytes` is empty. /// /// This never panics. /// /// *WARNING*: This is not designed for performance. If you're looking for /// a fast UTF-8 decoder, this is not it. If you feel like you need one in /// this crate, then please file an issue and discuss your use case. pub(crate) fn decode(bytes: &[u8]) -> Option> { if bytes.is_empty() { return None; } let string = match core::str::from_utf8(&bytes[..bytes.len().min(4)]) { Ok(s) => s, Err(ref err) if err.valid_up_to() > 0 => { core::str::from_utf8(&bytes[..err.valid_up_to()]).unwrap() } // In this case, we want to return 1-3 bytes that make up a prefix of // a potentially valid codepoint. Err(err) => { return Some(Err( &bytes[..err.error_len().unwrap_or_else(|| bytes.len())] )) } }; // OK because we guaranteed above that `string` // must be non-empty. And thus, `str::chars` must // yield at least one Unicode scalar value. Some(Ok(string.chars().next().unwrap())) }