Sign In

jiff

Package Overview
Dependencies
Maintainers
1
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jiff - cargo Package Compare versions

Comparing version
0.2.32
to
0.2.33
+1
-1
.cargo_vcs_info.json
{
"git": {
"sha1": "bc70277861e54973e8a9be91bc92c44de56719b5"
"sha1": "ddd26a89c7f0a6fa8008f35021462e71a8db8b40"
},
"path_in_vcs": ""
}

@@ -25,5 +25,5 @@ # This file is automatically @generated by Cargo.

name = "anyhow"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"

@@ -44,5 +44,5 @@ [[package]]

name = "bitflags"
version = "2.13.0"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"

@@ -63,5 +63,5 @@ [[package]]

name = "cc"
version = "1.2.65"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
dependencies = [

@@ -104,5 +104,5 @@ "find-msvc-tools",

name = "console"
version = "0.16.3"
version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
dependencies = [

@@ -122,5 +122,5 @@ "encode_unicode",

name = "defmt"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f"
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
dependencies = [

@@ -133,11 +133,10 @@ "bitflags 1.3.2",

name = "defmt-macros"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b"
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
dependencies = [
"defmt-parser",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
"syn 2.0.119",
]

@@ -151,3 +150,3 @@

dependencies = [
"thiserror 2.0.18",
"thiserror 2.0.19",
]

@@ -197,17 +196,17 @@

name = "futures-core"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "futures-task"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
[[package]]
name = "futures-util"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
dependencies = [

@@ -263,5 +262,5 @@ "futures-core",

name = "humantime"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15"

@@ -322,3 +321,3 @@ [[package]]

name = "jiff"
version = "0.2.32"
version = "0.2.33"
dependencies = [

@@ -332,2 +331,3 @@ "anyhow",

"insta",
"jiff-core",
"jiff-static",

@@ -356,11 +356,22 @@ "jiff-tzdb",

[[package]]
name = "jiff-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
dependencies = [
"defmt",
"log",
]
[[package]]
name = "jiff-static"
version = "0.2.32"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc"
checksum = "96fb828bff41eeb269a9665f950cb359e8018f7288b8f34db24937ff54ed8fc5"
dependencies = [
"jiff-core",
"jiff-tzdb",
"proc-macro2",
"quote",
"syn",
"syn 2.0.119",
]

@@ -456,5 +467,5 @@

name = "memchr"
version = "2.8.2"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"

@@ -536,3 +547,3 @@ [[package]]

"phf_shared 0.11.3",
"rand 0.8.6",
"rand 0.8.7",
]

@@ -566,5 +577,5 @@

name = "portable-atomic"
version = "1.13.1"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"

@@ -587,28 +598,6 @@ [[package]]

[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [

@@ -624,3 +613,3 @@ "unicode-ident",

dependencies = [
"rand 0.10.1",
"rand 0.10.2",
]

@@ -630,5 +619,5 @@

name = "quote"
version = "1.0.46"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [

@@ -646,5 +635,5 @@ "proc-macro2",

name = "rand"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
dependencies = [

@@ -656,5 +645,5 @@ "rand_core 0.6.4",

name = "rand"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [

@@ -679,5 +668,5 @@ "getrandom",

name = "regex"
version = "1.12.4"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [

@@ -692,5 +681,5 @@ "aho-corasick",

name = "regex-automata"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [

@@ -714,3 +703,3 @@ "aho-corasick",

dependencies = [
"bitflags 2.13.0",
"bitflags 2.13.1",
"errno",

@@ -724,5 +713,5 @@ "libc",

name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"

@@ -746,5 +735,5 @@ [[package]]

name = "serde"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [

@@ -769,5 +758,5 @@ "serde_core",

name = "serde_core"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [

@@ -779,9 +768,9 @@ "serde_derive",

name = "serde_derive"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 3.0.0",
]

@@ -857,3 +846,3 @@

"quote",
"syn",
"syn 2.0.119",
]

@@ -863,5 +852,5 @@

name = "syn"
version = "2.0.118"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [

@@ -874,2 +863,13 @@ "proc-macro2",

[[package]]
name = "syn"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2fac314a64dc9a36e61a9eb4261a5e9bbfbc922b27e518af97bc32b926cf967"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tabwriter"

@@ -907,7 +907,7 @@ version = "1.4.1"

name = "thiserror"
version = "2.0.18"
version = "2.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
dependencies = [
"thiserror-impl 2.0.18",
"thiserror-impl 2.0.19",
]

@@ -923,3 +923,3 @@

"quote",
"syn",
"syn 2.0.119",
]

@@ -929,9 +929,9 @@

name = "thiserror-impl"
version = "2.0.18"
version = "2.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 3.0.0",
]

@@ -941,5 +941,5 @@

name = "time"
version = "0.3.51"
version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327"
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
dependencies = [

@@ -965,5 +965,5 @@ "deranged",

name = "time-macros"
version = "0.2.30"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935"
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
dependencies = [

@@ -1060,3 +1060,3 @@ "num-conv",

"quote",
"syn",
"syn 2.0.119",
"wasm-bindgen-shared",

@@ -1124,3 +1124,3 @@ ]

"quote",
"syn",
"syn 2.0.119",
]

@@ -1136,3 +1136,3 @@

"quote",
"syn",
"syn 2.0.119",
]

@@ -1181,4 +1181,4 @@

name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO

name = "jiff"
version = "0.2.32"
version = "0.2.33"
authors = ["Andrew Gallant <jamslam@gmail.com>"]

@@ -72,2 +72,3 @@ build = false

alloc = [
"jcore/alloc",
"serde_core?/alloc",

@@ -86,3 +87,6 @@ "portable-atomic-util/alloc",

]
defmt = ["dep:defmt"]
defmt = [
"dep:defmt",
"jcore/defmt",
]
js = [

@@ -92,3 +96,6 @@ "dep:wasm-bindgen",

]
logging = ["dep:log"]
logging = [
"dep:log",
"jcore/logging",
]
perf-inline = []

@@ -103,6 +110,10 @@ serde = ["dep:serde_core"]

"alloc",
"jcore/std",
"log?/std",
"serde_core?/std",
]
tz-fat = ["jiff-static?/tz-fat"]
tz-fat = [
"jcore/tz-fat",
"jiff-static?/tz-fat",
]
tz-system = [

@@ -135,2 +146,7 @@ "std",

[dependencies.jcore]
version = "0.1.0"
default-features = false
package = "jiff-core"
[dependencies.jiff-static]

@@ -219,3 +235,3 @@ version = "0.2"

[target."cfg(any())".dependencies.jiff-static]
version = "=0.2.32"
version = "=0.2.33"

@@ -246,4 +262,7 @@ [target.'cfg(any(windows, target_family = "wasm"))'.dependencies.jiff-tzdb-platform]

[profile.test]
opt-level = 1
[profile.testrelease]
debug-assertions = false
inherits = "test"

@@ -0,1 +1,3 @@

use jcore::civil::ISOWeekDate as JISOWeekDate;
use crate::{

@@ -5,3 +7,2 @@ civil::{Date, DateTime, Weekday},

fmt::temporal::{DEFAULT_DATETIME_PARSER, DEFAULT_DATETIME_PRINTER},
util::b,
Zoned,

@@ -158,5 +159,3 @@ };

pub struct ISOWeekDate {
year: i16,
week: i8,
weekday: Weekday,
pub(crate) inner: JISOWeekDate,
}

@@ -169,7 +168,3 @@

/// value. That is, `-9999-01-01`.
pub const MIN: ISOWeekDate = ISOWeekDate {
year: b::ISOYear::MIN,
week: b::ISOWeek::MIN,
weekday: Weekday::Monday,
};
pub const MIN: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::MIN };

@@ -180,8 +175,3 @@ /// The minimum representable ISO week date.

/// value. That is, `9999-12-31`.
pub const MAX: ISOWeekDate = ISOWeekDate {
year: b::ISOYear::MAX,
// Technical max is 52, but 9999 is not a leap year.
week: 52,
weekday: Weekday::Friday,
};
pub const MAX: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::MAX };

@@ -203,4 +193,3 @@ /// The first day of the zeroth year.

/// ```
pub const ZERO: ISOWeekDate =
ISOWeekDate { year: 0, week: 1, weekday: Weekday::Monday };
pub const ZERO: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::ZERO };

@@ -244,64 +233,7 @@ /// Create a new ISO week date from it constituent parts.

) -> Result<ISOWeekDate, Error> {
let year = b::ISOYear::check(year)?;
let week = b::ISOWeek::check(week)?;
// All combinations of years, weeks and weekdays allowed by our
// range types are valid ISO week dates with one exception: a week
// number of 53 is only valid for "long" years. Or years with an ISO
// leap week. It turns out this only happens when the last day of the
// year is a Thursday.
//
// Note that if the ranges in this crate are changed, this could be
// a little trickier if the range of ISOYear is different from Year.
debug_assert_eq!(b::Year::MIN, b::ISOYear::MIN);
debug_assert_eq!(b::Year::MAX, b::ISOYear::MAX);
if week == 53 && !is_long_year(year) {
return Err(b::ISOWeek::error().into());
}
// And also, the maximum Date constrains what we can utter with
// ISOWeekDate so that we can preserve infallible conversions between
// them. So since 9999-12-31 maps to 9999 W52 Friday, it follows that
// Saturday and Sunday are not allowed when the year is at the maximum
// value. So reject them.
//
// We don't need to worry about the minimum because the minimum date
// (-9999-01-01) corresponds also to the minimum possible combination
// of an ISO week date's fields: -9999 W01 Monday. Nice.
if year == b::ISOYear::MAX
&& week == 52
&& weekday.to_monday_zero_offset()
> Weekday::Friday.to_monday_zero_offset()
{
return Err(b::WeekdayMondayOne::error().into());
}
Ok(ISOWeekDate { year, week, weekday })
JISOWeekDate::new(year, week, weekday.to_jcore())
.map(ISOWeekDate::from_jcore)
.map_err(Error::jcore_range)
}
/// Like `ISOWeekDate::new`, but constrains out-of-bounds values
/// to their closest valid equivalent.
///
/// For example, given `9999 W52 Saturday`, this will return
/// `9999 W52 Friday`.
#[cfg(test)]
#[inline]
fn new_constrain(
year: i16,
mut week: i8,
mut weekday: Weekday,
) -> ISOWeekDate {
debug_assert_eq!(b::Year::MIN, b::ISOYear::MIN);
debug_assert_eq!(b::Year::MAX, b::ISOYear::MAX);
if week == 53 && !is_long_year(year) {
week = 52;
}
if year == b::ISOYear::MAX
&& week == 52
&& weekday.to_monday_zero_offset()
> Weekday::Friday.to_monday_zero_offset()
{
weekday = Weekday::Friday;
}
ISOWeekDate { year, week, weekday }
}
/// Converts a Gregorian date to an ISO week date.

@@ -351,3 +283,3 @@ ///

pub fn year(self) -> i16 {
self.year
self.inner.year()
}

@@ -377,3 +309,3 @@

pub fn week(self) -> i8 {
self.week
self.inner.week()
}

@@ -405,3 +337,3 @@

pub fn weekday(self) -> Weekday {
self.weekday
Weekday::from_jcore(self.inner.weekday())
}

@@ -416,3 +348,3 @@

/// Since `-9999-01-01` falls on a Monday, it follows that the minimum
/// support Gregorian date is exactly equivalent to the minimum supported
/// supported Gregorian date is exactly equivalent to the minimum supported
/// ISO 8601 week date. This means that this routine can never actually

@@ -445,8 +377,6 @@ /// fail, but only insomuch as the minimums line up. For that reason, and

pub fn first_of_week(self) -> Result<ISOWeekDate, Error> {
// I believe this can never return an error because `Monday` is in
// bounds for all possible year-and-week combinations. This is *only*
// because -9999-01-01 corresponds to -9999-W01-Monday. Which is kinda
// lucky. And I guess if we ever change the ranges, this could become
// fallible.
ISOWeekDate::new(self.year(), self.week(), Weekday::Monday)
self.inner
.first_of_week()
.map(ISOWeekDate::from_jcore)
.map_err(Error::jcore_range)
}

@@ -488,6 +418,6 @@

pub fn last_of_week(self) -> Result<ISOWeekDate, Error> {
// This can return an error when in the last week of the maximum year
// supported by Jiff. That's because the Saturday and Sunday of that
// week are actually in Gregorian year 10,000.
ISOWeekDate::new(self.year(), self.week(), Weekday::Sunday)
self.inner
.last_of_week()
.map(ISOWeekDate::from_jcore)
.map_err(Error::jcore_range)
}

@@ -530,7 +460,6 @@

pub fn first_of_year(self) -> Result<ISOWeekDate, Error> {
// I believe this can never return an error because `Monday` is in
// bounds for all possible years. This is *only* because -9999-01-01
// corresponds to -9999-W01-Monday. Which is kinda lucky. And I guess
// if we ever change the ranges, this could become fallible.
ISOWeekDate::new(self.year(), 1, Weekday::Monday)
self.inner
.first_of_year()
.map(ISOWeekDate::from_jcore)
.map_err(Error::jcore_range)
}

@@ -580,6 +509,6 @@

pub fn last_of_year(self) -> Result<ISOWeekDate, Error> {
// This can return an error when in the maximum year supported by
// Jiff. That's because the last Saturday and Sunday of that year are
// actually in Gregorian year 10,000.
ISOWeekDate::new(self.year(), self.weeks_in_year(), Weekday::Sunday)
self.inner
.last_of_year()
.map(ISOWeekDate::from_jcore)
.map_err(Error::jcore_range)
}

@@ -606,7 +535,3 @@

pub fn days_in_year(self) -> i16 {
if self.in_long_year() {
371
} else {
364
}
self.inner.days_in_year()
}

@@ -633,7 +558,3 @@

pub fn weeks_in_year(self) -> i8 {
if self.in_long_year() {
53
} else {
52
}
self.inner.weeks_in_year()
}

@@ -659,3 +580,3 @@

pub fn in_long_year(self) -> bool {
is_long_year(self.year())
self.inner.in_long_year()
}

@@ -687,5 +608,6 @@

pub fn tomorrow(self) -> Result<ISOWeekDate, Error> {
// I suppose we could probably implement this in a more efficient
// manner but avoiding the roundtrip through Gregorian dates.
self.date().tomorrow().map(|d| d.iso_week_date())
self.inner
.tomorrow()
.map(ISOWeekDate::from_jcore)
.map_err(Error::jcore_range)
}

@@ -717,5 +639,6 @@

pub fn yesterday(self) -> Result<ISOWeekDate, Error> {
// I suppose we could probably implement this in a more efficient
// manner but avoiding the roundtrip through Gregorian dates.
self.date().yesterday().map(|d| d.iso_week_date())
self.inner
.yesterday()
.map(ISOWeekDate::from_jcore)
.map_err(Error::jcore_range)
}

@@ -743,2 +666,12 @@

}
#[inline]
pub(crate) const fn from_jcore(week_date: JISOWeekDate) -> ISOWeekDate {
ISOWeekDate { inner: week_date }
}
#[inline]
pub(crate) const fn to_jcore(self) -> JISOWeekDate {
self.inner
}
}

@@ -888,70 +821,1 @@

}
#[cfg(test)]
impl quickcheck::Arbitrary for ISOWeekDate {
fn arbitrary(g: &mut quickcheck::Gen) -> ISOWeekDate {
let year = b::ISOYear::arbitrary(g);
let week = b::ISOWeek::arbitrary(g);
let weekday = Weekday::arbitrary(g);
ISOWeekDate::new_constrain(year, week, weekday)
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = ISOWeekDate>> {
alloc::boxed::Box::new(
(self.year(), self.week(), self.weekday()).shrink().map(
|(year, week, weekday)| {
ISOWeekDate::new_constrain(year, week, weekday)
},
),
)
}
}
/// Returns true if the given ISO year is a "long" year or not.
///
/// A "long" year is a year with 53 weeks. Otherwise, it's a "short" year
/// with 52 weeks.
fn is_long_year(year: i16) -> bool {
// Inspired by: https://en.wikipedia.org/wiki/ISO_week_date#Weeks_per_year
let last =
Date::new(year, 12, 31).expect("last day of year is always valid");
let weekday = last.weekday();
weekday == Weekday::Thursday
|| (last.in_leap_year() && weekday == Weekday::Friday)
}
#[cfg(not(miri))]
#[cfg(test)]
mod tests {
use super::*;
quickcheck::quickcheck! {
fn prop_all_long_years_have_53rd_week(year: i16) -> quickcheck::TestResult {
if b::Year::check(year).is_err() {
return quickcheck::TestResult::discard();
}
quickcheck::TestResult::from_bool(!is_long_year(year)
|| ISOWeekDate::new(year, 53, Weekday::Sunday).is_ok())
}
fn prop_prev_day_is_less(wd: ISOWeekDate) -> quickcheck::TestResult {
use crate::ToSpan;
if wd == ISOWeekDate::MIN {
return quickcheck::TestResult::discard();
}
let prev_date = wd.date().checked_add(-1.days()).unwrap();
quickcheck::TestResult::from_bool(prev_date.iso_week_date() < wd)
}
fn prop_next_day_is_greater(wd: ISOWeekDate) -> quickcheck::TestResult {
use crate::ToSpan;
if wd == ISOWeekDate::MAX {
return quickcheck::TestResult::discard();
}
let next_date = wd.date().checked_add(1.days()).unwrap();
quickcheck::TestResult::from_bool(wd < next_date.iso_week_date())
}
}
}

@@ -178,14 +178,14 @@ /*!

/// ```
/// use jiff::civil::DateTime;
/// use jiff::civil::datetime;
///
/// let d = DateTime::constant(2024, 2, 29, 21, 30, 5, 123_456_789);
/// assert_eq!(d.date().year(), 2024);
/// assert_eq!(d.date().month(), 2);
/// assert_eq!(d.date().day(), 29);
/// assert_eq!(d.time().hour(), 21);
/// assert_eq!(d.time().minute(), 30);
/// assert_eq!(d.time().second(), 5);
/// assert_eq!(d.time().millisecond(), 123);
/// assert_eq!(d.time().microsecond(), 456);
/// assert_eq!(d.time().nanosecond(), 789);
/// let dt = datetime(2024, 2, 29, 21, 30, 5, 123_456_789);
/// assert_eq!(dt.date().year(), 2024);
/// assert_eq!(dt.date().month(), 2);
/// assert_eq!(dt.date().day(), 29);
/// assert_eq!(dt.time().hour(), 21);
/// assert_eq!(dt.time().minute(), 30);
/// assert_eq!(dt.time().second(), 5);
/// assert_eq!(dt.time().millisecond(), 123);
/// assert_eq!(dt.time().microsecond(), 456);
/// assert_eq!(dt.time().nanosecond(), 789);
/// ```

@@ -273,12 +273,12 @@ #[inline]

/// ```
/// use jiff::civil::Time;
/// use jiff::civil::time;
///
/// const BEDTIME: Time = Time::constant(21, 30, 5, 123_456_789);
/// assert_eq!(BEDTIME.hour(), 21);
/// assert_eq!(BEDTIME.minute(), 30);
/// assert_eq!(BEDTIME.second(), 5);
/// assert_eq!(BEDTIME.millisecond(), 123);
/// assert_eq!(BEDTIME.microsecond(), 456);
/// assert_eq!(BEDTIME.nanosecond(), 789);
/// assert_eq!(BEDTIME.subsec_nanosecond(), 123_456_789);
/// let t = time(21, 30, 5, 123_456_789);
/// assert_eq!(t.hour(), 21);
/// assert_eq!(t.minute(), 30);
/// assert_eq!(t.second(), 5);
/// assert_eq!(t.millisecond(), 123);
/// assert_eq!(t.microsecond(), 456);
/// assert_eq!(t.nanosecond(), 789);
/// assert_eq!(t.subsec_nanosecond(), 123_456_789);
/// ```

@@ -285,0 +285,0 @@ #[inline]

@@ -1,2 +0,2 @@

use crate::{error::Error, shared::util::itime::IWeekday, util::b};
use crate::error::Error;

@@ -123,21 +123,7 @@ /// A representation for the day of the week.

pub fn from_monday_zero_offset(offset: i8) -> Result<Weekday, Error> {
Ok(Weekday::from_monday_zero_offset_unchecked(
b::WeekdayMondayZero::check(offset)?,
))
jcore::civil::Weekday::from_monday_zero_offset(offset)
.map_err(Error::jcore_range)
.map(Weekday::from_jcore)
}
#[inline]
fn from_monday_zero_offset_unchecked(offset: impl Into<i64>) -> Weekday {
match offset.into() {
0 => Weekday::Monday,
1 => Weekday::Tuesday,
2 => Weekday::Wednesday,
3 => Weekday::Thursday,
4 => Weekday::Friday,
5 => Weekday::Saturday,
6 => Weekday::Sunday,
_ => unreachable!(),
}
}
/// Convert an offset to a structured `Weekday`.

@@ -168,4 +154,5 @@ ///

pub fn from_monday_one_offset(offset: i8) -> Result<Weekday, Error> {
let offset = b::WeekdayMondayOne::check(offset)?;
Weekday::from_monday_zero_offset(offset - 1)
jcore::civil::Weekday::from_monday_one_offset(offset)
.map_err(Error::jcore_range)
.map(Weekday::from_jcore)
}

@@ -200,4 +187,5 @@

pub fn from_sunday_zero_offset(offset: i8) -> Result<Weekday, Error> {
let offset = b::WeekdaySundayZero::check(offset)?;
Weekday::from_monday_zero_offset((offset - 1).rem_euclid(7))
jcore::civil::Weekday::from_sunday_zero_offset(offset)
.map_err(Error::jcore_range)
.map(Weekday::from_jcore)
}

@@ -230,4 +218,5 @@

pub fn from_sunday_one_offset(offset: i8) -> Result<Weekday, Error> {
let offset = b::WeekdaySundayOne::check(offset)?;
Weekday::from_monday_zero_offset((offset - 2).rem_euclid(7))
jcore::civil::Weekday::from_sunday_one_offset(offset)
.map_err(Error::jcore_range)
.map(Weekday::from_jcore)
}

@@ -251,3 +240,3 @@

pub fn to_monday_zero_offset(self) -> i8 {
self.to_monday_one_offset() - 1
self.to_jcore().to_monday_zero_offset()
}

@@ -271,3 +260,3 @@

pub fn to_monday_one_offset(self) -> i8 {
self as i8
self.to_jcore().to_monday_one_offset()
}

@@ -291,8 +280,3 @@

pub fn to_sunday_zero_offset(self) -> i8 {
let offset = self.to_monday_one_offset();
if offset == 7 {
0
} else {
offset
}
self.to_jcore().to_sunday_zero_offset()
}

@@ -316,3 +300,3 @@

pub fn to_sunday_one_offset(self) -> i8 {
self.to_sunday_zero_offset() + 1
self.to_jcore().to_sunday_one_offset()
}

@@ -378,4 +362,3 @@

pub fn since(self, other: Weekday) -> i8 {
(self.to_monday_zero_offset() - other.to_monday_zero_offset())
.rem_euclid(7)
self.to_jcore().since(other.to_jcore())
}

@@ -401,3 +384,3 @@

pub fn until(self, other: Weekday) -> i8 {
other.since(self)
self.to_jcore().until(other.to_jcore())
}

@@ -440,7 +423,3 @@

pub fn wrapping_add<D: Into<i64>>(self, days: D) -> Weekday {
let start = i64::from(self.to_monday_zero_offset());
let rhs = days.into();
let end = start.wrapping_add(rhs).rem_euclid(7);
// Always valid because of the mod 7 above.
Weekday::from_monday_zero_offset_unchecked(end)
Weekday::from_jcore(self.to_jcore().wrapping_add(days.into()))
}

@@ -485,3 +464,3 @@

pub fn wrapping_sub<D: Into<i64>>(self, days: D) -> Weekday {
self.wrapping_add(-days.into())
Weekday::from_jcore(self.to_jcore().wrapping_sub(days.into()))
}

@@ -509,12 +488,3 @@

pub fn cycle_forward(self) -> WeekdaysForward {
let nexts = [
self,
self.wrapping_add(1),
self.wrapping_add(2),
self.wrapping_add(3),
self.wrapping_add(4),
self.wrapping_add(5),
self.wrapping_add(6),
];
WeekdaysForward { it: nexts.into_iter().cycle() }
WeekdaysForward { it: self.to_jcore().cycle_forward() }
}

@@ -542,12 +512,3 @@

pub fn cycle_reverse(self) -> WeekdaysReverse {
let nexts = [
self,
self.wrapping_sub(1),
self.wrapping_sub(2),
self.wrapping_sub(3),
self.wrapping_sub(4),
self.wrapping_sub(5),
self.wrapping_sub(6),
];
WeekdaysReverse { it: nexts.into_iter().cycle() }
WeekdaysReverse { it: self.to_jcore().cycle_reverse() }
}

@@ -558,12 +519,11 @@ }

#[inline]
pub(crate) fn from_iweekday(iweekday: IWeekday) -> Weekday {
match iweekday.to_monday_one_offset() {
1 => Weekday::Monday,
2 => Weekday::Tuesday,
3 => Weekday::Wednesday,
4 => Weekday::Thursday,
5 => Weekday::Friday,
6 => Weekday::Saturday,
7 => Weekday::Sunday,
_ => unreachable!(),
pub(crate) const fn from_jcore(weekday: jcore::civil::Weekday) -> Weekday {
match weekday {
jcore::civil::Weekday::Monday => Weekday::Monday,
jcore::civil::Weekday::Tuesday => Weekday::Tuesday,
jcore::civil::Weekday::Wednesday => Weekday::Wednesday,
jcore::civil::Weekday::Thursday => Weekday::Thursday,
jcore::civil::Weekday::Friday => Weekday::Friday,
jcore::civil::Weekday::Saturday => Weekday::Saturday,
jcore::civil::Weekday::Sunday => Weekday::Sunday,
}

@@ -573,4 +533,12 @@ }

#[inline]
pub(crate) fn to_iweekday(self) -> IWeekday {
IWeekday::from_monday_one_offset(self.to_monday_one_offset())
pub(crate) const fn to_jcore(self) -> jcore::civil::Weekday {
match self {
Weekday::Monday => jcore::civil::Weekday::Monday,
Weekday::Tuesday => jcore::civil::Weekday::Tuesday,
Weekday::Wednesday => jcore::civil::Weekday::Wednesday,
Weekday::Thursday => jcore::civil::Weekday::Thursday,
Weekday::Friday => jcore::civil::Weekday::Friday,
Weekday::Saturday => jcore::civil::Weekday::Saturday,
Weekday::Sunday => jcore::civil::Weekday::Sunday,
}
}

@@ -615,3 +583,3 @@ }

// Since addition is commutative, we don't might if users write `n + weekday`
// Since addition is commutative, we don't care if users write `n + weekday`
// or `weekday + n`.

@@ -754,3 +722,3 @@

fn arbitrary(g: &mut quickcheck::Gen) -> Weekday {
let offset = b::WeekdayMondayZero::arbitrary(g);
let offset = crate::util::b::WeekdayMondayZero::arbitrary(g);
Weekday::from_monday_zero_offset(offset).unwrap()

@@ -773,3 +741,3 @@ }

pub struct WeekdaysForward {
it: core::iter::Cycle<core::array::IntoIter<Weekday, 7>>,
it: jcore::civil::WeekdaysForward,
}

@@ -782,3 +750,3 @@

fn next(&mut self) -> Option<Weekday> {
self.it.next()
self.it.next().map(Weekday::from_jcore)
}

@@ -794,3 +762,3 @@ }

pub struct WeekdaysReverse {
it: core::iter::Cycle<core::array::IntoIter<Weekday, 7>>,
it: jcore::civil::WeekdaysReverse,
}

@@ -803,3 +771,3 @@

fn next(&mut self) -> Option<Weekday> {
self.it.next()
self.it.next().map(Weekday::from_jcore)
}

@@ -809,18 +777,1 @@ }

impl core::iter::FusedIterator for WeekdaysReverse {}
#[cfg(test)]
mod tests {
use super::*;
quickcheck::quickcheck! {
fn prop_since_add_equals_self(wd1: Weekday, wd2: Weekday) -> bool {
let days = wd1.since(wd2);
wd2.wrapping_add(days) == wd1
}
fn prop_until_add_equals_other(wd1: Weekday, wd2: Weekday) -> bool {
let days = wd1.until(wd2);
wd1.wrapping_add(days) == wd2
}
}
}

@@ -77,3 +77,3 @@ use core::time::Duration as UnsignedDuration;

let sdur = if udur.as_secs() == i64::MIN.unsigned_abs() {
SignedDuration::new_without_nano_overflow(
SignedDuration::new_unchecked(
i64::MIN,

@@ -80,0 +80,0 @@ // OK because `udur.subsec_nanos()` < 999_999_999.

@@ -120,3 +120,3 @@ use crate::util::{

*self.root().kind(),
Bounds(_) | SpecialBounds(_) | ITimeRange(_)
Bounds(_) | SpecialBounds(_) | JcoreRange(_)
)

@@ -208,28 +208,41 @@ }

/// Builds a `jiff::Error` from a `jcore::tz::posix::ParseError`.
///
/// This very much cannot be added as a `From` impl because that would
/// introduce a public dependency on `jcore`.
#[inline(never)]
#[cold]
pub(crate) fn special_bounds(err: SpecialBoundsError) -> Error {
Error::from(ErrorKind::SpecialBounds(err))
}
/// Creates a new error from the special "shared" error type.
pub(crate) fn itime_range(
err: crate::shared::util::itime::RangeError,
pub(crate) fn jcore_posix_parse(
err: jcore::tz::posix::ParseError,
) -> Error {
Error::from(ErrorKind::ITimeRange(err))
Error::from(ErrorKind::JcorePosixParse(err))
}
/// Creates a new error from the special TZif error type.
/// Builds a `jiff::Error` from a `jcore::tz::tzif::ParseError`.
///
/// This very much cannot be added as a `From` impl because that would
/// introduce a public dependency on `jcore`.
#[cfg(feature = "alloc")]
pub(crate) fn tzif(err: crate::shared::tzif::TzifError) -> Error {
Error::from(ErrorKind::Tzif(err))
#[inline(never)]
#[cold]
pub(crate) fn jcore_tzif_parse(err: jcore::tz::tzif::ParseError) -> Error {
Error::from(ErrorKind::JcoreTzifParse(err))
}
/// Creates a new error from the special `PosixTimeZoneError` type.
pub(crate) fn posix_tz(
err: crate::shared::posix::PosixTimeZoneError,
) -> Error {
Error::from(ErrorKind::PosixTz(err))
/// Builds a `jiff::Error` from a `jcore::bounds::RangeError`.
///
/// This very much cannot be added as a `From` impl because that would
/// introduce a public dependency on `jcore`.
#[inline(never)]
#[cold]
pub(crate) fn jcore_range(err: jcore::bounds::RangeError) -> Error {
Error::from(ErrorKind::JcoreRange(err))
}
#[inline(never)]
#[cold]
pub(crate) fn special_bounds(err: SpecialBoundsError) -> Error {
Error::from(ErrorKind::SpecialBounds(err))
}
/// A convenience constructor for building an I/O error.

@@ -445,7 +458,9 @@ ///

IO(IOError),
ITimeRange(crate::shared::util::itime::RangeError),
JcorePosixParse(jcore::tz::posix::ParseError),
#[cfg(feature = "alloc")]
JcoreTzifParse(jcore::tz::tzif::ParseError),
JcoreRange(jcore::bounds::RangeError),
OsStrUtf8(self::util::OsStrUtf8Error),
ParseInt(self::util::ParseIntError),
ParseFraction(self::util::ParseFractionError),
PosixTz(crate::shared::posix::PosixTimeZoneError),
RoundingIncrement(self::util::RoundingIncrementError),

@@ -461,3 +476,2 @@ SignedDuration(self::signed_duration::Error),

TzOffset(self::tz::offset::Error),
TzPosix(self::tz::posix::Error),
TzSystem(self::tz::system::Error),

@@ -467,4 +481,2 @@ TzTimeZone(self::tz::timezone::Error),

TzZic(self::tz::zic::Error),
#[cfg(feature = "alloc")]
Tzif(crate::shared::tzif::TzifError),
Unknown,

@@ -481,2 +493,6 @@ Zoned(self::zoned::Error),

Bounds(ref msg) => msg.fmt(f),
JcorePosixParse(ref msg) => msg.fmt(f),
#[cfg(feature = "alloc")]
JcoreTzifParse(ref msg) => msg.fmt(f),
JcoreRange(ref msg) => msg.fmt(f),
Civil(ref err) => err.fmt(f),

@@ -497,7 +513,5 @@ CrateFeature(ref err) => err.fmt(f),

IO(ref err) => err.fmt(f),
ITimeRange(ref err) => err.fmt(f),
OsStrUtf8(ref err) => err.fmt(f),
ParseInt(ref err) => err.fmt(f),
ParseFraction(ref err) => err.fmt(f),
PosixTz(ref err) => err.fmt(f),
RoundingIncrement(ref err) => err.fmt(f),

@@ -513,8 +527,5 @@ SignedDuration(ref err) => err.fmt(f),

TzOffset(ref err) => err.fmt(f),
TzPosix(ref err) => err.fmt(f),
TzSystem(ref err) => err.fmt(f),
TzTimeZone(ref err) => err.fmt(f),
TzZic(ref err) => err.fmt(f),
#[cfg(feature = "alloc")]
Tzif(ref err) => err.fmt(f),
Unknown => f.write_str("unknown Jiff error"),

@@ -760,2 +771,11 @@ Zoned(ref err) => err.fmt(f),

// This trait impl is okay because `IntoError` is a crate-internal trait. So
// this impl will not cause a public dependency on `jcore`.
impl IntoError for jcore::bounds::RangeError {
#[inline(always)]
fn into_error(self) -> Error {
Error::jcore_range(self)
}
}
/// A trait for contextualizing error values.

@@ -762,0 +782,0 @@ ///

@@ -5,5 +5,4 @@ pub(crate) mod ambiguous;

pub(crate) mod offset;
pub(crate) mod posix;
pub(crate) mod system;
pub(crate) mod timezone;
pub(crate) mod zic;

@@ -1,3 +0,5 @@

use crate::{error, util::b, Unit};
use jcore::constants as c;
use crate::{error, Unit};
#[derive(Clone, Debug)]

@@ -150,13 +152,13 @@ #[cfg_attr(feature = "defmt", derive(defmt::Format))]

match *self {
NanosPerCivilDay => b::NANOS_PER_CIVIL_DAY,
MicrosPerCivilDay => b::MICROS_PER_CIVIL_DAY,
MillisPerCivilDay => b::MILLIS_PER_CIVIL_DAY,
SecsPerCivilDay => b::SECS_PER_CIVIL_DAY,
MinsPerCivilDay => b::MINS_PER_CIVIL_DAY,
HoursPerCivilDay => b::HOURS_PER_CIVIL_DAY,
NanosPerMicro => b::NANOS_PER_MICRO,
MicrosPerMilli => b::MICROS_PER_MILLI,
MillisPerSec => b::MILLIS_PER_SEC,
SecsPerMin => b::SECS_PER_MIN,
MinsPerHour => b::MINS_PER_HOUR,
NanosPerCivilDay => c::NANOS_PER_CIVIL_DAY,
MicrosPerCivilDay => c::MICROS_PER_CIVIL_DAY,
MillisPerCivilDay => c::MILLIS_PER_CIVIL_DAY,
SecsPerCivilDay => c::SECS_PER_CIVIL_DAY,
MinsPerCivilDay => c::MINS_PER_CIVIL_DAY,
HoursPerCivilDay => c::HOURS_PER_CIVIL_DAY,
NanosPerMicro => c::NANOS_PER_MICRO,
MicrosPerMilli => c::MICROS_PER_MILLI,
MillisPerSec => c::MILLIS_PER_SEC,
SecsPerMin => c::SECS_PER_MIN,
MinsPerHour => c::MINS_PER_HOUR,
Days => 2,

@@ -163,0 +165,0 @@ }

@@ -0,1 +1,3 @@

use jcore::bounds::Sign;
use crate::{

@@ -8,3 +10,3 @@ error::{fmt::friendly::Error as E, ErrorContext},

},
util::{b::Sign, parse},
util::parse,
Error, SignedDuration, Span, Unit,

@@ -11,0 +13,0 @@ };

@@ -104,2 +104,4 @@ /*!

use jcore::{bounds::Sign, constants as c};
use crate::{

@@ -210,3 +212,3 @@ error::{fmt::offset::Error as E, Error, ErrorContext},

/// either `1` or `-1`, never `0`.
sign: b::Sign,
sign: Sign,
/// The hours component. This is non-optional because every UTC offset must

@@ -232,5 +234,5 @@ /// have at least hours.

fn to_offset(&self) -> Result<Offset, Error> {
let mut seconds = i32::from(self.hours) * b::SECS_PER_HOUR_32;
let mut seconds = i32::from(self.hours) * c::SECS_PER_HOUR_32;
if let Some(part_minutes) = self.minutes {
seconds += i32::from(part_minutes) * b::SECS_PER_MIN_32;
seconds += i32::from(part_minutes) * c::SECS_PER_MIN_32;
}

@@ -579,8 +581,8 @@ if let Some(part_seconds) = self.seconds {

input: &'i [u8],
) -> Result<Parsed<'i, b::Sign>, Error> {
) -> Result<Parsed<'i, Sign>, Error> {
let sign = input.get(0).copied().ok_or(E::EndOfInputNumeric)?;
let sign = if sign == b'+' {
b::Sign::Positive
Sign::Positive
} else if sign == b'-' {
b::Sign::Negative
Sign::Negative
} else {

@@ -599,3 +601,4 @@ return Err(Error::from(E::InvalidSignPlusOrMinus));

parse::split(input, 2).ok_or(E::EndOfInputHour)?;
let hours = b::OffsetHours::parse(hours).context(E::ParseHours)?;
let hours =
parse::bi64::<b::OffsetHours>(hours).context(E::ParseHours)?;
Ok(Parsed { value: hours, input })

@@ -611,4 +614,4 @@ }

parse::split(input, 2).ok_or(E::EndOfInputMinute)?;
let minutes =
b::OffsetMinutes::parse(minutes).context(E::ParseMinutes)?;
let minutes = parse::bi64::<b::OffsetMinutes>(minutes)
.context(E::ParseMinutes)?;
Ok(Parsed { value: minutes, input })

@@ -624,4 +627,4 @@ }

parse::split(input, 2).ok_or(E::EndOfInputSecond)?;
let seconds =
b::OffsetSeconds::parse(seconds).context(E::ParseSeconds)?;
let seconds = parse::bi64::<b::OffsetSeconds>(seconds)
.context(E::ParseSeconds)?;
Ok(Parsed { value: seconds, input })

@@ -1025,3 +1028,3 @@ }

let numeric = Numeric {
sign: b::Sign::Positive,
sign: Sign::Positive,
hours: b::OffsetHours::MAX,

@@ -1035,3 +1038,3 @@ minutes: Some(b::OffsetMinutes::MAX),

let numeric = Numeric {
sign: b::Sign::Positive,
sign: Sign::Positive,
hours: b::OffsetHours::MAX,

@@ -1052,3 +1055,3 @@ minutes: Some(b::OffsetMinutes::MAX),

let numeric = Numeric {
sign: b::Sign::Negative,
sign: Sign::Negative,
hours: b::OffsetHours::MAX,

@@ -1062,3 +1065,3 @@ minutes: Some(b::OffsetMinutes::MAX),

let numeric = Numeric {
sign: b::Sign::Negative,
sign: Sign::Negative,
hours: b::OffsetHours::MAX,

@@ -1065,0 +1068,0 @@ minutes: Some(b::OffsetMinutes::MAX),

@@ -0,8 +1,7 @@

use jcore::{bounds::Sign, constants as c};
use crate::{
error::{fmt::util::Error as E, ErrorContext},
fmt::Parsed,
util::{
b::{self, Sign},
parse,
},
util::{b, parse},
Error, SignedDuration, Span, Unit,

@@ -403,4 +402,3 @@ };

let total_nanos = (millis * 1_000_000) + (micros * 1_000) + nanos;
let mut sdur =
SignedDuration::new_without_nano_overflow(total_secs, total_nanos);
let mut sdur = SignedDuration::new_unchecked(total_secs, total_nanos);
if self.get_sign().is_negative() {

@@ -956,7 +954,7 @@ sdur = -sdur;

let nanos = match unit {
Unit::Hour => fraction * b::SECS_PER_HOUR,
Unit::Minute => fraction * b::SECS_PER_MIN,
Unit::Hour => fraction * c::SECS_PER_HOUR,
Unit::Minute => fraction * c::SECS_PER_MIN,
Unit::Second => fraction,
Unit::Millisecond => fraction / b::NANOS_PER_MICRO,
Unit::Microsecond => fraction / b::NANOS_PER_MILLI,
Unit::Millisecond => fraction / c::NANOS_PER_MICRO,
Unit::Microsecond => fraction / c::NANOS_PER_MILLI,
unit => {

@@ -989,3 +987,3 @@ return Err(Error::from(E::NotAllowedFractionalUnit {

let seconds = value
.checked_mul(b::SECS_PER_HOUR)
.checked_mul(c::SECS_PER_HOUR)
.ok_or(E::ConversionToSecondsFailed { unit: Unit::Hour })?;

@@ -996,3 +994,3 @@ SignedDuration::from_secs(seconds)

let seconds = value
.checked_mul(b::SECS_PER_MIN)
.checked_mul(c::SECS_PER_MIN)
.ok_or(E::ConversionToSecondsFailed { unit: Unit::Minute })?;

@@ -999,0 +997,0 @@ SignedDuration::from_secs(seconds)

@@ -758,2 +758,8 @@ /*!

/// This is NOT part of Jiff's public API.
///
/// This is exported for use by `jiff-static`'s procedural macro.
#[doc(hidden)]
pub use jcore as __jcore;
pub use crate::{

@@ -786,4 +792,2 @@ error::Error,

mod now;
#[doc(hidden)]
pub mod shared;
mod signed_duration;

@@ -836,3 +840,3 @@ mod span;

dbg!((b::SpanNanoseconds::MIN, b::SpanNanoseconds::MAX));
dbg!((b::UnixSeconds::MIN, b::UnixSeconds::MAX));
dbg!((b::UnixEpochSeconds::MIN, b::UnixEpochSeconds::MAX));
dbg!((b::UnixEpochDays::MIN, b::UnixEpochDays::MAX));

@@ -839,0 +843,0 @@ }

@@ -0,5 +1,6 @@

use jcore::tz::AmbiguousOffset as JAmbiguousOffset;
use crate::{
civil::DateTime,
error::{tz::ambiguous::Error as E, Error, ErrorContext},
shared::util::itime::IAmbiguousOffset,
tz::{Offset, TimeZone},

@@ -209,18 +210,18 @@ Timestamp, Zoned,

#[inline]
pub(crate) const fn from_iambiguous_offset_const(
iaoff: IAmbiguousOffset,
pub(crate) const fn from_jcore(
offset: JAmbiguousOffset,
) -> AmbiguousOffset {
match iaoff {
IAmbiguousOffset::Unambiguous { offset } => {
let offset = Offset::from_ioffset_const(offset);
match offset {
JAmbiguousOffset::Unambiguous { offset } => {
let offset = Offset::from_jcore(offset);
AmbiguousOffset::Unambiguous { offset }
}
IAmbiguousOffset::Gap { before, after } => {
let before = Offset::from_ioffset_const(before);
let after = Offset::from_ioffset_const(after);
JAmbiguousOffset::Gap { before, after } => {
let before = Offset::from_jcore(before);
let after = Offset::from_jcore(after);
AmbiguousOffset::Gap { before, after }
}
IAmbiguousOffset::Fold { before, after } => {
let before = Offset::from_ioffset_const(before);
let after = Offset::from_ioffset_const(after);
JAmbiguousOffset::Fold { before, after } => {
let before = Offset::from_jcore(before);
let after = Offset::from_jcore(after);
AmbiguousOffset::Fold { before, after }

@@ -227,0 +228,0 @@ }

@@ -6,2 +6,4 @@ use alloc::{

use jcore::util::ArrayStr;
use crate::{

@@ -13,3 +15,3 @@ error::{

tz::TimeZone,
util::{array_str::ArrayStr, escape, utf8},
util::{escape, utf8},
};

@@ -16,0 +18,0 @@

@@ -7,3 +7,3 @@ use crate::tz::{TimeZone, TimeZoneNameIter};

impl Database {
pub(crate) fn new() -> Database {
pub(crate) const fn new() -> Database {
Database

@@ -10,0 +10,0 @@ }

use crate::tz::{db::special_time_zone, TimeZone, TimeZoneNameIter};
#[derive(Clone)]
pub(crate) struct Database;
impl Database {
pub(crate) fn new() -> Database {
pub(crate) const fn new() -> Database {
Database

@@ -8,0 +9,0 @@ }

@@ -15,2 +15,4 @@ use alloc::{

use jcore::util::ArrayStr;
use crate::{

@@ -23,3 +25,3 @@ error::{tz::db::Error as E, Error},

},
util::{self, array_str::ArrayStr, cache::Expiration, utf8},
util::{self, cache::Expiration, utf8},
};

@@ -26,0 +28,0 @@

@@ -35,4 +35,29 @@ use crate::{

{
static NONE: TimeZoneDatabase = TimeZoneDatabase::none();
&NONE
// Without `std` there's no lazily initialized global state. But when
// the tzdb is bundled into the binary, we can still hand out a usable
// database: the bundled database needs no allocation, so it can be
// constructed in a `const`. (Without `std`, the zoneinfo and
// concatenated databases are both unavailable, so there's nothing to
// prefer over the bundle.)
//
// Ref: https://github.com/BurntSushi/jiff/issues/533
#[cfg(any(
feature = "tzdb-bundle-always",
all(
feature = "tzdb-bundle-platform",
any(windows, target_family = "wasm"),
),
))]
static DB: TimeZoneDatabase = TimeZoneDatabase {
inner: Repr::Bundled(bundled::Database::new()),
};
#[cfg(not(any(
feature = "tzdb-bundle-always",
all(
feature = "tzdb-bundle-platform",
any(windows, target_family = "wasm"),
),
)))]
static DB: TimeZoneDatabase = TimeZoneDatabase::none();
&DB
}

@@ -192,5 +217,24 @@ // #[cfg(all(feature = "std", not(miri)))]

pub struct TimeZoneDatabase {
inner: Option<Arc<Kind>>,
inner: Repr,
}
/// The internal representation of a `TimeZoneDatabase`.
///
/// The bundled database is kept out of the `Arc` because it carries no data
/// of its own (the tzdb is compiled into the binary and any parsed zones are
/// cached in a global). This lets it be constructed in a `const`, which is
/// what makes it possible for `db()` to return the bundled database even when
/// `std` is unavailable (and so there is no lazily initialized global state).
///
/// Ref: https://github.com/BurntSushi/jiff/issues/533
#[derive(Clone)]
enum Repr {
/// A database for which all lookups fail.
Empty,
/// The bundled database. Needs no allocation, so no `Arc`.
Bundled(bundled::Database),
/// A database backed by an `Arc` so that clones share its cache.
Arc(Arc<Kind>),
}
#[derive(Debug)]

@@ -202,3 +246,2 @@ // Needed for core-only "dumb" `Arc`.

Concatenated(concatenated::Database),
Bundled(bundled::Database),
}

@@ -218,3 +261,3 @@

pub const fn none() -> TimeZoneDatabase {
TimeZoneDatabase { inner: None }
TimeZoneDatabase { inner: Repr::Empty }
}

@@ -290,3 +333,3 @@

if !db.is_definitively_empty() {
return TimeZoneDatabase::new(Kind::Bundled(db));
return TimeZoneDatabase { inner: Repr::Bundled(db) };
}

@@ -439,3 +482,3 @@

}
TimeZoneDatabase::new(Kind::Bundled(db))
TimeZoneDatabase { inner: Repr::Bundled(db) }
}

@@ -445,3 +488,3 @@

fn new(kind: Kind) -> TimeZoneDatabase {
TimeZoneDatabase { inner: Some(Arc::new(kind)) }
TimeZoneDatabase { inner: Repr::Arc(Arc::new(kind)) }
}

@@ -472,25 +515,17 @@

pub fn get(&self, name: &str) -> Result<TimeZone, Error> {
let inner = self
.inner
.as_deref()
.ok_or_else(|| E::failed_time_zone_no_database_configured(name))?;
match *inner {
Kind::ZoneInfo(ref db) => {
if let Some(tz) = db.get(name) {
trace!("found time zone `{name}` in {db:?}", db = self);
return Ok(tz);
}
let found = match self.inner {
Repr::Empty => {
return Err(Error::from(
E::failed_time_zone_no_database_configured(name),
));
}
Kind::Concatenated(ref db) => {
if let Some(tz) = db.get(name) {
trace!("found time zone `{name}` in {db:?}", db = self);
return Ok(tz);
}
}
Kind::Bundled(ref db) => {
if let Some(tz) = db.get(name) {
trace!("found time zone `{name}` in {db:?}", db = self);
return Ok(tz);
}
}
Repr::Bundled(ref db) => db.get(name),
Repr::Arc(ref kind) => match **kind {
Kind::ZoneInfo(ref db) => db.get(name),
Kind::Concatenated(ref db) => db.get(name),
},
};
if let Some(tz) = found {
trace!("found time zone `{name}` in {db:?}", db = self);
return Ok(tz);
}

@@ -519,9 +554,9 @@ Err(Error::from(E::failed_time_zone(name)))

pub fn available<'d>(&'d self) -> TimeZoneNameIter<'d> {
let Some(inner) = self.inner.as_deref() else {
return TimeZoneNameIter::empty();
};
match *inner {
Kind::ZoneInfo(ref db) => db.available(),
Kind::Concatenated(ref db) => db.available(),
Kind::Bundled(ref db) => db.available(),
match self.inner {
Repr::Empty => TimeZoneNameIter::empty(),
Repr::Bundled(ref db) => db.available(),
Repr::Arc(ref kind) => match **kind {
Kind::ZoneInfo(ref db) => db.available(),
Kind::Concatenated(ref db) => db.available(),
},
}

@@ -540,7 +575,9 @@ }

pub fn reset(&self) {
let Some(inner) = self.inner.as_deref() else { return };
match *inner {
Kind::ZoneInfo(ref db) => db.reset(),
Kind::Concatenated(ref db) => db.reset(),
Kind::Bundled(ref db) => db.reset(),
match self.inner {
Repr::Empty => {}
Repr::Bundled(ref db) => db.reset(),
Repr::Arc(ref kind) => match **kind {
Kind::ZoneInfo(ref db) => db.reset(),
Kind::Concatenated(ref db) => db.reset(),
},
}

@@ -567,7 +604,9 @@ }

pub fn is_definitively_empty(&self) -> bool {
let Some(inner) = self.inner.as_deref() else { return true };
match *inner {
Kind::ZoneInfo(ref db) => db.is_definitively_empty(),
Kind::Concatenated(ref db) => db.is_definitively_empty(),
Kind::Bundled(ref db) => db.is_definitively_empty(),
match self.inner {
Repr::Empty => true,
Repr::Bundled(ref db) => db.is_definitively_empty(),
Repr::Arc(ref kind) => match **kind {
Kind::ZoneInfo(ref db) => db.is_definitively_empty(),
Kind::Concatenated(ref db) => db.is_definitively_empty(),
},
}

@@ -580,9 +619,9 @@ }

f.write_str("TimeZoneDatabase(")?;
let Some(inner) = self.inner.as_deref() else {
return f.write_str("unavailable)");
};
match *inner {
Kind::ZoneInfo(ref db) => core::fmt::Debug::fmt(db, f)?,
Kind::Concatenated(ref db) => core::fmt::Debug::fmt(db, f)?,
Kind::Bundled(ref db) => core::fmt::Debug::fmt(db, f)?,
match self.inner {
Repr::Empty => return f.write_str("unavailable)"),
Repr::Bundled(ref db) => core::fmt::Debug::fmt(db, f)?,
Repr::Arc(ref kind) => match **kind {
Kind::ZoneInfo(ref db) => core::fmt::Debug::fmt(db, f)?,
Kind::Concatenated(ref db) => core::fmt::Debug::fmt(db, f)?,
},
}

@@ -729,5 +768,35 @@ f.write_str(")")

/// This tests that the size of a time zone database is kept at a single
/// word.
/// Tests that the global database returns the bundled tzdb when the bundle
/// is enabled but neither the zoneinfo nor concatenated databases are.
///
/// This configuration doesn't have `std` (since both `tzdb-zoneinfo` and
/// `tzdb-concatenated` imply it), and so without special handling `db()`
/// used to return an empty database despite the tzdb being compiled in.
///
/// Regression test for: https://github.com/BurntSushi/jiff/issues/533
#[cfg(all(
feature = "tzdb-bundle-always",
not(feature = "tzdb-zoneinfo"),
not(feature = "tzdb-concatenated"),
))]
#[test]
fn bundled_db_when_only_bundle_enabled() {
let db = db();
assert!(!db.is_definitively_empty());
assert!(db.get("America/New_York").is_ok());
// The convenience APIs route through the global `db()`, so this is
// the symptom originally reported in #533.
assert!(crate::civil::date(2024, 7, 4)
.at(12, 0, 0, 0)
.in_tz("America/New_York")
.is_ok());
}
/// This tests that the size of a time zone database is kept small.
///
/// It's two words because the bundled database is stored outside the
/// `Arc` (so it can be constructed in a `const`, see `db()` and #533),
/// which means the representation needs a tag to distinguish it from the
/// empty and `Arc`-backed variants.
///
/// I think it would probably be okay to make this bigger if we had a

@@ -741,3 +810,3 @@ /// good reason to, but it seems sensible to put a road-block to avoid

let word = core::mem::size_of::<usize>();
assert_eq!(word, core::mem::size_of::<TimeZoneDatabase>());
assert_eq!(2 * word, core::mem::size_of::<TimeZoneDatabase>());
}

@@ -744,0 +813,0 @@ // A `TimeZoneDatabase` in core-only is vapid.

@@ -18,6 +18,3 @@ use alloc::{string::String, vec, vec::Vec};

timestamp::Timestamp,
tz::{
db::special_time_zone, tzif::is_possibly_tzif, TimeZone,
TimeZoneNameIter,
},
tz::{db::special_time_zone, TimeZone, TimeZoneNameIter},
util::{self, cache::Expiration, parse, utf8},

@@ -643,3 +640,3 @@ };

}
if !is_possibly_tzif(&buf) {
if !jcore::tz::tzif::is_possibly_tzif(&buf) {
// This is a trace because it's perfectly normal for a

@@ -646,0 +643,0 @@ // non-TZif file to be in a zoneinfo directory. But it could

@@ -149,3 +149,3 @@ /*!

mod timezone;
pub(crate) mod tzif;
mod tzif;
// See module comment for WIP status. :-(

@@ -152,0 +152,0 @@ #[cfg(all(test, feature = "alloc"))]

/*!
Provides a parser for [POSIX's `TZ` environment variable][posix-env].
Provides a `Display` impl wrapper for POSIX time zones.
NOTE: Sadly, at time of writing, the actual parser is in `src/shared/posix.rs`.
This is so it can be shared (via simple code copying) with proc macros like
the one found in `jiff-tzdb-static`. The parser populates a "lowest common
denominator" data type. In normal use in Jiff, this type is converted into
the types defined below. This module still does provide the various time zone
operations. Only the parsing is written elsewhere.
The `TZ` environment variable is most commonly used to set a time zone. For
example, `TZ=America/New_York`. But it can also be used to tersely define DST
transitions. Moreover, the format is not just used as an environment variable,
but is also included at the end of TZif files (version 2 or greater). The IANA
Time Zone Database project also [documents the `TZ` variable][iana-env] with
a little more commentary.
Note that we (along with pretty much everyone else) don't strictly follow
POSIX here. Namely, `TZ=America/New_York` isn't a POSIX compatible usage,
and I believe it technically should be `TZ=:America/New_York`. Nevertheless,
apparently some group of people (IANA folks?) decided `TZ=America/New_York`
should be fine. From the [IANA `theory.html` documentation][iana-env]:
> It was recognized that allowing the TZ environment variable to take on values
> such as 'America/New_York' might cause "old" programs (that expect TZ to have
> a certain form) to operate incorrectly; consideration was given to using
> some other environment variable (for example, TIMEZONE) to hold the string
> used to generate the TZif file's name. In the end, however, it was decided
> to continue using TZ: it is widely used for time zone purposes; separately
> maintaining both TZ and TIMEZONE seemed a nuisance; and systems where "new"
> forms of TZ might cause problems can simply use legacy TZ values such as
> "EST5EDT" which can be used by "new" programs as well as by "old" programs
> that assume pre-POSIX TZ values.
Indeed, even [musl subscribes to this behavior][musl-env]. So that's what we do
here too.
Note that a POSIX time zone like `EST5` corresponds to the UTC offset `-05:00`,
and `GMT-4` corresponds to the UTC offset `+04:00`. Yes, it's backwards. How
fun.
# IANA v3+ Support
While this module and many of its types are directly associated with POSIX,
this module also plays a supporting role for `TZ` strings in the IANA TZif
binary format for versions 2 and greater. Specifically, for versions 3 and
greater, some minor extensions are supported here via `IanaTz::parse`. But
using `PosixTz::parse` is limited to parsing what is specified by POSIX.
Nevertheless, we generally use `IanaTz::parse` everywhere, even when parsing
the `TZ` environment variable. The reason for this is that it seems to be what
other programs do in practice (for example, GNU date).
# `no-std` and `no-alloc` support
A big part of this module works fine in core-only environments. But because
core-only environments provide means of indirection, and embedding a
`PosixTimeZone` into a `TimeZone` without indirection would use up a lot of
space (and thereby make `Zoned` quite chunky), we provide core-only support
principally through a proc macro. Namely, a `PosixTimeZone` can be parsed by
the proc macro and then turned into static data.
POSIX time zone support isn't explicitly provided directly as a public API
for core-only environments, but is implicitly supported via TZif. (Since TZif
data contains POSIX time zone strings.)
[posix-env]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
[iana-env]: https://data.iana.org/time-zones/tzdb-2024a/theory.html#functions
[musl-env]: https://wiki.musl-libc.org/environment-variables
This lives here because none of the formatting machinery is available
in `jiff-core`. And while we could still write out a `Display` impl in
`jiff-core`, it doesn't seem worth that duplication.
*/
use core::fmt::Debug;
use jcore::tz::posix;
use crate::{
civil::DateTime,
error::{tz::posix::Error as E, Error, ErrorContext},
fmt, shared,
timestamp::Timestamp,
tz::{
timezone::TimeZoneAbbreviation, AmbiguousOffset, Dst, Offset,
TimeZoneOffsetInfo, TimeZoneTransition,
},
util::{array_str::Abbreviation, parse},
};
use crate::fmt;
/// The result of parsing the POSIX `TZ` environment variable.
/// A wrapper around jcore's posix time zone implementation.
///
/// A `TZ` variable can either be a time zone string with an optional DST
/// transition rule, or it can begin with a `:` followed by an arbitrary set of
/// bytes that is implementation defined.
///
/// In practice, the content following a `:` is treated as an IANA time zone
/// name. Moreover, even if the `TZ` string doesn't start with a `:` but
/// corresponds to a IANA time zone name, then it is interpreted as such.
/// (See the module docs.) However, this type only encapsulates the choices
/// strictly provided by POSIX: either a time zone string with an optional DST
/// transition rule, or an implementation defined string with a `:` prefix. If,
/// for example, `TZ="America/New_York"`, then that case isn't encapsulated by
/// this type. Callers needing that functionality will need to handle the error
/// returned by parsing this type and layer their own semantics on top.
#[cfg(feature = "tz-system")]
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum PosixTzEnv {
/// A valid POSIX time zone with an optional DST transition rule.
Rule(PosixTimeZoneOwned),
/// An implementation defined string. This occurs when the `TZ` value
/// starts with a `:`. The string returned here does not include the `:`.
Implementation(alloc::boxed::Box<str>),
}
/// This wrapper provides `Display`, `Debug` and `Format` impls using the
/// formatter in this crate. (jcore does not do formatting.)
pub(crate) struct TimeZoneFormatter<'a>(pub(crate) &'a posix::TimeZone);
#[cfg(feature = "tz-system")]
impl PosixTzEnv {
/// Parse a POSIX `TZ` environment variable string from the given bytes.
fn parse(bytes: impl AsRef<[u8]>) -> Result<PosixTzEnv, Error> {
let bytes = bytes.as_ref();
if bytes.get(0) == Some(&b':') {
let Ok(string) = core::str::from_utf8(&bytes[1..]) else {
return Err(Error::from(E::ColonPrefixInvalidUtf8));
};
Ok(PosixTzEnv::Implementation(string.into()))
} else {
PosixTimeZone::parse(bytes).map(PosixTzEnv::Rule)
}
}
/// Parse a POSIX `TZ` environment variable string from the given `OsStr`.
pub(crate) fn parse_os_str(
osstr: impl AsRef<std::ffi::OsStr>,
) -> Result<PosixTzEnv, Error> {
PosixTzEnv::parse(parse::os_str_bytes(osstr.as_ref())?)
}
}
#[cfg(feature = "tz-system")]
impl core::fmt::Display for PosixTzEnv {
impl<'a> core::fmt::Display for TimeZoneFormatter<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
PosixTzEnv::Rule(ref tz) => core::fmt::Display::fmt(tz, f),
PosixTzEnv::Implementation(ref imp) => {
f.write_str(":")?;
core::fmt::Display::fmt(imp, f)
}
}
fmt::temporal::DateTimePrinter::new()
.print_posix_time_zone(self.0, fmt::StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
/// An owned POSIX time zone.
///
/// That is, a POSIX time zone whose abbreviations are inlined into the
/// representation. As opposed to a static POSIX time zone whose abbreviations
/// are `&'static str`.
pub(crate) type PosixTimeZoneOwned = PosixTimeZone<Abbreviation>;
/// An owned POSIX time zone whose abbreviations are `&'static str`.
pub(crate) type PosixTimeZoneStatic = PosixTimeZone<&'static str>;
/// A POSIX time zone.
///
/// # On "reasonable" POSIX time zones
///
/// Jiff only supports "reasonable" POSIX time zones. A "reasonable" POSIX time
/// zone is a POSIX time zone that has a DST transition rule _when_ it has a
/// DST time zone abbreviation. Without the transition rule, it isn't possible
/// to know when DST starts and stops.
///
/// POSIX technically allows a DST time zone abbreviation *without* a
/// transition rule, but the behavior is literally unspecified. So Jiff just
/// rejects them.
///
/// Note that if you're confused as to why Jiff accepts `TZ=EST5EDT` (where
/// `EST5EDT` is an example of an _unreasonable_ POSIX time zone), that's
/// because Jiff rejects `EST5EDT` and instead attempts to use it as an IANA
/// time zone identifier. And indeed, the IANA Time Zone Database contains an
/// entry for `EST5EDT` (presumably for legacy reasons).
///
/// Also, we expect `TZ` strings parsed from IANA v2+ formatted `tzfile`s to
/// also be reasonable or parsing fails. This also seems to be consistent with
/// the [GNU C Library]'s treatment of the `TZ` variable: it only documents
/// support for reasonable POSIX time zone strings.
///
/// Note that a V2 `TZ` string is precisely identical to a POSIX `TZ`
/// environment variable string. A V3 `TZ` string however supports signed DST
/// transition times, and hours in the range `0..=167`. The V2 and V3 here
/// reference how `TZ` strings are defined in the TZif format specified by
/// [RFC 9636]. V2 is the original version of it straight from POSIX, where as
/// V3+ corresponds to an extension added to V3 (and newer versions) of the
/// TZif format. V3 is a superset of V2, so in practice, Jiff just permits
/// V3 everywhere.
///
/// [GNU C Library]: https://www.gnu.org/software/libc/manual/2.25/html_node/TZ-Variable.html
/// [RFC 9636]: https://datatracker.ietf.org/doc/rfc9636/
#[derive(Clone, Debug, Eq, PartialEq)]
// NOT part of Jiff's public API
#[doc(hidden)]
// This ensures the alignment of this type is always *at least* 8 bytes. This
// is required for the pointer tagging inside of `TimeZone` to be sound. At
// time of writing (2024-02-24), this explicit `repr` isn't required on 64-bit
// systems since the type definition is such that it will have an alignment of
// at least 8 bytes anyway. But this *is* required for 32-bit systems, where
// the type definition at present only has an alignment of 4 bytes.
#[repr(align(8))]
pub struct PosixTimeZone<ABBREV> {
inner: shared::PosixTimeZone<ABBREV>,
}
impl PosixTimeZone<Abbreviation> {
/// Parse a IANA tzfile v3+ `TZ` string from the given bytes.
#[cfg(feature = "alloc")]
pub(crate) fn parse(
bytes: impl AsRef<[u8]>,
) -> Result<PosixTimeZoneOwned, Error> {
let bytes = bytes.as_ref();
let inner = shared::PosixTimeZone::parse(bytes.as_ref())
.map_err(Error::posix_tz)
.context(E::InvalidPosixTz)?;
Ok(PosixTimeZone { inner })
}
/// Like `parse`, but parses a POSIX TZ string from a prefix of the
/// given input. And remaining input is returned.
#[cfg(feature = "alloc")]
pub(crate) fn parse_prefix<'b, B: AsRef<[u8]> + ?Sized + 'b>(
bytes: &'b B,
) -> Result<(PosixTimeZoneOwned, &'b [u8]), Error> {
let bytes = bytes.as_ref();
let (inner, remaining) =
shared::PosixTimeZone::parse_prefix(bytes.as_ref())
.map_err(Error::posix_tz)
.context(E::InvalidPosixTz)?;
Ok((PosixTimeZone { inner }, remaining))
}
/// Converts from the shared-but-internal API for use in proc macros.
#[cfg(feature = "alloc")]
pub(crate) fn from_shared_owned(
sh: shared::PosixTimeZone<Abbreviation>,
) -> PosixTimeZoneOwned {
PosixTimeZone { inner: sh }
}
}
impl PosixTimeZone<&'static str> {
/// Converts from the shared-but-internal API for use in proc macros.
///
/// This works in a `const` context by requiring that the time zone
/// abbreviations are `static` strings. This is used when converting
/// code generated by a proc macro to this Jiff internal type.
pub(crate) const fn from_shared_const(
sh: shared::PosixTimeZone<&'static str>,
) -> PosixTimeZoneStatic {
PosixTimeZone { inner: sh }
}
}
impl<ABBREV: AsRef<str>> PosixTimeZone<ABBREV> {
/// Returns the underlying "shared" type. (The type shared between `jiff`
/// and `jiff-static`.)
pub(crate) fn shared(&self) -> &shared::PosixTimeZone<ABBREV> {
&self.inner
}
}
impl<ABBREV: AsRef<str> + Debug> PosixTimeZone<ABBREV> {
/// 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: Timestamp) -> Offset {
Offset::from_ioffset_const(
self.inner.to_offset(timestamp.to_itimestamp_const()),
)
}
/// 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: Timestamp,
) -> TimeZoneOffsetInfo<'_> {
let (ioff, abbrev, is_dst) =
self.inner.to_offset_info(timestamp.to_itimestamp_const());
let offset = Offset::from_ioffset_const(ioff);
let abbreviation = TimeZoneAbbreviation::Borrowed(abbrev);
TimeZoneOffsetInfo { offset, dst: Dst::from(is_dst), abbreviation }
}
/// 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: DateTime) -> AmbiguousOffset {
let iamoff = self.inner.to_ambiguous_kind(dt.to_idatetime_const());
AmbiguousOffset::from_iambiguous_offset_const(iamoff)
}
/// 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<'t>(
&'t self,
timestamp: Timestamp,
) -> Option<TimeZoneTransition<'t>> {
let (its, ioff, abbrev, is_dst) =
self.inner.previous_transition(timestamp.to_itimestamp_const())?;
let timestamp = Timestamp::from_itimestamp_const(its);
let offset = Offset::from_ioffset_const(ioff);
let dst = Dst::from(is_dst);
Some(TimeZoneTransition { timestamp, offset, 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<'t>(
&'t self,
timestamp: Timestamp,
) -> Option<TimeZoneTransition<'t>> {
let (its, ioff, abbrev, is_dst) =
self.inner.next_transition(timestamp.to_itimestamp_const())?;
let timestamp = Timestamp::from_itimestamp_const(its);
let offset = Offset::from_ioffset_const(ioff);
let dst = Dst::from(is_dst);
Some(TimeZoneTransition { timestamp, offset, abbrev, dst })
}
}
impl<ABBREV: AsRef<str>> core::fmt::Display for PosixTimeZone<ABBREV> {
impl<'a> core::fmt::Debug for TimeZoneFormatter<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
fmt::temporal::DateTimePrinter::new()
.print_posix_time_zone(self.shared(), fmt::StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
core::fmt::Display::fmt(self, f)
}

@@ -347,6 +34,6 @@ }

#[cfg(feature = "defmt")]
impl<ABBREV: AsRef<str>> defmt::Format for PosixTimeZone<ABBREV> {
impl<'a> defmt::Format for TimeZoneFormatter<'a> {
fn format(&self, f: defmt::Formatter) {
defmt::unwrap!(fmt::temporal::DateTimePrinter::new()
.print_posix_time_zone(self.shared(), fmt::DefmtWrite(f)))
.print_posix_time_zone(self.0, fmt::DefmtWrite(f)))
}

@@ -361,19 +48,56 @@ }

#[cfg(feature = "tz-system")]
/// 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.
#[cfg(feature = "alloc")]
#[test]
fn parse_posix_tz() {
// We used to parse this and then error when we tried to
// convert to a "reasonable" POSIX time zone with a DST
// transition rule. We never actually used unreasonable POSIX
// time zones and it was complicating the type definitions, so
// now we just reject it outright.
assert!(PosixTzEnv::parse("EST5EDT").is_err());
fn roundtrips() {
fn assert_roundtrip(tz: &str) {
use alloc::string::{String, ToString};
let tz = PosixTzEnv::parse(":EST5EDT").unwrap();
assert_eq!(tz, PosixTzEnv::Implementation("EST5EDT".into()));
use jcore::tz::posix;
// We require implementation strings to be UTF-8, because we're
// sensible.
assert!(PosixTzEnv::parse(b":EST5\xFFEDT").is_err());
fn to_string(tz: &posix::TimeZone) -> String {
TimeZoneFormatter(tz).to_string()
}
let tz = posix::TimeZone::parse(tz).unwrap();
let reparsed = posix::TimeZone::parse(to_string(&tz)).unwrap();
assert_eq!(tz, reparsed);
assert_eq!(to_string(&tz), to_string(&reparsed));
}
let cases = [
"WART4WARST,J1/-3,J365/20",
"WART4WARST,J1/-4,J365/21",
"EST5EDT,M3.2.0,M11.1.0",
"XXX-2<+01>-1,0/0,J365/23",
"EST24EDT,J1,J365",
"EST-24EDT,J1,J365",
"EST5EDT,J1,J365/5:12:34",
"EST+5EDT,M3.2.0/2,M11.1.0/2",
"EST+5EDT,M1.1.1,M12.5.2",
"EST5EDT,0/0,J365/25",
"XXX3EDT4,0/0,J365/23",
"XXX3EDT4,0/0,365",
"XXX3EDT4,J1/-167:59:59,J365/167:59:59",
"EST5EDT,J1,J365/5:12:34",
"EST+5EDT,M3.2.0/2,M11.1.0/2",
"EST+5EDT,J60,J365",
"EST+5EDT,59,365",
"EST+5EDT,M1.1.1,M12.5.2",
"EST5EDT,J1,J365/5:12:34",
"EST5EDT,J1/23:59:59,J365/24:00:00",
"EST5EDT,J1/-1,J365/167:00:00",
];
for tz in cases {
assert_roundtrip(tz);
}
}
}

@@ -5,5 +5,7 @@ use std::{sync::RwLock, time::Duration};

use jcore::tz::posix;
use crate::{
error::{tz::system::Error as E, Error, ErrorContext},
tz::{posix::PosixTzEnv, TimeZone, TimeZoneDatabase},
tz::{TimeZone, TimeZoneDatabase},
util::cache::Expiration,

@@ -196,3 +198,3 @@ };

}
let tz_name_or_path = match PosixTzEnv::parse_os_str(&tzenv) {
let tz_name_or_path = match posix::TzEnv::parse_os_str(&tzenv) {
Err(_err) => {

@@ -205,4 +207,4 @@ debug!(

}
Ok(PosixTzEnv::Implementation(string)) => string.to_string(),
Ok(PosixTzEnv::Rule(tz)) => {
Ok(posix::TzEnv::Implementation(string)) => string.to_string(),
Ok(posix::TzEnv::Rule(tz)) => {
return Ok(Some(TimeZone::from_posix_tz(tz)))

@@ -209,0 +211,0 @@ }

#[cfg(not(miri))]
use crate::tz::tzif::TzifOwned;
use jcore::tz::tzif;

@@ -114,7 +114,4 @@ /// A concatenated list of TZif data with a header and an index block.

#[cfg(not(miri))]
pub(crate) fn parse(self) -> TzifOwned {
use alloc::string::ToString;
let name = Some(self.name.to_string());
TzifOwned::parse(name, self.data).unwrap_or_else(|err| {
pub(crate) fn parse(self) -> tzif::TimeZone {
tzif::TimeZone::parse(self.data).unwrap_or_else(|err| {
panic!("failed to parse TZif test file for {:?}: {err}", self.name)

@@ -126,9 +123,6 @@ })

#[cfg(not(miri))]
pub(crate) fn parse_v1(self) -> TzifOwned {
use alloc::string::ToString;
let name = Some(self.name.to_string());
pub(crate) fn parse_v1(self) -> tzif::TimeZone {
let mut data = self.data.to_vec();
data[4] = 0;
TzifOwned::parse(name, &data).unwrap_or_else(|err| {
tzif::TimeZone::parse(&data).unwrap_or_else(|err| {
panic!(

@@ -135,0 +129,0 @@ "failed to parse V1 TZif test file for {:?}: {err}",

/*!
This module provides support for TZif binary files from the [Time Zone
Database].
Contains some tests for the TZif parser in `jiff-core`.
These binary files are the ones commonly found in Unix distributions in the
`/usr/share/zoneinfo` directory.
[Time Zone Database]: https://www.iana.org/time-zones
These tests are a little awkward to have live in `jiff-core` because of the use
of `insta` and because I didn't want to duplicate the time zone data at the
time of writing. For the former, I didn't want to use `insta` in `jiff-core`
because I've found it to be somewhat painful to use.
*/
use core::{fmt::Debug, ops::Range};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
use crate::{
civil::DateTime,
error::Error,
shared::{self, util::array_str::Abbreviation},
timestamp::Timestamp,
tz::{
posix::PosixTimeZone, timezone::TimeZoneAbbreviation, AmbiguousOffset,
Dst, Offset, TimeZoneOffsetInfo, TimeZoneTransition,
},
};
/// The owned variant of `Tzif`.
#[cfg(feature = "alloc")]
pub(crate) type TzifOwned = Tzif<
String,
Abbreviation,
Vec<shared::TzifLocalTimeType>,
Vec<i64>,
Vec<shared::TzifDateTime>,
Vec<shared::TzifDateTime>,
Vec<shared::TzifTransitionInfo>,
>;
/// The static variant of `Tzif`.
pub(crate) type TzifStatic = Tzif<
&'static str,
&'static str,
&'static [shared::TzifLocalTimeType],
&'static [i64],
&'static [shared::TzifDateTime],
&'static [shared::TzifDateTime],
&'static [shared::TzifTransitionInfo],
>;
/// A time zone based on IANA TZif formatted data.
///
/// TZif is a binary format described by RFC 8536. Its typical structure is to
/// define a single time zone per file in the `/usr/share/zoneinfo` directory
/// on Unix systems. The name of a time zone is its file path with the
/// `/usr/share/zoneinfo/` prefix stripped from it.
///
/// This type doesn't provide any facilities for dealing with files on disk
/// or the `/usr/share/zoneinfo` directory. This type is just for parsing the
/// contents of TZif formatted data in memory, and turning it into a data type
/// that can be used as a time zone.
#[derive(Debug)]
// not part of Jiff's public API
#[doc(hidden)]
// This ensures the alignment of this type is always *at least* 8 bytes. This
// is required for the pointer tagging inside of `TimeZone` to be sound. At
// time of writing (2024-02-24), this explicit `repr` isn't required on 64-bit
// systems since the type definition is such that it will have an alignment of
// at least 8 bytes anyway. But this *is* required for 32-bit systems, where
// the type definition at present only has an alignment of 4 bytes.
#[repr(align(8))]
pub struct Tzif<STR, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS> {
inner: shared::Tzif<STR, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS>,
/// The POSIX time zone for this TZif data, if present.
///
/// Note that this is also present on `shared::Tzif`, but uses the
/// `shared::PosixTimeZone` type, which isn't quite what we want here.
///
/// For now we just duplicate it, which is slightly unfortunate. But this
/// is small and not a huge deal. Ideally we can clean this up later.
posix_tz: Option<PosixTimeZone<ABBREV>>,
}
impl TzifStatic {
/// Converts from the shared-but-internal API for use in proc macros.
///
/// This specifically works in a `const` context. And it requires that
/// caller to pass in the parsed `Tzif` in its fixed form along with the
/// variable length local time types and transitions. (Technically, the
/// TZ identifier and the designations are also variable length despite
/// being parsed of `TzifFixed`, but in practice they can be handled just
/// fine via `&'static str`.)
///
/// Notice that the `types` and `transitions` are *not* from the `shared`
/// API, but rather, from the types defined in this module. They have to
/// be this way because there's a conversion step that occurs. In practice,
/// this sort of thing is embedded as a literal in source code via a proc
/// macro. Like this:
///
/// ```text
/// static TZIF: Tzif<&str, &str, &[LocalTimeType], &[Transition]> =
/// Tzif::from_shared_const(
/// shared::TzifFixed {
/// name: Some("America/New_York"),
/// version: b'3',
/// checksum: 0xDEADBEEF,
/// designations: "ESTEDT",
/// posix_tz: None,
/// },
/// &[
/// shared::TzifLocalTimeType {
/// offset: -5 * 60 * 60,
/// is_dst: false,
/// designation: 0..3,
/// indicator: shared::TzifIndicator::LocalWall,
/// }.to_jiff(),
/// ],
/// &[
/// shared::TzifTransition {
/// timestamp: 123456789,
/// type_index: 0,
/// }.to_jiff(-5, -5),
/// ],
/// );
/// ```
///
/// Or something like that anyway. The point is, our `static` slices are
/// variable length and they need to be the right types. At least, I
/// couldn't see a simpler way to arrange this.
pub(crate) const fn from_shared_const(
sh: shared::TzifStatic,
) -> TzifStatic {
let posix_tz = match sh.fixed.posix_tz {
None => None,
Some(posix_tz) => Some(PosixTimeZone::from_shared_const(posix_tz)),
};
Tzif { inner: sh, posix_tz }
}
}
#[cfg(feature = "alloc")]
impl TzifOwned {
/// Parses the given data as a TZif formatted file.
///
/// The name given is attached to the `Tzif` value returned, but is
/// otherwise not significant.
///
/// If the given data is not recognized to be valid TZif, then an error is
/// returned.
///
/// In general, callers may assume that it is safe to pass arbitrary or
/// even untrusted data to this function and count on it not panicking
/// or using resources that aren't limited to a small constant factor of
/// the size of the data itself. That is, callers can reliably limit the
/// resources used by limiting the size of the data given to this parse
/// function.
pub(crate) fn parse(
name: Option<String>,
bytes: &[u8],
) -> Result<Self, Error> {
let sh = shared::TzifOwned::parse(name, bytes).map_err(Error::tzif)?;
Ok(TzifOwned::from_shared_owned(sh))
}
/// Converts from the shared-but-internal API for use in proc macros.
///
/// This is not `const` since it accepts owned values on the heap for
/// variable length data inside `Tzif`.
pub(crate) fn from_shared_owned(sh: shared::TzifOwned) -> TzifOwned {
let posix_tz = match sh.fixed.posix_tz {
None => None,
Some(posix_tz) => Some(PosixTimeZone::from_shared_owned(posix_tz)),
};
Tzif { inner: sh, posix_tz }
}
}
impl<
STR: AsRef<str>,
ABBREV: AsRef<str> + Debug,
TYPES: AsRef<[shared::TzifLocalTimeType]>,
TIMESTAMPS: AsRef<[i64]>,
STARTS: AsRef<[shared::TzifDateTime]>,
ENDS: AsRef<[shared::TzifDateTime]>,
INFOS: AsRef<[shared::TzifTransitionInfo]>,
> Tzif<STR, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS>
{
/// Returns the name given to this TZif data in its constructor.
pub(crate) fn name(&self) -> Option<&str> {
self.inner.fixed.name.as_ref().map(|n| n.as_ref())
}
/// Returns the appropriate time zone offset to use for the given
/// timestamp.
pub(crate) fn to_offset(&self, timestamp: Timestamp) -> Offset {
match self.to_local_time_type(timestamp) {
Ok(typ) => Offset::from_seconds_unchecked(typ.offset),
Err(tz) => tz.to_offset(timestamp),
}
}
/// 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: Timestamp,
) -> TimeZoneOffsetInfo<'_> {
let typ = match self.to_local_time_type(timestamp) {
Ok(typ) => typ,
Err(tz) => return tz.to_offset_info(timestamp),
};
let abbreviation =
TimeZoneAbbreviation::Borrowed(self.designation(typ));
TimeZoneOffsetInfo {
offset: Offset::from_seconds_unchecked(typ.offset),
dst: Dst::from(typ.is_dst),
abbreviation,
}
}
/// Returns the local time type for the timestamp given.
///
/// If one could not be found, then this implies that the caller should
/// use the POSIX time zone returned in the error variant.
fn to_local_time_type(
&self,
timestamp: Timestamp,
) -> Result<&shared::TzifLocalTimeType, &PosixTimeZone<ABBREV>> {
let timestamp = timestamp.as_second();
// This is guaranteed because we always push at least one transition.
// This isn't guaranteed by TZif since it might have 0 transitions,
// but we always add a "dummy" first transition with our minimum
// `Timestamp` value. TZif doesn't do this because there is no
// universal minimum timestamp. (`i64::MIN` is a candidate, but that's
// likely to cause overflow in readers that don't do error checking.)
//
// The result of the dummy transition is that the code below is simpler
// with fewer special cases.
let timestamps = self.timestamps();
assert!(!timestamps.is_empty(), "transitions is non-empty");
let index = if timestamp > *timestamps.last().unwrap() {
timestamps.len() - 1
} else {
let search = self.timestamps().binary_search(&timestamp);
match search {
// Since the first transition is always Timestamp::MIN, it's
// impossible for any timestamp to sort before it.
Err(0) => {
unreachable!("impossible to come before Timestamp::MIN")
}
Ok(i) => i,
// i points to the position immediately after the matching
// timestamp. And since we know that i>0 because of the i==0
// check above, we can safely subtract 1.
Err(i) => i.checked_sub(1).expect("i is non-zero"),
}
};
// Our index is always in bounds. The only way it couldn't be is if
// binary search returns an Err(len) for a time greater than the
// maximum transition. But we account for that above by converting
// Err(len) to Err(len-1).
debug_assert!(index < timestamps.len());
// RFC 8536 says: "Local time for timestamps on or after the last
// transition is specified by the TZ string in the footer (Section 3.3)
// if present and nonempty; otherwise, it is unspecified."
//
// Subtracting 1 is OK because we know self.transitions is not empty.
let index = if index < timestamps.len() - 1 {
// This is the typical case in "fat" TZif files: we found a
// matching transition.
index
} else {
match self.posix_tz() {
// This is the typical case in "slim" TZif files, where the
// last transition is, as I understand it, the transition at
// which a consistent rule started that a POSIX TZ string can
// fully describe. For example, (as of 2024-03-27) the last
// transition in the "fat" America/New_York TZif file is
// in 2037, where as in the "slim" version it is 2007.
//
// This is likely why some things break with the "slim"
// version: they don't support POSIX TZ strings (or don't
// support them correctly).
Some(tz) => return Err(tz),
// This case is technically unspecified, but I think the
// typical thing to do is to just use the last transition.
// I'm not 100% sure on this one.
None => index,
}
};
Ok(self.local_time_type(index))
}
/// 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: DateTime) -> AmbiguousOffset {
// This implementation very nearly mirrors `to_local_time_type`
// above in the beginning: we do a binary search to find transition
// applicable for the given datetime. Except, we do it on wall clock
// times instead of timestamps. And in particular, each transition
// begins with a possibly ambiguous range of wall clock times
// corresponding to either a "gap" or "fold" in time.
let dtt = shared::TzifDateTime::new(
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
);
let (starts, ends) = (self.civil_starts(), self.civil_ends());
assert!(!starts.is_empty(), "transitions is non-empty");
let this_index = match starts.binary_search(&dtt) {
Err(0) => unreachable!("impossible to come before DateTime::MIN"),
Ok(i) => i,
Err(i) => i.checked_sub(1).expect("i is non-zero"),
};
debug_assert!(this_index < starts.len());
let this_offset = self.local_time_type(this_index).offset;
// This is a little tricky, but we need to check for ambiguous civil
// datetimes before possibly using the POSIX TZ string. Namely, a
// datetime could be ambiguous with respect to the last transition,
// and we should handle that according to the gap/fold determined for
// that transition. We cover this case in tests in tz/mod.rs for the
// Pacific/Honolulu time zone, whose last transition begins with a gap.
match self.transition_kind(this_index) {
shared::TzifTransitionKind::Gap if dtt < ends[this_index] => {
// A gap/fold can only appear when there exists a previous
// transition.
let prev_index = this_index.checked_sub(1).unwrap();
let prev_offset = self.local_time_type(prev_index).offset;
return AmbiguousOffset::Gap {
before: Offset::from_seconds_unchecked(prev_offset),
after: Offset::from_seconds_unchecked(this_offset),
};
}
shared::TzifTransitionKind::Fold if dtt < ends[this_index] => {
// A gap/fold can only appear when there exists a previous
// transition.
let prev_index = this_index.checked_sub(1).unwrap();
let prev_offset = self.local_time_type(prev_index).offset;
return AmbiguousOffset::Fold {
before: Offset::from_seconds_unchecked(prev_offset),
after: Offset::from_seconds_unchecked(this_offset),
};
}
_ => {}
}
// The datetime given is not ambiguous with respect to any of the
// transitions in the TZif data. But, if we matched at or after the
// last transition, then we need to use the POSIX TZ string (which
// could still return an ambiguous offset).
if this_index == starts.len() - 1 {
if let Some(tz) = self.posix_tz() {
return tz.to_ambiguous_kind(dt);
}
// This case is unspecified according to RFC 8536. It means that
// the given datetime exceeds all transitions *and* there is no
// POSIX TZ string. So this can happen in V1 files for example.
// But those should hopefully be essentially non-existent nowadays
// (2024-03). In any case, we just fall through to using the last
// transition, which does seem likely to be wrong ~half the time
// in time zones with DST. But there really isn't much else we can
// do I think.
}
AmbiguousOffset::Unambiguous {
offset: Offset::from_seconds_unchecked(this_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<'t>(
&'t self,
ts: Timestamp,
) -> Option<TimeZoneTransition<'t>> {
assert!(!self.timestamps().is_empty(), "transitions is non-empty");
let mut timestamp = ts.as_second();
if ts.subsec_nanosecond() != 0 {
timestamp = timestamp.saturating_add(1);
}
let search = self.timestamps().binary_search(&timestamp);
let index = match search {
Ok(i) | Err(i) => i.checked_sub(1)?,
};
let index = if index == 0 {
// The first transition is a dummy that we insert, so if we land on
// it here, treat it as if it doesn't exist.
return None;
} else if index == self.timestamps().len() - 1 {
if let Some(ref posix_tz) = self.posix_tz() {
// Since the POSIX TZ must be consistent with the last
// transition, it must be the case that tzif_last <=
// posix_prev_trans in all cases. So the transition according
// to the POSIX TZ is always correct here.
//
// What if this returns `None` though? I'm not sure in which
// cases that could matter, and I think it might be a violation
// of the TZif format if it does.
//
// It can return `None`! In the case of a time zone that
// has eliminated DST, it might have historical time zone
// transitions but a POSIX time zone without DST. (For example,
// `America/Sao_Paulo`.) And thus, this would return `None`.
// So if it does, we pretend as if the POSIX time zone doesn't
// exist.
if let Some(trans) = posix_tz.previous_transition(ts) {
return Some(trans);
}
}
index
} else {
index
};
let timestamp = self.timestamps()[index];
let typ = self.local_time_type(index);
Some(TimeZoneTransition {
timestamp: Timestamp::constant(timestamp, 0),
offset: Offset::from_seconds_unchecked(typ.offset),
abbrev: self.designation(typ),
dst: Dst::from(typ.is_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<'t>(
&'t self,
ts: Timestamp,
) -> Option<TimeZoneTransition<'t>> {
assert!(!self.timestamps().is_empty(), "transitions is non-empty");
let timestamp = ts.as_second();
let search = self.timestamps().binary_search(&timestamp);
let index = match search {
Ok(i) => i.checked_add(1)?,
Err(i) => i,
};
let index = if index == 0 {
// The first transition is a dummy that we insert, so if we land on
// it here, treat it as if it doesn't exist.
return None;
} else if index >= self.timestamps().len() {
if let Some(posix_tz) = self.posix_tz() {
// Since the POSIX TZ must be consistent with the last
// transition, it must be the case that next.timestamp <=
// posix_next_tans in all cases. So the transition according to
// the POSIX TZ is always correct here.
//
// What if this returns `None` though? I'm not sure in which
// cases that could matter, and I think it might be a violation
// of the TZif format if it does.
//
// In the "previous" case above, this could return `None` even
// when there are historical time zone transitions in the case
// of a time zone eliminating DST (e.g., `America/Sao_Paulo`).
// But unlike the previous case, if we get `None` here, then
// that is the real answer because there are no other known
// future time zone transitions.
//
// 2025-05-05: OK, this could return `None` and this is fine.
// It happens for time zones that had DST but then stopped
// it at some point in the past. The POSIX time zone has no
// DST and thus returns `None`. That's fine. But there was a
// problem: we were using the POSIX time zone even when there
// was a historical time zone transition after the timestamp
// given. That was fixed by changing the condition when we get
// here: it can only happen when the timestamp given comes at
// or after all historical time zone transitions.
return posix_tz.next_transition(ts);
}
self.timestamps().len() - 1
} else {
index
};
let timestamp = self.timestamps()[index];
let typ = self.local_time_type(index);
Some(TimeZoneTransition {
timestamp: Timestamp::constant(timestamp, 0),
offset: Offset::from_seconds_unchecked(typ.offset),
abbrev: self.designation(typ),
dst: Dst::from(typ.is_dst),
})
}
fn designation(&self, typ: &shared::TzifLocalTimeType) -> &str {
// OK because we verify that the designation range on every local
// time type is a valid range into `self.designations`.
&self.designations()[typ.designation()]
}
fn local_time_type(
&self,
transition_index: usize,
) -> &shared::TzifLocalTimeType {
// OK because we require that `type_index` always points to a valid
// local time type.
&self.types()[usize::from(self.infos()[transition_index].type_index)]
}
fn transition_kind(
&self,
transition_index: usize,
) -> shared::TzifTransitionKind {
self.infos()[transition_index].kind
}
fn posix_tz(&self) -> Option<&PosixTimeZone<ABBREV>> {
self.posix_tz.as_ref()
}
fn designations(&self) -> &str {
self.inner.fixed.designations.as_ref()
}
fn types(&self) -> &[shared::TzifLocalTimeType] {
self.inner.types.as_ref()
}
fn timestamps(&self) -> &[i64] {
self.inner.transitions.timestamps.as_ref()
}
fn civil_starts(&self) -> &[shared::TzifDateTime] {
self.inner.transitions.civil_starts.as_ref()
}
fn civil_ends(&self) -> &[shared::TzifDateTime] {
self.inner.transitions.civil_ends.as_ref()
}
fn infos(&self) -> &[shared::TzifTransitionInfo] {
self.inner.transitions.infos.as_ref()
}
}
impl<STR: AsRef<str>, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS> Eq
for Tzif<STR, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS>
{
}
impl<STR: AsRef<str>, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS> PartialEq
for Tzif<STR, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS>
{
fn eq(&self, rhs: &Self) -> bool {
self.inner.fixed.name.as_ref().map(|n| n.as_ref())
== rhs.inner.fixed.name.as_ref().map(|n| n.as_ref())
&& self.inner.fixed.checksum == rhs.inner.fixed.checksum
}
}
impl shared::TzifLocalTimeType {
fn designation(&self) -> Range<usize> {
usize::from(self.designation.0)..usize::from(self.designation.1)
}
}
impl core::fmt::Display for shared::TzifIndicator {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
shared::TzifIndicator::LocalWall => f.write_str("local/wall"),
shared::TzifIndicator::LocalStandard => f.write_str("local/std"),
shared::TzifIndicator::UTStandard => f.write_str("ut/std"),
}
}
}
/// Does a quick check that returns true if the data might be in TZif format.
///
/// It is possible that this returns true even if the given data is not in TZif
/// format. However, it is impossible for this to return false when the given
/// data is TZif. That is, a false positive is allowed but a false negative is
/// not.
#[cfg(feature = "tzdb-zoneinfo")]
pub(crate) fn is_possibly_tzif(data: &[u8]) -> bool {
data.starts_with(b"TZif")
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use alloc::{string::ToString, vec};
use alloc::{
string::{String, ToString},
vec,
};
use jcore::tz::tzif;
#[cfg(not(miri))]
use crate::tz::testdata::TZIF_TEST_FILES;
use super::*;
/// This converts TZif data into a human readable format.

@@ -609,7 +31,19 @@ ///

/// represented in some way in this output.
fn tzif_to_human_readable(tzif: &TzifOwned) -> String {
fn tzif_to_human_readable(tzif: &tzif::TimeZone) -> String {
use std::io::Write;
fn datetime(dt: shared::TzifDateTime) -> DateTime {
DateTime::constant(
struct IndicatorDisplay(tzif::Indicator);
impl core::fmt::Display for IndicatorDisplay {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self.0 {
tzif::Indicator::LocalWall => write!(f, "local/wall"),
tzif::Indicator::LocalStandard => write!(f, "local/std"),
tzif::Indicator::UTStandard => write!(f, "ut/std"),
}
}
}
fn datetime(dt: tzif::DateTime) -> jiff::civil::DateTime {
jiff::civil::DateTime::constant(
dt.year(),

@@ -628,15 +62,7 @@ dt.month(),

writeln!(out, "TIME ZONE NAME").unwrap();
writeln!(out, " {}", tzif.name().unwrap_or("UNNAMED")).unwrap();
writeln!(out, "TIME ZONE VERSION").unwrap();
writeln!(
out,
" {}",
char::try_from(tzif.inner.fixed.version).unwrap()
)
.unwrap();
writeln!(out, " {}", char::try_from(tzif.version).unwrap()).unwrap();
writeln!(out, "LOCAL TIME TYPES").unwrap();
for (i, typ) in tzif.inner.types.iter().enumerate() {
for (i, typ) in tzif.types.iter().enumerate() {
writeln!(

@@ -646,27 +72,30 @@ out,

designation={desig}\t{dst}\tindicator={ind}",
off = Offset::from_seconds_unchecked(typ.offset),
desig = tzif.designation(&typ),
dst = if typ.is_dst { "dst" } else { "" },
ind = typ.indicator,
off = jiff::tz::Offset::from_seconds(typ.offset.seconds())
.unwrap(),
desig = tzif.designations[usize::from(typ.designation)],
dst = if typ.dst.is_dst() { "dst" } else { "" },
ind = IndicatorDisplay(typ.indicator),
)
.unwrap();
}
if !tzif.timestamps().is_empty() {
if !tzif.transitions.timestamps.is_empty() {
writeln!(out, "TRANSITIONS").unwrap();
for i in 0..tzif.timestamps().len() {
let timestamp = Timestamp::constant(tzif.timestamps()[i], 0);
let dt = Offset::UTC.to_datetime(timestamp);
let typ = tzif.local_time_type(i);
for i in 0..tzif.transitions.timestamps.len() {
let trans = &tzif.transitions;
let timestamp = trans.timestamps[i];
let dt = jcore::tz::Offset::UTC
.to_datetime(timestamp.to_standard_timestamp());
let typ = tzif.types[usize::from(trans.infos[i].type_index)];
let wall =
alloc::format!("{}", datetime(tzif.civil_starts()[i]));
let ambiguous = match tzif.transition_kind(i) {
shared::TzifTransitionKind::Unambiguous => {
alloc::format!("{}", datetime(trans.civil_starts[i]));
let ambiguous = match trans.infos[i].kind {
tzif::TransitionKind::Unambiguous => {
"unambiguous".to_string()
}
shared::TzifTransitionKind::Gap => {
let end = datetime(tzif.civil_ends()[i]);
tzif::TransitionKind::Gap => {
let end = datetime(trans.civil_ends[i]);
alloc::format!(" gap-until({end})")
}
shared::TzifTransitionKind::Fold => {
let end = datetime(tzif.civil_ends()[i]);
tzif::TransitionKind::Fold => {
let end = datetime(trans.civil_ends[i]);
alloc::format!("fold-until({end})")

@@ -683,6 +112,7 @@ }

ts = timestamp.as_second(),
type_index = tzif.infos()[i].type_index,
off = Offset::from_seconds_unchecked(typ.offset),
desig = tzif.designation(typ),
dst = if typ.is_dst { "dst" } else { "" },
type_index = trans.infos[i].type_index,
off = jiff::tz::Offset::from_seconds(typ.offset.seconds())
.unwrap(),
desig = tzif.designations[usize::from(typ.designation)],
dst = if typ.dst.is_dst() { "dst" } else { "" },
)

@@ -694,3 +124,8 @@ .unwrap();

writeln!(out, "POSIX TIME ZONE STRING").unwrap();
writeln!(out, " {}", posix_tz).unwrap();
writeln!(
out,
" {}",
crate::tz::posix::TimeZoneFormatter(posix_tz)
)
.unwrap();
}

@@ -720,3 +155,3 @@ String::from_utf8(out.into_inner().unwrap()).unwrap()

std::fs::read(&val).with_context(|| alloc::format!("{val:?}"))?;
let tzif = Tzif::parse(Some(val.to_string()), &bytes)?;
let tzif = tzif::TimeZone::parse(&bytes)?;
std::eprint!("{}", tzif_to_human_readable(&tzif));

@@ -773,17 +208,8 @@ Ok(())

let Ok(bytes) = std::fs::read(dent.path()) else { continue };
if !is_possibly_tzif(&bytes) {
if !tzif::is_possibly_tzif(&bytes) {
continue;
}
let tzname = dent
.path()
.strip_prefix(TZDIR)
.unwrap_or_else(|_| {
panic!("all paths in TZDIR have {TZDIR:?} prefix")
})
.to_str()
.expect("all paths to be valid UTF-8")
.to_string();
// OK at this point, we're pretty sure `bytes` should be a TZif
// binary file. So try to parse it and fail the test if it fails.
if let Err(err) = Tzif::parse(Some(tzname), &bytes) {
if let Err(err) = tzif::TimeZone::parse(&bytes) {
panic!("failed to parse TZif file {:?}: {err}", dent.path());

@@ -790,0 +216,0 @@ }

@@ -8,55 +8,16 @@ /*!

#![allow(dead_code)]
use jcore::{
bounds::{self, const_check, Bounds, RawBoundsError},
constants as c,
};
use crate::{Error, SignedDuration};
use crate::Error;
pub(crate) const DAYS_PER_WEEK: i64 = 7;
pub(crate) const HOURS_PER_CIVIL_DAY: i64 = 24;
pub(crate) const MINS_PER_CIVIL_DAY: i64 = HOURS_PER_CIVIL_DAY * MINS_PER_HOUR;
pub(crate) const MINS_PER_HOUR: i64 = 60;
pub(crate) const SECS_PER_WEEK: i64 = DAYS_PER_WEEK * SECS_PER_CIVIL_DAY;
pub(crate) const SECS_PER_CIVIL_DAY: i64 = HOURS_PER_CIVIL_DAY * SECS_PER_HOUR;
pub(crate) const SECS_PER_HOUR: i64 = SECS_PER_MIN * MINS_PER_HOUR;
pub(crate) const SECS_PER_MIN: i64 = 60;
pub(crate) const MILLIS_PER_CIVIL_DAY: i64 =
SECS_PER_CIVIL_DAY * MILLIS_PER_SEC;
pub(crate) const MILLIS_PER_SEC: i64 = 1_000;
pub(crate) const MICROS_PER_CIVIL_DAY: i64 =
SECS_PER_CIVIL_DAY * MICROS_PER_SEC;
pub(crate) const MICROS_PER_SEC: i64 = MILLIS_PER_SEC * MICROS_PER_MILLI;
pub(crate) const MICROS_PER_MILLI: i64 = 1_000;
pub(crate) const NANOS_PER_WEEK: i64 = DAYS_PER_WEEK * NANOS_PER_CIVIL_DAY;
pub(crate) const NANOS_PER_CIVIL_DAY: i64 =
HOURS_PER_CIVIL_DAY * NANOS_PER_HOUR;
pub(crate) const NANOS_PER_HOUR: i64 = MINS_PER_HOUR * NANOS_PER_MIN;
pub(crate) const NANOS_PER_MIN: i64 = SECS_PER_MIN * NANOS_PER_SEC;
pub(crate) const NANOS_PER_SEC: i64 = MILLIS_PER_SEC * NANOS_PER_MILLI;
pub(crate) const NANOS_PER_MILLI: i64 = MICROS_PER_MILLI * NANOS_PER_MICRO;
pub(crate) const NANOS_PER_MICRO: i64 = 1_000;
pub(crate) const DAYS_PER_WEEK_32: i32 = 7;
pub(crate) const HOURS_PER_CIVIL_DAY_32: i32 = 24;
pub(crate) const MINS_PER_HOUR_32: i32 = 60;
pub(crate) const SECS_PER_CIVIL_DAY_32: i32 =
HOURS_PER_CIVIL_DAY_32 * SECS_PER_HOUR_32;
pub(crate) const SECS_PER_HOUR_32: i32 = SECS_PER_MIN_32 * MINS_PER_HOUR_32;
pub(crate) const SECS_PER_MIN_32: i32 = 60;
pub(crate) const MILLIS_PER_SEC_32: i32 = 1_000;
pub(crate) const MICROS_PER_SEC_32: i32 =
MILLIS_PER_SEC_32 * MICROS_PER_MILLI_32;
pub(crate) const MICROS_PER_MILLI_32: i32 = 1_000;
pub(crate) const NANOS_PER_SEC_32: i32 =
MILLIS_PER_SEC_32 * NANOS_PER_MILLI_32;
pub(crate) const NANOS_PER_MILLI_32: i32 =
MICROS_PER_MILLI_32 * NANOS_PER_MICRO_32;
pub(crate) const NANOS_PER_MICRO_32: i32 = 1_000;
/// This macro writes out the boiler plate to define a boundary type.
/// Writes the definition of a single boundary type.
///
/// Specifically, it implements the `Bounds` trait and provides a few
/// concrete methods. The concrete methods are mostly wrappers around
/// the generic trait methods. They are provided so that callers don't
/// have to import the `Bounds` trait to use them.
macro_rules! define_bounds {
($((
/// When only the name and type are given, then this copies the `WHAT`,
/// `MIN` and `MAX` definitions from `jcore`. This avoids copying the specific
/// range values and maintains a single point of truth.
macro_rules! define_one_boundary_type {
(
// The name of the boundary type.

@@ -75,77 +36,109 @@ $name:ident,

$max:expr $(,)?
)),* $(,)?) => {
$(
pub(crate) struct $name(());
) => {
pub(crate) struct $name(());
impl Bounds for $name {
const WHAT: &'static str = $what;
const MIN: Self::Primitive = $min;
const MAX: Self::Primitive = $max;
type Primitive = $ty;
impl Bounds for $name {
const WHAT: &'static str = $what;
const MIN: Self::Primitive = $min;
const MAX: Self::Primitive = $max;
type Primitive = $ty;
type Error = BoundsError;
#[cold]
#[inline(never)]
fn error() -> BoundsError {
BoundsError::$name(RawBoundsError::new())
}
#[cold]
#[inline(never)]
fn error() -> BoundsError {
Self::error()
}
}
impl $name {
pub(crate) const MIN: $ty = <$name as Bounds>::MIN;
pub(crate) const MAX: $ty = <$name as Bounds>::MAX;
const LEN: i128 = Self::MAX as i128 - Self::MIN as i128 + 1;
#[allow(dead_code)]
impl $name {
pub(crate) const MIN: $ty = <$name as Bounds>::MIN;
pub(crate) const MAX: $ty = <$name as Bounds>::MAX;
const LEN: i128 = Self::MAX as i128 - Self::MIN as i128 + 1;
#[cold]
pub(crate) const fn error() -> BoundsError {
BoundsError::$name(RawBoundsError::new())
}
#[cold]
pub(crate) const fn error() -> BoundsError {
BoundsError::$name(
RawBoundsWrapperError(RawBoundsError::new()),
)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn check(n: impl Into<i64>) -> Result<$ty, BoundsError> {
<$name as Bounds>::check(n)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn check(
n: impl Into<i64>,
) -> Result<$ty, BoundsError> {
<$name as Bounds>::check(n)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) const fn checkc(n: i64) -> Result<$ty, BoundsError> {
match self::checkc::$ty(n) {
Ok(n) => Ok(n),
Err(err) => Err(BoundsError::$name(err)),
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) const fn checkc(n: i64) -> Result<$ty, BoundsError> {
match const_check::$ty(n) {
Ok(n) => Ok(n),
Err(err) => {
Err(BoundsError::$name(RawBoundsWrapperError(err)))
}
}
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn check128(n: impl Into<i128>) -> Result<$ty, BoundsError> {
<$name as Bounds>::check128(n)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn checked_add(
n1: $ty,
n2: $ty,
) -> Result<$ty, BoundsError> {
<$name as Bounds>::checked_add(n1, n2)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn parse(bytes: &[u8]) -> Result<$ty, Error> {
<$name as Bounds>::parse(bytes)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn checked_mul(
n1: $ty,
n2: $ty,
) -> Result<$ty, BoundsError> {
<$name as Bounds>::checked_mul(n1, n2)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn checked_add(n1: $ty, n2: $ty) -> Result<$ty, BoundsError> {
<$name as Bounds>::checked_add(n1, n2)
}
#[cfg(test)]
pub(crate) fn arbitrary(g: &mut quickcheck::Gen) -> $ty {
use quickcheck::Arbitrary;
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn checked_sub(n1: $ty, n2: $ty) -> Result<$ty, BoundsError> {
<$name as Bounds>::checked_sub(n1, n2)
}
let mut n: $ty = <$ty>::arbitrary(g);
n = n.wrapping_rem_euclid(Self::LEN as $ty);
n += Self::MIN;
n
}
}
};
// A shortcut for defining a boundary type in `jiff` that is just a mirror
// of a boundary type defined in `jiff-core`. We do this instead of using
// the boundary type from `jiff-core` directly because we can't have a
// `From` impl on a `jiff-core` error type for `jiff::Error`. (Because
// that would make `jiff-core` a public dependency of `jiff`. Which means
// any semver incompatible release of `jiff-core` would require a semver
// incompatible release of `jiff`. Since `jiff-core` evolves more quickly
// than `jiff`, this would be bad juju.)
($name:ident, $ty:ident $(,)?) => {
define_one_boundary_type!(
$name,
$ty,
jcore::bounds::$name::WHAT,
jcore::bounds::$name::MIN,
jcore::bounds::$name::MAX,
);
};
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn checked_mul(n1: $ty, n2: $ty) -> Result<$ty, BoundsError> {
<$name as Bounds>::checked_mul(n1, n2)
}
#[cfg(test)]
pub(crate) fn arbitrary(g: &mut quickcheck::Gen) -> $ty {
use quickcheck::Arbitrary;
let mut n: $ty = <$ty>::arbitrary(g);
n = n.wrapping_rem_euclid(Self::LEN as $ty);
n += Self::MIN;
n
}
}
/// This macro writes out the boiler plate to define a boundary type.
///
/// Specifically, it implements the `Bounds` trait and provides a few
/// concrete methods. The concrete methods are mostly wrappers around
/// the generic trait methods. They are provided so that callers don't
/// have to import the `Bounds` trait to use them.
macro_rules! define_bounds {
($((
$name:ident,
$ty:ident
$($tt:tt)*
)),* $(,)?) => {
$(
define_one_boundary_type!($name, $ty $($tt)*);
)*

@@ -157,3 +150,3 @@

pub(crate) enum BoundsError {
$($name(RawBoundsError<$name>),)*
$($name(RawBoundsWrapperError<$name>),)*
}

@@ -164,3 +157,3 @@

match *self {
$(BoundsError::$name(ref err) => err.fmt(f),)*
$(BoundsError::$name(ref err) => err.0.fmt(f),)*
}

@@ -172,24 +165,20 @@ }

// This is where we define all of Jiff's ranges.
//
// Well, that's not quite true. Many of them are actually defined in
// `jiff-core`. For those, we still have an entry here that still produces
// a boundary type, but it reuses the minimum and maximum values from
// `jiff-core`. This is so `jiff` completely owns its own boundary types
// (to avoid a public dep on `jiff-core`) while simultaneously preserving a
// single point of truth for every range type.
define_bounds! {
(Century, i8, "century", 0, 99),
(
CivilDayNanosecond,
i64,
"nanoseconds (in one civil day)",
0,
NANOS_PER_CIVIL_DAY - 1,
),
(
CivilDaySecond,
i32,
"seconds (in one civil day)",
0,
SECS_PER_CIVIL_DAY_32 - 1,
),
(Day, i8, "day", 1, 31),
(DayOfYear, i16, "day-of-year", 1, 366),
(Hour, i8, "hour", 0, 23),
(CivilDayNanosecond, i64),
(CivilDaySecond, i32),
(Day, i8),
(DayOfYear, i16),
(Hour, i8),
(Hour12, i8, "hour (12 hour clock)", 1, 12),
(ISOWeek, i8, "iso-week", 1, 53),
(ISOYear, i16, "iso-year", -9999, 9999),
(ISOWeek, i8),
(ISOYear, i16),
// This matches Temporal's range.

@@ -201,75 +190,20 @@ // See: https://github.com/tc39/proposal-temporal/issues/2458#issuecomment-1380742911

(LeapSecond, i8, "second", 0, 60),
(Microsecond, i16, "microsecond", 0, 999),
(Millisecond, i16, "millisecond", 0, 999),
(Minute, i8, "minute", 0, 59),
(Month, i8, "month", 1, 12),
(Nanosecond, i16, "nanosecond", 0, 999),
(NthWeekday, i32, "nth weekday", SpanWeeks::MIN, SpanWeeks::MAX),
// The number of hours allowed in a time zone offset.
//
// This number was somewhat arbitrarily chosen. In part because it's bigger
// than any current offset in actual use by a wide margin, and in part
// because POSIX `TZ` strings require the ability to store offsets in the
// range `-24:59:59..=25:59:59`. Note though that we make the range a
// little bigger with `-25:59:59..=25:59:59` so that negating an offset
// always produces a valid offset.
//
// Note that RFC 8536 actually allows offsets to be much bigger, namely,
// in the range `(-2^31, 2^31)`, where both ends are _exclusive_ (`-2^31`
// is explicitly disallowed, and `2^31` overflows a signed 32-bit
// integer). But RFC 8536 does say that it *should* be in the range
// `[-89999, 93599]`, which matches POSIX. In order to keep our offset
// small, we stick roughly to what POSIX requires.
//
// Note that we support a slightly bigger range of offsets than Temporal.
// Temporal seems to support only up to 23 hours, but we go up to 25 hours.
// This is done to support POSIX time zone strings, which also require 25
// hours (plus the maximal minute/second components).
(OffsetHours, i8, "time zone offset hours", -25, 25),
(OffsetMinutes, i8, "time zone offset minutes", -59, 59),
(OffsetSeconds, i8, "time zone offset seconds", -59, 59),
(
OffsetTotalSeconds,
i32,
"time zone offset total seconds",
-Self::MAX,
(OffsetHours::MAX as i32 * SECS_PER_HOUR_32)
+ (OffsetMinutes::MAX as i32 * MINS_PER_HOUR_32)
+ OffsetSeconds::MAX as i32,
),
(Second, i8, "second", 0, 59),
(Microsecond, i16),
(Millisecond, i16),
(Minute, i8),
(Month, i8),
(Nanosecond, i16),
(NthWeekday, i32),
(OffsetHours, i8),
(OffsetMinutes, i8),
(OffsetSeconds, i8),
(OffsetTotalSeconds, i32),
(Second, i8),
(SignedDurationSeconds, i64, "signed duration seconds", i64::MIN, i64::MAX),
(SpanYears, i16, "years", -Self::MAX, (Year::LEN - 1) as i16),
(SpanMonths, i32, "months", -Self::MAX, SpanYears::MAX as i32 * 12),
(SpanWeeks, i32, "weeks", -Self::MAX, SpanDays::MAX / DAYS_PER_WEEK_32),
(SpanDays, i32, "days", -Self::MAX, SpanHours::MAX / HOURS_PER_CIVIL_DAY_32),
(SpanHours, i32, "hours", -Self::MAX, (SpanMinutes::MAX / MINS_PER_HOUR) as i32),
(SpanMinutes, i64, "minutes", -Self::MAX, SpanSeconds::MAX / SECS_PER_MIN),
// The maximum number of seconds that can be expressed with a span.
//
// All of our span types (except for years and months, since they have
// variable length even in civil datetimes) are defined in terms of this
// constant. The way it's defined is a little odd, so let's break it down.
//
// Firstly, a span of seconds should be able to represent at least the
// complete span supported by `Timestamp`. Thus, it's based off of
// `UnixSeconds::LEN`. That is, a span should be able to represent the
// value `UnixSeconds::MAX - UnixSeconds::MIN`.
//
// Secondly, a span should also be able to account for any amount of
// possible time that a time zone offset might add or subtract to an
// `Timestamp`. This also means it can account for any difference between
// two `civil::DateTime` values.
//
// Thirdly, we would like our span to be divisible by
// `SECONDS_PER_CIVIL_DAY`. This isn't strictly required, but it makes
// defining boundaries a little smoother. If it weren't divisible, then the
// lower bounds on some types would need to be adjusted by one.
//
// Note that neither the existence of this constant nor defining our
// spans based on it impacts the correctness of doing arithmetic on zoned
// instants. Arithmetic on zoned instants still uses "civil" spans, but the
// length of time for some units (like a day) might vary. The arithmetic
// for zoned instants accounts for this explicitly. But it still must obey
// the limits set here.
(SpanWeeks, i32, "weeks", -Self::MAX, SpanDays::MAX / c::DAYS_PER_CIVIL_WEEK_32),
(SpanDays, i32, "days", -Self::MAX, SpanHours::MAX / c::HOURS_PER_CIVIL_DAY_32),
(SpanHours, i32, "hours", -Self::MAX, (SpanMinutes::MAX / c::MINS_PER_HOUR) as i32),
(SpanMinutes, i64, "minutes", -Self::MAX, SpanSeconds::MAX / c::SECS_PER_MIN),
(

@@ -279,9 +213,4 @@ SpanSeconds,

"seconds",
-Self::MAX,
next_multiple_of(
UnixSeconds::LEN as i64
+ OffsetTotalSeconds::MAX as i64
+ SECS_PER_CIVIL_DAY,
SECS_PER_CIVIL_DAY,
),
bounds::DeltaSeconds::MIN,
bounds::DeltaSeconds::MAX,
),

@@ -293,3 +222,3 @@ (

-Self::MAX,
SpanSeconds::MAX * MILLIS_PER_SEC,
SpanSeconds::MAX * c::MILLIS_PER_SEC,
),

@@ -301,3 +230,3 @@ (

-Self::MAX,
SpanMilliseconds::MAX * MICROS_PER_MILLI,
SpanMilliseconds::MAX * c::MICROS_PER_MILLI,
),

@@ -309,3 +238,3 @@ (SpanMultiple, i64, "span multiple", i64::MIN + 1, i64::MAX),

// `UnixSeconds::MAX * NANOSECONDS_PER_SECOND` cannot fit into 64-bits. We
// could use a `i128`, but it doesn't seem worth it.
// could use a `i128`, but it doesn't seem worth bloating up `Span`.
//

@@ -316,87 +245,25 @@ // Also note that our min is equal to -max, so that the total number of

(SpanNanoseconds, i64, "nanoseconds", i64::MIN + 1, i64::MAX),
(SubsecNanosecond, i32, "subsecond nanosecond", 0, NANOS_PER_SEC_32 - 1),
(SubsecNanosecond, i32),
(SignedSubsecNanosecond, i32),
(UnixEpochDays, i32),
(
SignedSubsecNanosecond,
i32,
"subsecond nanosecond",
-SubsecNanosecond::MAX,
SubsecNanosecond::MAX,
),
// The number of days from the Unix epoch for the Gregorian calendar.
//
// The range supported is based on the range of Unix timestamps that we
// support.
//
// While I had originally used the "rate die" concept from Calendrical
// Calculations, I found [Howard Hinnant's formulation][date-algorithms]
// much more straight-forward.
//
// [date-algorithms]: http://howardhinnant.github.io/date_algorithms.html
(
UnixEpochDays,
i32,
"Unix epoch days",
(UnixSeconds::MIN+ OffsetTotalSeconds::MIN as i64).div_euclid(SECS_PER_CIVIL_DAY) as i32,
(UnixSeconds::MAX + OffsetTotalSeconds::MAX as i64).div_euclid(SECS_PER_CIVIL_DAY) as i32,
),
(
UnixMilliseconds,
UnixEpochMilliseconds,
i64,
"Unix timestamp milliseconds",
UnixSeconds::MIN * MILLIS_PER_SEC,
UnixSeconds::MAX * MILLIS_PER_SEC,
UnixEpochSeconds::MIN * c::MILLIS_PER_SEC,
UnixEpochSeconds::MAX * c::MILLIS_PER_SEC,
),
(
UnixMicroseconds,
UnixEpochMicroseconds,
i64,
"Unix timestamp microseconds",
UnixMilliseconds::MIN * MICROS_PER_MILLI,
UnixMilliseconds::MAX * MICROS_PER_MILLI,
UnixEpochMilliseconds::MIN * c::MICROS_PER_MILLI,
UnixEpochMilliseconds::MAX * c::MICROS_PER_MILLI,
),
// The range of Unix seconds supported by Jiff.
//
// This range should correspond to the first second of `Year::MIN` up
// through (and including) the last second of `Year::MAX`. Actually
// computing that is non-trivial, however, it can be computed easily enough
// using Unix programs like `date`:
//
// ```text
// $ TZ=0 date -d 'Mon Jan 1 12:00:00 AM -9999' +'%s'
// date: invalid date ‘Mon Jan 1 12:00:00 AM -9999’
// $ TZ=0 date -d 'Fri Dec 31 23:59:59 9999' +'%s'
// 253402300799
// ```
//
// Well, almost easily enough. `date` apparently doesn't support negative
// years. But it does support negative timestamps:
//
// ```text
// $ TZ=0 date -d '@-377705116800'
// Mon Jan 1 12:00:00 AM -9999
// $ TZ=0 date -d '@253402300799'
// Fri Dec 31 11:59:59 PM 9999
// ```
//
// With that said, we actually end up restricting the range a bit more
// than what's above. Namely, what's above is what we support for civil
// datetimes. Because of time zones, we need to choose whether all
// `Timestamp` values can be infallibly converted to `civil::DateTime`
// values, or whether all `civil::DateTime` values can be infallibly
// converted to `Timestamp` values. I chose the former because getting
// a civil datetime is important for formatting. If I didn't choose the
// former, there would be some instants that could not be formatted. Thus,
// we make room by shrinking the range of allowed instants by precisely the
// maximum supported time zone offset.
(
UnixSeconds,
i64,
"Unix timestamp seconds",
-377705116800 - OffsetTotalSeconds::MIN as i64,
253402300799 - OffsetTotalSeconds::MAX as i64,
),
(UnixEpochSeconds, i64),
(WeekNum, i8, "week-number", 0, 53),
(WeekdayMondayZero, i8, "weekday (Monday 0-indexed)", 0, 6),
(WeekdayMondayOne, i8, "weekday (Monday 1-indexed)", 1, 7),
(WeekdaySundayZero, i8, "weekday (Sunday 0-indexed)", 0, 6),
(WeekdaySundayOne, i8, "weekday (Sunday 1-indexed)", 1, 7),
(WeekdayMondayZero, i8),
(WeekdayMondayOne, i8),
(WeekdaySundayZero, i8),
(WeekdaySundayOne, i8),
// The range of years supported by Jiff.

@@ -411,5 +278,5 @@ //

// than just changing some ranges here.)
(Year, i16, "year", -9999, 9999),
(YearCE, i16, "CE year", 1, Year::MAX),
(YearBCE, i16, "BCE year", 1, Year::MAX + 1),
(Year, i16),
(YearCE, i16),
(YearBCE, i16),
(YearTwoDigit, i16, "year (2 digits)", 0, 99),

@@ -420,4 +287,4 @@ (

"nanoseconds (in one zoned datetime day)",
ZonedDaySeconds::MIN as i64 * NANOS_PER_SEC,
ZonedDaySeconds::MAX as i64 * NANOS_PER_SEC,
ZonedDaySeconds::MIN as i64 * c::NANOS_PER_SEC,
ZonedDaySeconds::MAX as i64 * c::NANOS_PER_SEC,
),

@@ -435,230 +302,6 @@ // The number of seconds permitted in a single day.

1,
7 * SECS_PER_CIVIL_DAY_32,
7 * c::SECS_PER_CIVIL_DAY_32,
),
}
/// An interface for defining boundaries on integer values.
pub(crate) trait Bounds: Sized {
/// A short human readable description of the values represented by these
/// bounds.
const WHAT: &'static str;
/// The minimum boundary value.
const MIN: Self::Primitive;
/// The maximum boundary value.
const MAX: Self::Primitive;
/// The primitive integer representation for this boundary type.
///
/// This is generally the smallest primitive integer type that fits the
/// minimum and maximum allowed values.
// MSRV: Ideally this would be a private trait. On newer versions
// of Rust (not sure when it started exactly), it's allowed but
// comes with a warn-by-default lint. I would like it to be private
// to avoid accidentally using it elsewhere, since it makes casts
// between integers very easy.
type Primitive: Primitive;
// We provide `check` and `check128` to avoid manifesting 128-bit integers
// in the vast majority of cases. While in theory the compiler should be
// able to see through it, this is such a primitive and common operation
// used throughout Jiff, that we specialize the overwhelmingly common case
// for 64-bit integers under the presumption that 64-bit integers (and
// smaller) are either always fast enough or are slower in environments
// where we care less about performance.
/// Create an error when a value is outside the bounds for this type.
fn error() -> BoundsError;
/// Converts the 64-bit integer provided into the primitive representation
/// of these bounds.
///
/// # Errors
///
/// This returns an error if the given integer does not fit in the bounds
/// prescribed by this trait implementation.
///
/// # Panics
///
/// This panics when `debug_assertions` are enabled if the bounds of
/// this implementation exceed what is representable in an `i64`. In
/// this case, callers must use `check128`.
#[cfg_attr(feature = "perf-inline", inline(always))]
fn check(n: impl Into<i64>) -> Result<Self::Primitive, BoundsError> {
// These asserts confirm that we only call this routine when our
// bounds fit into an i64. Otherwise, the `as_i64()` casts below
// are incorrect.
debug_assert!((i128::from(i64::MIN)..=i128::from(i64::MAX))
.contains(&Self::MIN.as_i128()));
debug_assert!((i128::from(i64::MIN)..=i128::from(i64::MAX))
.contains(&Self::MAX.as_i128()));
let n = n.into();
if !(Self::MIN.as_i64() <= n && n <= Self::MAX.as_i64()) {
return Err(Self::error());
}
Ok(Self::Primitive::from_i64(n))
}
/// Converts the 128-bit integer provided into the primitive representation
/// of these bounds.
///
/// # Errors
///
/// This returns an error if the given integer does not fit in the bounds
/// prescribed by this trait implementation.
#[cfg_attr(feature = "perf-inline", inline(always))]
fn check128(n: impl Into<i128>) -> Result<Self::Primitive, BoundsError> {
let n = n.into();
if !(Self::MIN.as_i128() <= n && n <= Self::MAX.as_i128()) {
return Err(Self::error());
}
Ok(Self::Primitive::from_i128(n))
}
/// Checks whether the given integer, in the same primitive representation
/// as this boundary type, is in bounds.
///
/// # Errors
///
/// This returns an error if the given integer does not fit in the bounds
/// prescribed by this trait implementation.
#[cfg_attr(feature = "perf-inline", inline(always))]
fn check_self(n: Self::Primitive) -> Result<Self::Primitive, BoundsError> {
if !(Self::MIN <= n && n <= Self::MAX) {
return Err(Self::error());
}
Ok(n)
}
/// Parses a 64-bit integer from the beginning to the end of the given
/// slice of bytes.
///
/// Note that this can never parse a negative integer since it doesn't
/// look for a sign. On success, the integer returned is always positive.
///
/// # Errors
///
/// If the given slice is not a valid integer (i.e., overflow or contains
/// anything other than `[0-9]`) or is not in the bounds for this trait
/// implementation, then an error is returned.
///
/// Note that the error can either be a parsing error or it can be a
/// boundary error.
#[cfg_attr(feature = "perf-inline", inline(always))]
fn parse(bytes: &[u8]) -> Result<Self::Primitive, Error> {
Ok(Self::check(crate::util::parse::i64(bytes)?)?)
}
/// Performs checked addition using this boundary type's primitive
/// representation.
///
/// # Errors
///
/// If the result exceeds the boundaries of the primitive type or of the
/// declared range for this type, then an error is returned.
#[cfg_attr(feature = "perf-inline", inline(always))]
fn checked_add(
n1: Self::Primitive,
n2: Self::Primitive,
) -> Result<Self::Primitive, BoundsError> {
Self::check_self(n1.checked_add(n2).ok_or_else(Self::error)?)
}
/// Performs checked subtraction using this boundary type's primitive
/// representation.
///
/// # Errors
///
/// If the result exceeds the boundaries of the primitive type or of the
/// declared range for this type, then an error is returned.
#[cfg_attr(feature = "perf-inline", inline(always))]
fn checked_sub(
n1: Self::Primitive,
n2: Self::Primitive,
) -> Result<Self::Primitive, BoundsError> {
Self::check_self(n1.checked_sub(n2).ok_or_else(Self::error)?)
}
/// Performs checked multiplication using this boundary type's primitive
/// representation.
///
/// # Errors
///
/// If the result exceeds the boundaries of the primitive type or of the
/// declared range for this type, then an error is returned.
#[cfg_attr(feature = "perf-inline", inline(always))]
fn checked_mul(
n1: Self::Primitive,
n2: Self::Primitive,
) -> Result<Self::Primitive, BoundsError> {
Self::check_self(n1.checked_mul(n2).ok_or_else(Self::error)?)
}
}
/// A simple trait for making `int as int` usable in a generic context.
///
/// All of these methods require callers to ensure the cast is correct.
pub(crate) trait Primitive:
Clone
+ Copy
+ Eq
+ PartialEq
+ PartialOrd
+ Ord
+ core::fmt::Debug
+ core::fmt::Display
{
fn as_i8(self) -> i8;
fn as_i16(self) -> i16;
fn as_i32(self) -> i32;
fn as_i64(self) -> i64;
fn as_i128(self) -> i128;
fn from_i8(n: i8) -> Self;
fn from_i16(n: i16) -> Self;
fn from_i32(n: i32) -> Self;
fn from_i64(n: i64) -> Self;
fn from_i128(n: i128) -> Self;
fn checked_add(self, n: Self) -> Option<Self>;
fn checked_sub(self, n: Self) -> Option<Self>;
fn checked_mul(self, n: Self) -> Option<Self>;
}
macro_rules! impl_primitive {
($($intty:ty),*) => {
$(
impl Primitive for $intty {
fn as_i8(self) -> i8 { self as i8 }
fn as_i16(self) -> i16 { self as i16 }
fn as_i32(self) -> i32 { self as i32 }
fn as_i64(self) -> i64 { self as i64 }
fn as_i128(self) -> i128 { self as i128 }
fn from_i8(n: i8) -> Self { n as $intty }
fn from_i16(n: i16) -> Self { n as $intty }
fn from_i32(n: i32) -> Self { n as $intty }
fn from_i64(n: i64) -> Self { n as $intty }
fn from_i128(n: i128) -> Self { n as $intty }
fn checked_add(self, n: $intty) -> Option<$intty> {
<$intty>::checked_add(self, n)
}
fn checked_sub(self, n: $intty) -> Option<$intty> {
<$intty>::checked_sub(self, n)
}
fn checked_mul(self, n: $intty) -> Option<$intty> {
<$intty>::checked_mul(self, n)
}
}
)*
}
}
impl_primitive!(i8, i16, i32, i64, i128);
impl From<BoundsError> for Error {

@@ -676,17 +319,18 @@ fn from(err: BoundsError) -> Error {

pub(crate) struct RawBoundsError<B>(core::marker::PhantomData<B>);
/// A helper type to implement `defmt::Format`.
///
/// This avoids implementing it in `jiff-core`.
#[derive(Eq, PartialEq)]
pub(crate) struct RawBoundsWrapperError<B>(RawBoundsError<B>);
impl<B> RawBoundsError<B> {
const fn new() -> RawBoundsError<B> {
RawBoundsError(core::marker::PhantomData)
}
}
impl<B> Copy for RawBoundsWrapperError<B> {}
impl<B> Clone for RawBoundsError<B> {
fn clone(&self) -> RawBoundsError<B> {
RawBoundsError::new()
impl<B> Clone for RawBoundsWrapperError<B> {
#[inline]
fn clone(&self) -> RawBoundsWrapperError<B> {
RawBoundsWrapperError(RawBoundsError::new())
}
}
impl<B, P> core::fmt::Debug for RawBoundsError<B>
impl<B, P> core::fmt::Debug for RawBoundsWrapperError<B>
where

@@ -697,11 +341,7 @@ B: Bounds<Primitive = P>,

fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("RawBoundsError")
.field("what", &B::WHAT)
.field("min", &B::MIN)
.field("max", &B::MAX)
.finish()
core::fmt::Debug::fmt(&self.0, f)
}
}
impl<B, P> core::fmt::Display for RawBoundsError<B>
impl<B, P> core::fmt::Display for RawBoundsWrapperError<B>
where

@@ -712,9 +352,3 @@ B: Bounds<Primitive = P>,

fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"parameter '{what}' is not in the required range of {min}..={max}",
what = B::WHAT,
min = B::MIN,
max = B::MAX,
)
core::fmt::Display::fmt(&self.0, f)
}

@@ -724,3 +358,3 @@ }

#[cfg(feature = "defmt")]
impl<B, P> defmt::Format for RawBoundsError<B>
impl<B, P> defmt::Format for RawBoundsWrapperError<B>
where

@@ -747,3 +381,2 @@ B: Bounds<Primitive = P>,

pub(crate) enum SpecialBoundsError {
UnixNanoseconds,
SignedDurationFloatOutOfRangeF32,

@@ -758,16 +391,9 @@ SignedDurationFloatOutOfRangeF64,

let (what, min, max) = match *self {
UnixNanoseconds => (
"Unix timestamp nanoseconds",
UnixMicroseconds::MIN as i128 * (NANOS_PER_MICRO as i128),
UnixMicroseconds::MAX as i128 * (NANOS_PER_MICRO as i128),
match *self {
SignedToUnsignedDuration => f.write_str(
"negative signed durations cannot be converted \
to an unsigned duration",
),
SignedToUnsignedDuration => {
return f.write_str(
"negative signed durations cannot be converted \
to an unsigned duration",
);
}
SignedDurationFloatOutOfRangeF32 => {
return write!(
write!(
f,

@@ -778,6 +404,6 @@ "parameter 'floating point seconds' is not in \

max = i64::MAX as f32,
);
)
}
SignedDurationFloatOutOfRangeF64 => {
return write!(
write!(
f,

@@ -788,9 +414,5 @@ "parameter 'floating point seconds' is not in \

max = i64::MAX as f64,
);
)
}
};
write!(
f,
"parameter '{what}' is not in the required range of {min}..={max}",
)
}
}

@@ -811,362 +433,2 @@ }

/// A representation of a numeric sign.
///
/// Its `Display` impl emits the ASCII minus sign, `-` when this
/// is negative. It emits the empty string in all other cases.
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord,
)]
#[repr(i8)]
pub(crate) enum Sign {
#[default]
Zero = 0,
Positive = 1,
Negative = -1,
}
impl Sign {
pub(crate) fn is_zero(self) -> bool {
matches!(self, Sign::Zero)
}
pub(crate) fn is_positive(self) -> bool {
matches!(self, Sign::Positive)
}
pub(crate) fn is_negative(self) -> bool {
matches!(self, Sign::Negative)
}
pub(crate) fn signum(self) -> i8 {
self.as_i8()
}
pub(crate) fn as_i8(self) -> i8 {
self as i8
}
pub(crate) fn as_i16(self) -> i16 {
i16::from(self.as_i8())
}
pub(crate) fn as_i32(self) -> i32 {
i32::from(self.as_i8())
}
pub(crate) fn as_i64(self) -> i64 {
i64::from(self.as_i8())
}
pub(crate) fn as_i128(self) -> i128 {
i128::from(self.as_i8())
}
pub(crate) fn from_ordinals<T: Ord>(t1: T, t2: T) -> Sign {
use core::cmp::Ordering::*;
match t1.cmp(&t2) {
Less => Sign::Negative,
Equal => Sign::Zero,
Greater => Sign::Positive,
}
}
}
impl core::ops::Neg for Sign {
type Output = Sign;
fn neg(self) -> Sign {
match self {
Sign::Positive => Sign::Negative,
Sign::Zero => Sign::Zero,
Sign::Negative => Sign::Positive,
}
}
}
impl From<i8> for Sign {
fn from(n: i8) -> Sign {
Sign::from(i64::from(n))
}
}
impl From<i16> for Sign {
fn from(n: i16) -> Sign {
Sign::from(i64::from(n))
}
}
impl From<i32> for Sign {
fn from(n: i32) -> Sign {
Sign::from(i64::from(n))
}
}
impl From<i64> for Sign {
fn from(n: i64) -> Sign {
if n == 0 {
Sign::Zero
} else if n > 0 {
Sign::Positive
} else {
Sign::Negative
}
}
}
impl From<i128> for Sign {
fn from(n: i128) -> Sign {
if n == 0 {
Sign::Zero
} else if n > 0 {
Sign::Positive
} else {
Sign::Negative
}
}
}
impl From<f64> for Sign {
fn from(n: f64) -> Sign {
use core::num::FpCategory::*;
// This is a little odd, but we want +/- 0 to
// always have a sign of zero, so as to be consistent
// with how we deal with signed integers.
//
// As for NaN... It should generally be a bug if
// Jiff ever materializes a NaN. Notably, I do not
// believe there are any APIs in which a float is
// given from the caller. Jiff only ever uses them
// internally or returns them. So if we get a NaN,
// it's on us. If we do, just assign it a zero sign?
if matches!(n.classify(), Nan | Zero) {
Sign::Zero
} else if n.is_sign_positive() {
Sign::Positive
} else {
Sign::Negative
}
}
}
impl From<SignedDuration> for Sign {
fn from(n: SignedDuration) -> Sign {
if n.is_zero() {
Sign::Zero
} else if n.is_positive() {
Sign::Positive
} else {
Sign::Negative
}
}
}
impl core::ops::Mul<Sign> for Sign {
type Output = Sign;
fn mul(self, rhs: Sign) -> Sign {
match (self, rhs) {
(Sign::Zero, _) | (_, Sign::Zero) => Sign::Zero,
(Sign::Positive, Sign::Positive) => Sign::Positive,
(Sign::Negative, Sign::Negative) => Sign::Positive,
(Sign::Positive, Sign::Negative) => Sign::Negative,
(Sign::Negative, Sign::Positive) => Sign::Negative,
}
}
}
impl core::ops::Mul<i8> for Sign {
type Output = i8;
fn mul(self, n: i8) -> i8 {
self.as_i8() * n
}
}
impl core::ops::Mul<Sign> for i8 {
type Output = i8;
fn mul(self, n: Sign) -> i8 {
self * n.as_i8()
}
}
impl core::ops::Mul<i16> for Sign {
type Output = i16;
fn mul(self, n: i16) -> i16 {
self.as_i16() * n
}
}
impl core::ops::Mul<Sign> for i16 {
type Output = i16;
fn mul(self, n: Sign) -> i16 {
self * n.as_i16()
}
}
impl core::ops::Mul<i32> for Sign {
type Output = i32;
fn mul(self, n: i32) -> i32 {
self.as_i32() * n
}
}
impl core::ops::Mul<Sign> for i32 {
type Output = i32;
fn mul(self, n: Sign) -> i32 {
self * n.as_i32()
}
}
impl core::ops::Mul<i64> for Sign {
type Output = i64;
fn mul(self, n: i64) -> i64 {
self.as_i64() * n
}
}
impl core::ops::Mul<Sign> for i64 {
type Output = i64;
fn mul(self, n: Sign) -> i64 {
self * n.as_i64()
}
}
impl core::ops::Mul<i128> for Sign {
type Output = i128;
fn mul(self, n: i128) -> i128 {
self.as_i128() * n
}
}
impl core::ops::Mul<Sign> for i128 {
type Output = i128;
fn mul(self, n: Sign) -> i128 {
self * n.as_i128()
}
}
impl core::fmt::Display for Sign {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.is_negative() {
f.write_str("-")
} else {
Ok(())
}
}
}
mod checkc {
use super::{Bounds, RawBoundsError};
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(super) const fn i8<B>(n: i64) -> Result<i8, RawBoundsError<B>>
where
B: Bounds<Primitive = i8>,
{
// These asserts confirm that we only call this routine
// when our bounds fit into an i64. Otherwise, the
// `as` casts below are incorrect.
debug_assert!(
(i64::MIN as i128) <= (B::MIN as i128)
&& (B::MIN as i128) <= (i64::MAX as i128),
);
debug_assert!(
(i64::MIN as i128) <= (B::MAX as i128)
&& (B::MAX as i128) <= (i64::MAX as i128),
);
if !((B::MIN as i64) <= n && n <= (B::MAX as i64)) {
return Err(RawBoundsError::new());
}
Ok(n as i8)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(super) const fn i16<B>(n: i64) -> Result<i16, RawBoundsError<B>>
where
B: Bounds<Primitive = i16>,
{
// These asserts confirm that we only call this routine
// when our bounds fit into an i64. Otherwise, the
// `as` casts below are incorrect.
debug_assert!(
(i64::MIN as i128) <= (B::MIN as i128)
&& (B::MIN as i128) <= (i64::MAX as i128),
);
debug_assert!(
(i64::MIN as i128) <= (B::MAX as i128)
&& (B::MAX as i128) <= (i64::MAX as i128),
);
if !((B::MIN as i64) <= n && n <= (B::MAX as i64)) {
return Err(RawBoundsError::new());
}
Ok(n as i16)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(super) const fn i32<B>(n: i64) -> Result<i32, RawBoundsError<B>>
where
B: Bounds<Primitive = i32>,
{
// These asserts confirm that we only call this routine
// when our bounds fit into an i64. Otherwise, the
// `as` casts below are incorrect.
debug_assert!(
(i64::MIN as i128) <= (B::MIN as i128)
&& (B::MIN as i128) <= (i64::MAX as i128),
);
debug_assert!(
(i64::MIN as i128) <= (B::MAX as i128)
&& (B::MAX as i128) <= (i64::MAX as i128),
);
if !((B::MIN as i64) <= n && n <= (B::MAX as i64)) {
return Err(RawBoundsError::new());
}
Ok(n as i32)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(super) const fn i64<B>(n: i64) -> Result<i64, RawBoundsError<B>>
where
B: Bounds<Primitive = i64>,
{
// These asserts confirm that we only call this routine
// when our bounds fit into an i64. Otherwise, the
// `as` casts below are incorrect.
debug_assert!(
(i64::MIN as i128) <= (B::MIN as i128)
&& (B::MIN as i128) <= (i64::MAX as i128),
);
debug_assert!(
(i64::MIN as i128) <= (B::MAX as i128)
&& (B::MAX as i128) <= (i64::MAX as i128),
);
if !(B::MIN <= n && n <= B::MAX) {
return Err(RawBoundsError::new());
}
Ok(n)
}
}
/// Computes the next multiple of `rhs` that is greater than or equal to `lhs`.
///
/// Taken from:
/// https://github.com/rust-lang/rust/blob/eff958c59e8c07ba0515e164b825c9001b242294/library/core/src/num/int_macros.rs
const fn next_multiple_of(lhs: i64, rhs: i64) -> i64 {
// This would otherwise fail when calculating `r` when self == T::MIN.
if rhs == -1 {
return lhs;
}
let r = lhs % rhs;
let m = if (r > 0 && rhs < 0) || (r < 0 && rhs > 0) { r + rhs } else { r };
if m == 0 {
lhs
} else {
lhs + (rhs - m)
}
}
#[cfg(test)]

@@ -1173,0 +435,0 @@ mod tests {

@@ -1,2 +0,1 @@

pub(crate) mod array_str;
pub(crate) mod b;

@@ -3,0 +2,0 @@ pub(crate) mod borrow;

@@ -1,3 +0,8 @@

use crate::error::util::{ParseFractionError, ParseIntError};
use jcore::bounds::Bounds;
use crate::{
error::util::{ParseFractionError, ParseIntError},
Error,
};
/// Parses an `i64` number from the beginning to the end of the given slice of

@@ -31,2 +36,21 @@ /// ASCII digit characters.

/// Like `self::i64`, but also does a boundary check for the given type.
///
/// # Errors
///
/// If the given slice is not a valid integer (i.e., overflow or contains
/// anything other than `[0-9]`) or is not in the bounds for the given `Bounds`
/// implementation, then an error is returned.
///
/// Note that the error can either be a parsing error or it can be a
/// boundary error.
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn bi64<B>(bytes: &[u8]) -> Result<B::Primitive, Error>
where
B: Bounds,
Error: From<B::Error>,
{
Ok(B::check(self::i64(bytes)?)?)
}
/// Parsed an optional `u64` that is a prefix of `bytes`.

@@ -133,32 +157,2 @@ ///

/// Parses an `OsStr` into a `&str` when `&[u8]` isn't easily available.
///
/// The main difference between this and `OsStr::to_str` is that this will
/// be a zero-cost conversion on Unix platforms to `&[u8]`. On Windows, this
/// will do UTF-8 validation and return an error if it's invalid UTF-8.
#[cfg(feature = "tz-system")]
pub(crate) fn os_str_bytes<'o, O>(
os_str: &'o O,
) -> Result<&'o [u8], crate::error::util::OsStrUtf8Error>
where
O: ?Sized + AsRef<std::ffi::OsStr>,
{
let os_str = os_str.as_ref();
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
Ok(os_str.as_bytes())
}
#[cfg(not(unix))]
{
// It is suspect that we're doing UTF-8 validation and then throwing
// away the fact that we did UTF-8 validation. So this could lead
// to an extra UTF-8 check if the caller ultimately needs UTF-8. If
// that's important, we can add a new API that returns a `&str`. But it
// probably won't matter because an `OsStr` in this crate is usually
// just an environment variable.
Ok(os_str_utf8(os_str)?.as_bytes())
}
}
/// Splits the given input into two slices at the given position.

@@ -165,0 +159,0 @@ ///

use crate::error;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) enum Error {
ColonPrefixInvalidUtf8,
InvalidPosixTz,
}
impl From<Error> for error::Error {
#[cold]
#[inline(never)]
fn from(err: Error) -> error::Error {
error::ErrorKind::TzPosix(err).into()
}
}
impl error::IntoError for Error {
fn into_error(self) -> error::Error {
self.into()
}
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::Error::*;
match *self {
ColonPrefixInvalidUtf8 => f.write_str(
"POSIX time zone string with a `:` prefix \
contains invalid UTF-8",
),
InvalidPosixTz => f.write_str("invalid POSIX time zone string"),
}
}
}
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
}
// 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,
],
];
/*!
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:
<https://github.com/BurntSushi/jiff/issues/256>
# 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.
* All `#[cfg(feature = "defmt")]` annotations and gated items are removed. The
`jiff-static` proc macro only needs these types internally for parsing and
code generation.
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.
// only-jiff-start
pub type TzifStatic = Tzif<
&'static str,
&'static str,
&'static [TzifLocalTimeType],
&'static [i64],
&'static [TzifDateTime],
&'static [TzifDateTime],
&'static [TzifTransitionInfo],
>;
// only-jiff-end
/// An alias for TZif data whose backing storage is on the heap.
#[cfg(feature = "alloc")]
pub type TzifOwned = Tzif<
alloc::string::String,
self::util::array_str::Abbreviation,
alloc::vec::Vec<TzifLocalTimeType>,
alloc::vec::Vec<i64>,
alloc::vec::Vec<TzifDateTime>,
alloc::vec::Vec<TzifDateTime>,
alloc::vec::Vec<TzifTransitionInfo>,
>;
/// An alias for TZif transition data whose backing storage is on the heap.
#[cfg(feature = "alloc")]
pub type TzifTransitionsOwned = TzifTransitions<
alloc::vec::Vec<i64>,
alloc::vec::Vec<TzifDateTime>,
alloc::vec::Vec<TzifDateTime>,
alloc::vec::Vec<TzifTransitionInfo>,
>;
#[derive(Clone, Debug)]
pub struct Tzif<STR, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS> {
pub fixed: TzifFixed<STR, ABBREV>,
pub types: TYPES,
pub transitions: TzifTransitions<TIMESTAMPS, STARTS, ENDS, INFOS>,
}
#[derive(Clone, Debug)]
pub struct TzifFixed<STR, ABBREV> {
pub name: Option<STR>,
/// 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<PosixTimeZone<ABBREV>>,
}
#[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<TIMESTAMPS, STARTS, ENDS, INFOS> {
/// 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, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct TzifDateTime {
bits: i64,
}
impl TzifDateTime {
pub const ZERO: TzifDateTime = TzifDateTime::new(0, 0, 0, 0, 0, 0);
const MIN: TzifDateTime = TzifDateTime::new(-9999, 1, 1, 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
}
}
impl core::fmt::Debug for TzifDateTime {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if !f.alternate() {
f.debug_struct("TzifDateTime").field("bits", &self.bits).finish()
} else {
f.debug_tuple("TzifDateTime")
.field(&format_args!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
self.year(),
self.month(),
self.day(),
self.hour(),
self.minute(),
self.second(),
))
.finish()
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PosixTimeZone<ABBREV> {
pub std_abbrev: ABBREV,
pub std_offset: PosixOffset,
pub dst: Option<PosixDst<ABBREV>>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PosixDst<ABBREV> {
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,
}
// only-jiff-start
impl TzifStatic {
pub const fn into_jiff(self) -> crate::tz::tzif::TzifStatic {
crate::tz::tzif::TzifStatic::from_shared_const(self)
}
}
// only-jiff-end
// only-jiff-start
impl PosixTimeZone<&'static str> {
pub const fn into_jiff(self) -> crate::tz::posix::PosixTimeZoneStatic {
crate::tz::posix::PosixTimeZone::from_shared_const(self)
}
}
// only-jiff-end
// Does not require `alloc`, but is only used when `alloc` is enabled.
#[cfg(feature = "alloc")]
pub(crate) mod crc32;
pub(crate) mod posix;
#[cfg(feature = "alloc")]
pub(crate) mod tzif;
pub(crate) mod util;

Sorry, the diff of this file is too big to display

use alloc::{string::String, vec};
use super::{
util::{
array_str::Abbreviation,
itime::{IOffset, ITimestamp},
},
PosixTimeZone, TzifDateTime, TzifFixed, TzifIndicator, TzifLocalTimeType,
TzifOwned, TzifTransitionInfo, TzifTransitionKind, TzifTransitions,
TzifTransitionsOwned,
};
// These are Jiff min and max timestamp (in seconds) values.
//
// The TZif parser will clamp timestamps to this range. It's
// not ideal, but Jiff can't handle values outside of this range
// and completely refusing to use TZif data with pathological
// timestamps in typically irrelevant transitions is bad juju.
//
// Ref: https://github.com/BurntSushi/jiff/issues/163
// Ref: https://github.com/BurntSushi/jiff/pull/164
const TIMESTAMP_MIN: i64 = -377705023201;
const TIMESTAMP_MAX: i64 = 253402207200;
// Similarly for offsets, although in this case, if we find
// an offset outside of this range, we do actually error. This
// is because it could result in true incorrect datetimes for
// actual transitions.
//
// But our supported offset range is `-25:59:59..=+25:59:59`.
// There's no real time zone with offsets even close to those
// boundaries.
//
// If there is pathological data that we should ignore, then
// we should wait for a real bug report in order to determine
// the right way to ignore/clamp it.
const OFFSET_MIN: i32 = -93599;
const OFFSET_MAX: i32 = 93599;
// When fattening TZif data, this is the year to go up to.
//
// This year was chosen because it's what the "fat" TZif data generated
// by `zic` uses.
const FATTEN_UP_TO_YEAR: i16 = 2038;
// This is a "sanity" limit on the maximum number of transitions we'll
// add to TZif data when fattening them up.
//
// This is mostly just a defense-in-depth limit to avoid weird cases
// where a pathological POSIX time zone could be defined to create
// many transitions. It's not clear that this is actually possible,
// but I felt a little uneasy doing unbounded work that isn't linearly
// proportional to the input data. So, this limit is put into place for
// reasons of "good sense."
//
// For "normal" cases, there should be at most two transitions per
// year. So this limit permits 300/2=150 years of transition data.
// (Although we won't go above `FATTEN_UP_TO_YEAR`. See above.)
const FATTEN_MAX_TRANSITIONS: usize = 300;
impl TzifOwned {
/// Parses the given data as a TZif formatted file.
///
/// The name given is attached to the `Tzif` value returned, but is
/// otherwise not significant.
///
/// If the given data is not recognized to be valid TZif, then an error is
/// returned.
///
/// In general, callers may assume that it is safe to pass arbitrary or
/// even untrusted data to this function and count on it not panicking
/// or using resources that aren't limited to a small constant factor of
/// the size of the data itself. That is, callers can reliably limit the
/// resources used by limiting the size of the data given to this parse
/// function.
pub(crate) fn parse(
name: Option<String>,
bytes: &[u8],
) -> Result<TzifOwned, TzifError> {
let original = bytes;
let name = name.into();
let (header32, rest) =
Header::parse(4, bytes).map_err(TzifErrorKind::Header32)?;
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<String>,
header32: Header,
bytes: &'b [u8],
) -> Result<(TzifOwned, &'b [u8]), TzifError> {
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<String>,
header32: Header,
bytes: &'b [u8],
) -> Result<(TzifOwned, &'b [u8]), TzifError> {
let (_, rest) =
try_split_at(SplitAtError::V1, bytes, header32.data_block_len()?)?;
let (header64, rest) =
Header::parse(8, rest).map_err(TzifErrorKind::Header64)?;
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], TzifError> {
let (bytes, rest) = try_split_at(
SplitAtError::TransitionTimes,
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);
// only-jiff-start
warn!(
"found Unix timestamp `{timestamp}` that is outside \
Jiff's supported range, clamping to `{clamped}`",
);
// only-jiff-end
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], TransitionTypeError> {
let (bytes, rest) = try_split_at(
SplitAtError::TransitionTypes,
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(TransitionTypeError::ExceedsLocalTimeTypes);
}
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], TzifError> {
let (bytes, rest) = try_split_at(
SplitAtError::LocalTimeTypes,
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(TzifError::from(
LocalTimeTypeError::InvalidOffset { 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], TimeZoneDesignatorError> {
let (bytes, rest) = try_split_at(
SplitAtError::TimeZoneDesignations,
bytes,
header.time_zone_designations_len(),
)?;
self.fixed.designations = String::from_utf8(bytes.to_vec())
.map_err(|_| TimeZoneDesignatorError::InvalidUtf8)?;
// Holy hell, this is brutal. The boundary conditions are crazy.
for typ in self.types.iter_mut() {
let start = usize::from(typ.designation.0);
let suffix = self
.fixed
.designations
.get(start..)
.ok_or(TimeZoneDesignatorError::InvalidStart)?;
let len = suffix
.find('\x00')
.ok_or(TimeZoneDesignatorError::MissingNul)?;
let end = start
.checked_add(len)
.ok_or(TimeZoneDesignatorError::InvalidLength)?;
typ.designation.1 = u8::try_from(end)
.map_err(|_| TimeZoneDesignatorError::InvalidEnd)?;
}
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], TzifError> {
let (bytes, rest) = try_split_at(
SplitAtError::LeapSeconds,
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) {
// only-jiff-start
warn!(
"leap second occurrence `{occur}` is \
not in Jiff's supported range"
)
// only-jiff-end
}
}
assert!(it.remainder().is_empty());
Ok(rest)
}
fn parse_indicators<'b>(
&mut self,
header: &Header,
bytes: &'b [u8],
) -> Result<&'b [u8], IndicatorError> {
let (std_wall_bytes, rest) = try_split_at(
SplitAtError::StandardWallIndicators,
bytes,
header.standard_wall_len(),
)?;
let (ut_local_bytes, rest) = try_split_at(
SplitAtError::UTLocalIndicators,
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.
if ut_local_bytes.iter().any(|&byte| byte != 0) {
return Err(IndicatorError::UtLocalNonZero);
}
} 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(IndicatorError::InvalidStdWallIndicator);
};
}
} 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(IndicatorError::InvalidUtWallCombination);
}
_ => return Err(IndicatorError::InvalidCombination),
};
}
} 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], FooterError> {
if bytes.is_empty() {
return Err(FooterError::UnexpectedEnd);
}
if bytes[0] != b'\n' {
return Err(FooterError::MismatchEnd);
}
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 nlat = toscan
.iter()
.position(|&b| b == b'\n')
.ok_or(FooterError::TerminatorNotFound)?;
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 GNU tooling allows 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(FooterError::InvalidPosixTz)?;
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<(), InconsistentPosixTimeZoneError> {
// 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(InconsistentPosixTimeZoneError::Offset);
}
if is_dst != typ.is_dst {
return Err(InconsistentPosixTimeZoneError::Dst);
}
if abbrev != self.designation(&typ) {
return Err(InconsistentPosixTimeZoneError::Designation);
}
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 {
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;
// Special case the first transition, which is always a dummy
// transition establishing the lower bound. The loop below doesn't
// handle this correctly because it isn't guaranteed to get a
// `DateTime::MIN` value.
trans.infos[0].kind = TzifTransitionKind::Unambiguous;
trans.civil_starts[0] = TzifDateTime::MIN;
for i in 1..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 {
// only-jiff-start
warn!(
"fattening TZif data for `{name:?}` somehow generated \
more than {max} transitions, so giving up to avoid \
doing too much work",
name = self.fixed.name,
max = FATTEN_MAX_TRANSITIONS,
);
// only-jiff-end
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<Abbreviation>,
prev: ITimestamp,
) -> Option<ITimestamp> {
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<u8> {
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]), HeaderError> {
assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8");
if bytes.len() < 44 {
return Err(HeaderError::TooShort);
}
let (magic, rest) = bytes.split_at(4);
if magic != b"TZif" {
return Err(HeaderError::MismatchMagic);
}
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| {
HeaderError::ParseCount { kind: CountKind::Ut, convert: e }
})?;
let tzh_ttisstdcnt =
from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes).map_err(|e| {
HeaderError::ParseCount { kind: CountKind::Std, convert: e }
})?;
let tzh_leapcnt =
from_be_bytes_u32_to_usize(tzh_leapcnt_bytes).map_err(|e| {
HeaderError::ParseCount { kind: CountKind::Leap, convert: e }
})?;
let tzh_timecnt =
from_be_bytes_u32_to_usize(tzh_timecnt_bytes).map_err(|e| {
HeaderError::ParseCount { kind: CountKind::Time, convert: e }
})?;
let tzh_typecnt =
from_be_bytes_u32_to_usize(tzh_typecnt_bytes).map_err(|e| {
HeaderError::ParseCount { kind: CountKind::Type, convert: e }
})?;
let tzh_charcnt =
from_be_bytes_u32_to_usize(tzh_charcnt_bytes).map_err(|e| {
HeaderError::ParseCount { kind: CountKind::Char, convert: e }
})?;
if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt {
return Err(HeaderError::MismatchUtType);
}
if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt {
return Err(HeaderError::MismatchStdType);
}
if tzh_typecnt < 1 {
return Err(HeaderError::ZeroType);
}
if tzh_charcnt < 1 {
return Err(HeaderError::ZeroChar);
}
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<usize, HeaderError> {
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(HeaderError::InvalidDataBlock { version: self.version })
}
fn transition_times_len(&self) -> Result<usize, HeaderError> {
self.tzh_timecnt
.checked_mul(self.time_size)
.ok_or(HeaderError::InvalidTimeCount)
}
fn transition_types_len(&self) -> usize {
self.tzh_timecnt
}
fn local_time_types_len(&self) -> Result<usize, HeaderError> {
self.tzh_typecnt.checked_mul(6).ok_or(HeaderError::InvalidTypeCount)
}
fn time_zone_designations_len(&self) -> usize {
self.tzh_charcnt
}
fn leap_second_len(&self) -> Result<usize, HeaderError> {
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(HeaderError::InvalidLeapSecondCount)
}
fn standard_wall_len(&self) -> usize {
self.tzh_ttisstdcnt
}
fn ut_local_len(&self) -> usize {
self.tzh_ttisutcnt
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct TzifError {
kind: TzifErrorKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum TzifErrorKind {
Footer(FooterError),
Header(HeaderError),
Header32(HeaderError),
Header64(HeaderError),
InconsistentPosixTimeZone(InconsistentPosixTimeZoneError),
Indicator(IndicatorError),
LocalTimeType(LocalTimeTypeError),
SplitAt(SplitAtError),
TimeZoneDesignator(TimeZoneDesignatorError),
TransitionType(TransitionTypeError),
}
#[cfg(feature = "std")]
impl std::error::Error for TzifError {}
impl core::fmt::Display for TzifError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::TzifErrorKind::*;
match self.kind {
Footer(ref err) => {
f.write_str("invalid TZif footer: ")?;
err.fmt(f)
}
Header(ref err) => {
f.write_str("invalid TZif header: ")?;
err.fmt(f)
}
Header32(ref err) => {
f.write_str("invalid 32-bit TZif header: ")?;
err.fmt(f)
}
Header64(ref err) => {
f.write_str("invalid 64-bit TZif header: ")?;
err.fmt(f)
}
InconsistentPosixTimeZone(ref err) => {
f.write_str(
"found inconsistency with \
POSIX time zone transition rule \
in TZif file footer: ",
)?;
err.fmt(f)
}
Indicator(ref err) => {
f.write_str("failed to parse indicators: ")?;
err.fmt(f)
}
LocalTimeType(ref err) => {
f.write_str("failed to parse local time types: ")?;
err.fmt(f)
}
SplitAt(ref err) => err.fmt(f),
TimeZoneDesignator(ref err) => {
f.write_str("failed to parse time zone designators: ")?;
err.fmt(f)
}
TransitionType(ref err) => {
f.write_str("failed to parse time zone transition types: ")?;
err.fmt(f)
}
}
}
}
impl From<TzifErrorKind> for TzifError {
fn from(kind: TzifErrorKind) -> TzifError {
TzifError { kind }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum TransitionTypeError {
ExceedsLocalTimeTypes,
Split(SplitAtError),
}
impl From<TransitionTypeError> for TzifError {
fn from(err: TransitionTypeError) -> TzifError {
TzifErrorKind::TransitionType(err).into()
}
}
impl From<SplitAtError> for TransitionTypeError {
fn from(err: SplitAtError) -> TransitionTypeError {
TransitionTypeError::Split(err)
}
}
impl core::fmt::Display for TransitionTypeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::TransitionTypeError::*;
match *self {
ExceedsLocalTimeTypes => f.write_str(
"found time zone transition type index \
that exceeds the number of local time types",
),
Split(ref err) => err.fmt(f),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum LocalTimeTypeError {
InvalidOffset { offset: i32 },
Split(SplitAtError),
}
impl From<LocalTimeTypeError> for TzifError {
fn from(err: LocalTimeTypeError) -> TzifError {
TzifErrorKind::LocalTimeType(err).into()
}
}
impl From<SplitAtError> for LocalTimeTypeError {
fn from(err: SplitAtError) -> LocalTimeTypeError {
LocalTimeTypeError::Split(err)
}
}
impl core::fmt::Display for LocalTimeTypeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::LocalTimeTypeError::*;
match *self {
InvalidOffset { offset } => write!(
f,
"found local time type with \
out-of-bounds time zone offset: {offset}, \
Jiff's allowed range is `{OFFSET_MIN}..={OFFSET_MAX}`"
),
Split(ref err) => err.fmt(f),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum TimeZoneDesignatorError {
InvalidEnd,
InvalidLength,
InvalidStart,
InvalidUtf8,
MissingNul,
Split(SplitAtError),
}
impl From<TimeZoneDesignatorError> for TzifError {
fn from(err: TimeZoneDesignatorError) -> TzifError {
TzifErrorKind::TimeZoneDesignator(err).into()
}
}
impl From<SplitAtError> for TimeZoneDesignatorError {
fn from(err: SplitAtError) -> TimeZoneDesignatorError {
TimeZoneDesignatorError::Split(err)
}
}
impl core::fmt::Display for TimeZoneDesignatorError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::TimeZoneDesignatorError::*;
match *self {
InvalidEnd => f.write_str(
"found invalid end of time zone designator \
for local time type",
),
InvalidLength => f.write_str(
"found invalid length of time zone designator \
for local time type",
),
InvalidStart => f.write_str(
"found invalid start of time zone designator \
for local time type",
),
InvalidUtf8 => {
f.write_str("found invalid UTF-8 in time zone designators")
}
MissingNul => f.write_str(
"could not find NUL terminator for time zone designator",
),
Split(ref err) => err.fmt(f),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum IndicatorError {
InvalidCombination,
InvalidStdWallIndicator,
InvalidUtWallCombination,
Split(SplitAtError),
UtLocalNonZero,
}
impl From<IndicatorError> for TzifError {
fn from(err: IndicatorError) -> TzifError {
TzifErrorKind::Indicator(err).into()
}
}
impl From<SplitAtError> for IndicatorError {
fn from(err: SplitAtError) -> IndicatorError {
IndicatorError::Split(err)
}
}
impl core::fmt::Display for IndicatorError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::IndicatorError::*;
match *self {
InvalidCombination => f.write_str(
"found invalid std/wall or UT/local value for \
local time type, each must be 0 or 1",
),
InvalidStdWallIndicator => f.write_str(
"found invalid std/wall indicator, \
expected it to be 0 or 1",
),
InvalidUtWallCombination => f.write_str(
"found invalid UT-wall combination for \
local time type, only local-wall, \
local-standard and UT-standard are allowed",
),
Split(ref err) => err.fmt(f),
UtLocalNonZero => f.write_str(
"found non-zero UT/local indicator, \
but all such indicators should be zero",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum InconsistentPosixTimeZoneError {
Designation,
Dst,
Offset,
}
impl From<InconsistentPosixTimeZoneError> for TzifError {
fn from(err: InconsistentPosixTimeZoneError) -> TzifError {
TzifErrorKind::InconsistentPosixTimeZone(err).into()
}
}
impl core::fmt::Display for InconsistentPosixTimeZoneError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::InconsistentPosixTimeZoneError::*;
match *self {
Designation => f.write_str(
"expected last transition in TZif file to have \
a time zone abbreviation matching the abbreviation \
derived from the POSIX time zone transition rule",
),
Dst => f.write_str(
"expected last transition in TZif file to have \
a DST status matching the status derived from the \
POSIX time zone transition rule",
),
Offset => f.write_str(
"expected last transition in TZif file to have \
DST offset matching the offset derived from the \
POSIX time zone transition rule",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum FooterError {
InvalidPosixTz(crate::shared::posix::PosixTimeZoneError),
MismatchEnd,
TerminatorNotFound,
UnexpectedEnd,
}
impl From<FooterError> for TzifError {
fn from(err: FooterError) -> TzifError {
TzifErrorKind::Footer(err).into()
}
}
impl core::fmt::Display for FooterError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::FooterError::*;
match *self {
InvalidPosixTz(ref err) => {
f.write_str("invalid POSIX time zone transition rule")?;
core::fmt::Display::fmt(err, f)
}
MismatchEnd => f.write_str(
"expected to find `\\n` at the beginning of \
the TZif file footer, \
but found something else instead",
),
TerminatorNotFound => f.write_str(
"expected to find `\\n` terminating \
the TZif file footer, \
but no line terminator could be found",
),
UnexpectedEnd => f.write_str(
"expected to find `\\n` at the beginning of \
the TZif file footer, \
but found unexpected end of data",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum HeaderError {
InvalidDataBlock { version: u8 },
InvalidLeapSecondCount,
InvalidTimeCount,
InvalidTypeCount,
MismatchMagic,
MismatchStdType,
MismatchUtType,
ParseCount { kind: CountKind, convert: U32UsizeError },
TooShort,
ZeroChar,
ZeroType,
}
impl From<HeaderError> for TzifError {
fn from(err: HeaderError) -> TzifError {
TzifErrorKind::Header(err).into()
}
}
impl core::fmt::Display for HeaderError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::HeaderError::*;
match *self {
InvalidDataBlock { version } => write!(
f,
"length of data block in V{version} TZif file is too big",
),
InvalidLeapSecondCount => {
f.write_str("number of leap seconds is too big")
}
InvalidTimeCount => {
f.write_str("number of transition times is too big")
}
InvalidTypeCount => {
f.write_str("number of local time types is too big")
}
MismatchMagic => f.write_str("magic bytes mismatch"),
MismatchStdType => f.write_str(
"expected number of standard/wall indicators to be zero \
or equal to the number of local time types",
),
MismatchUtType => f.write_str(
"expected number of UT/local indicators to be zero \
or equal to the number of local time types",
),
ParseCount { ref kind, ref convert } => {
write!(f, "failed to parse `{kind}`: {convert}")
}
TooShort => f.write_str("too short"),
ZeroChar => f.write_str(
"expected number of time zone abbreviations fo be at least 1",
),
ZeroType => f.write_str(
"expected number of local time types fo be at least 1",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum CountKind {
Ut,
Std,
Leap,
Time,
Type,
Char,
}
impl core::fmt::Display for CountKind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::CountKind::*;
match *self {
Ut => f.write_str("tzh_ttisutcnt"),
Std => f.write_str("tzh_ttisstdcnt"),
Leap => f.write_str("tzh_leapcnt"),
Time => f.write_str("tzh_timecnt"),
Type => f.write_str("tzh_typecnt"),
Char => f.write_str("tzh_charcnt"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) enum SplitAtError {
V1,
LeapSeconds,
LocalTimeTypes,
StandardWallIndicators,
TimeZoneDesignations,
TransitionTimes,
TransitionTypes,
UTLocalIndicators,
}
impl From<SplitAtError> for TzifError {
fn from(err: SplitAtError) -> TzifError {
TzifErrorKind::SplitAt(err).into()
}
}
impl core::fmt::Display for SplitAtError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::SplitAtError::*;
f.write_str("expected bytes for '")?;
f.write_str(match *self {
V1 => "v1 TZif",
LeapSeconds => "leap seconds",
LocalTimeTypes => "local time types",
StandardWallIndicators => "standard/wall indicators",
TimeZoneDesignations => "time zone designations",
TransitionTimes => "transition times",
TransitionTypes => "transition types",
UTLocalIndicators => "UT/local indicators",
})?;
f.write_str("data block', but did not find enough bytes")?;
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct U32UsizeError;
impl core::fmt::Display for U32UsizeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"failed to parse integer because it is bigger than `{max}`",
max = usize::MAX,
)
}
}
/// 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: SplitAtError,
bytes: &'b [u8],
at: usize,
) -> Result<(&'b [u8], &'b [u8]), SplitAtError> {
if at > bytes.len() {
Err(what)
} 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<usize, U32UsizeError> {
let n = from_be_bytes_u32(bytes);
usize::try_from(n).map_err(|_| U32UsizeError)
}
/// 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())
}
/// A simple and not the most-efficient fixed size string on the stack.
///
/// This supplanted some uses of `Box<str>` 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<const N: usize> {
/// 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<const N: usize> ArrayStr<N> {
/// 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<ArrayStr<N>> {
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) const 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<const N: usize> From<&'static str> for ArrayStr<N> {
fn from(s: &'static str) -> ArrayStr<N> {
ArrayStr::new(s).unwrap()
}
}
impl<const N: usize> PartialEq<str> for ArrayStr<N> {
fn eq(&self, rhs: &str) -> bool {
self.as_str() == rhs
}
}
impl<const N: usize> PartialEq<&str> for ArrayStr<N> {
fn eq(&self, rhs: &&str) -> bool {
self.as_str() == *rhs
}
}
impl<const N: usize> PartialEq<ArrayStr<N>> for str {
fn eq(&self, rhs: &ArrayStr<N>) -> bool {
self == rhs.as_str()
}
}
impl<const N: usize> core::fmt::Debug for ArrayStr<N> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Debug::fmt(self.as_str(), f)
}
}
impl<const N: usize> core::fmt::Display for ArrayStr<N> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self.as_str(), f)
}
}
impl<const N: usize> core::fmt::Write for ArrayStr<N> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.push_str(s) {
Ok(())
} else {
Err(core::fmt::Error)
}
}
}
#[cfg(feature = "defmt")]
impl<const N: usize> defmt::Format for ArrayStr<N> {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{=str}", self.as_str())
}
}
impl<const N: usize> AsRef<str> for ArrayStr<N> {
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<str>`.
/// 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<ABBREVIATION_MAX>;
#[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());
}
}
/*!
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`.
*/
#[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;
// Shift second comfortably into the postive domain
// so that division and remainder can use unsigned math
// which is much faster.
// 30 * 400 years: 12,000 yr range > [-9,999..1970]
// (146097 being the number of days per 400 years).
const DAY_SHIFT: i32 = 30 * 146097;
const SEC_SHIFT: i64 = (DAY_SHIFT as i64) * 86_400;
let pos_sec = (second + (offset.second as i64) + SEC_SHIFT) as u64;
let mut epoch_day = (pos_sec / 86_400) as i32;
second = (pos_sec % 86_400) as i64;
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;
}
}
epoch_day -= DAY_SHIFT;
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<ITimestamp> {
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<IDateTime, RangeError> {
let day_second = self
.time
.to_second()
.second
.checked_add(seconds)
.ok_or_else(|| RangeError::DateTimeSeconds)?;
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 {
pub(crate) const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 };
pub(crate) 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: <https://github.com/cassioneri/eaf/blob/684d3cc32d14eee371d0abe4f683d6d6a49ed5c1/algorithms/neri_schneider.hpp#L40C3-L40C34>
#[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 {
// Fast technique to obtain weekday 1..7 (Mon..Sun)
// directly via an add-mul-shift. Relies on the fact
// that the Unix epoch was a Thursday and that 7 is a
// Mersenne number.
//
// Accurate over a limited range (-89478489 to 89478489)
// which is -243014-03-21 (Tue) to 246953-10-13 (Sat).
// This exceeds Jiff's range.
//
// Ref: https://www.benjoffe.com/fast-day-of-week
IWeekday::from_monday_one_offset({
const M: u32 = {
// MSRV(1.73): Just use `n.div_ceil` instead.
const fn div_ceil(lhs: u64, rhs: u64) -> u64 {
let d = lhs / rhs;
let r = lhs % rhs;
if r > 0 {
d + 1
} else {
d
}
}
let n = div_ceil(1u64 << 32, 7) as u32;
assert!(n == 613_566_757);
n
};
const Z: u32 = 0x90000000; // Magic add: see link above.
let rd: u32 = self.epoch_day as u32;
(rd.wrapping_mul(M).wrapping_add(Z) >> 29) 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<IEpochDay, RangeError> {
let epoch_day = self.epoch_day;
let sum = epoch_day
.checked_add(amount)
.ok_or_else(|| RangeError::EpochDayI32)?;
let ret = IEpochDay { epoch_day: sum };
if !(IEpochDay::MIN <= ret && ret <= IEpochDay::MAX) {
return Err(RangeError::EpochDayDays);
}
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<IDate, RangeError> {
if day > 28 {
let max_day = days_in_month(year, month);
if day > max_day {
return Err(RangeError::DateInvalidDays { year, month });
}
}
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<IDate, RangeError> {
if !(1 <= day && day <= 366) {
return Err(RangeError::DateInvalidDayOfYear { year });
}
let start = IDate { year, month: 1, day: 1 }.to_epoch_day();
let end = start
.checked_add(i32::from(day) - 1)
// This can only happen when `year=9999` and `day=366`.
.map_err(|_| RangeError::DayOfYear)?
.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(RangeError::DateInvalidDayOfYear { 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<IDate, RangeError> {
if !(1 <= day && day <= 365) {
return Err(RangeError::DateInvalidDayOfYearNoLeap);
}
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<IDate, RangeError> {
if nth == 0 || !(-5 <= nth && nth <= 5) {
return Err(RangeError::NthWeekdayOfMonth);
}
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(RangeError::DateInvalidDays {
year: self.year,
month: self.month,
});
}
IDate::try_new(self.year, self.month, day)
}
}
/// Returns the day before this date.
#[inline]
pub(crate) fn yesterday(self) -> Result<IDate, RangeError> {
if self.day == 1 {
if self.month == 1 {
let year = self.year - 1;
if year <= -10000 {
return Err(RangeError::Yesterday);
}
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<IDate, RangeError> {
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(RangeError::Tomorrow);
}
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<i16, RangeError> {
let year = self.year - 1;
if year <= -10_000 {
return Err(RangeError::YearPrevious);
}
Ok(year)
}
/// Returns the year one year from this date.
#[inline]
pub(crate) fn next_year(self) -> Result<i16, RangeError> {
let year = self.year + 1;
if year >= 10_000 {
return Err(RangeError::YearNext);
}
Ok(year)
}
/// Add the number of days to this date.
#[inline]
pub(crate) fn checked_add_days(
&self,
amount: i32,
) -> Result<IDate, RangeError> {
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..=7` 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 },
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) enum RangeError {
DateInvalidDayOfYear { year: i16 },
DateInvalidDayOfYearNoLeap,
DateInvalidDays { year: i16, month: i8 },
DateTimeSeconds,
DayOfYear,
EpochDayDays,
EpochDayI32,
NthWeekdayOfMonth,
Tomorrow,
YearNext,
YearPrevious,
Yesterday,
}
impl core::fmt::Display for RangeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::RangeError::*;
match *self {
DateInvalidDayOfYear { year } => write!(
f,
"number of days for `{year:04}` is invalid, \
must be in range `1..={max_day}`",
max_day = days_in_year(year),
),
DateInvalidDayOfYearNoLeap => f.write_str(
"number of days is invalid, must be in range `1..=365`",
),
DateInvalidDays { year, month } => write!(
f,
"parameter 'day' for `{year:04}-{month:02}` is invalid, \
must be in range `1..={max_day}`",
max_day = days_in_month(year, month),
),
DateTimeSeconds => {
f.write_str("adding seconds to datetime overflowed")
}
DayOfYear => f.write_str("day of year is invalid"),
EpochDayDays => write!(
f,
"adding to epoch day resulted in a value outside \
the allowed range of `{min}..={max}`",
min = IEpochDay::MIN.epoch_day,
max = IEpochDay::MAX.epoch_day,
),
EpochDayI32 => f.write_str(
"adding to epoch day overflowed 32-bit signed integer",
),
NthWeekdayOfMonth => f.write_str(
"invalid nth weekday of month, \
must be non-zero and in range `-5..=5`",
),
Tomorrow => f.write_str(
"returning tomorrow for `9999-12-31` is not \
possible because it is greater than Jiff's supported
maximum date",
),
YearNext => f.write_str(
"creating a date for a year following `9999` is \
not possible because it is greater than Jiff's supported \
maximum date",
),
YearPrevious => f.write_str(
"creating a date for a year preceding `-9999` is \
not possible because it is less than Jiff's supported \
minimum date",
),
Yesterday => f.write_str(
"returning yesterday for `-9999-01-01` is not \
possible because it is less than Jiff's supported
minimum date",
),
}
}
}
/// 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);
}
#[test]
fn from_day_of_year() {
assert_eq!(
IDate::from_day_of_year(9999, 365),
Ok(IDate { year: 9999, month: 12, day: 31 }),
);
assert_eq!(
IDate::from_day_of_year(9998, 366),
Err(RangeError::DateInvalidDayOfYear { year: 9998 }),
);
assert_eq!(
IDate::from_day_of_year(9999, 366),
Err(RangeError::DayOfYear),
);
}
#[test]
fn timestamp_to_datetime() {
let ts = ITimestamp { second: 0, nanosecond: 1 };
let dt = ts.to_datetime(IOffset { second: 1 });
assert_eq!(
dt,
IDateTime {
date: IDate { year: 1970, month: 1, day: 1 },
time: ITime {
hour: 0,
minute: 0,
second: 1,
subsec_nanosecond: 1
},
}
);
let ts = ITimestamp { second: 0, nanosecond: 1 };
let dt = ts.to_datetime(IOffset { second: -1 });
assert_eq!(
dt,
IDateTime {
date: IDate { year: 1969, month: 12, day: 31 },
time: ITime {
hour: 23,
minute: 59,
second: 59,
subsec_nanosecond: 1,
},
}
);
let ts = ITimestamp { second: 0, nanosecond: -1 };
let dt = ts.to_datetime(IOffset { second: 1 });
assert_eq!(
dt,
IDateTime {
date: IDate { year: 1970, month: 1, day: 1 },
time: ITime {
hour: 0,
minute: 0,
second: 0,
subsec_nanosecond: 999_999_999
},
}
);
let ts = ITimestamp { second: 0, nanosecond: -1 };
let dt = ts.to_datetime(IOffset { second: -1 });
assert_eq!(
dt,
IDateTime {
date: IDate { year: 1969, month: 12, day: 31 },
time: ITime {
hour: 23,
minute: 59,
second: 58,
subsec_nanosecond: 999_999_999,
},
}
);
}
}
pub(crate) mod array_str;
pub(crate) mod itime;
// Re-exported here for convenience.
//
// It used to be defined in this module, but moved to `shared` so that it could
// be used in the POSIX time zone parser.
pub(crate) use crate::shared::util::array_str::*;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display