Repository: chronotope/chrono (language: rust)

Issue:
Inconsistent crate features documentation
PR #1343 added a new `now` feature, however, this feature is only documented in `README.md` and not in the features list on `docs.rs`.

Compare README.md:
https://github.com/chronotope/chrono/blob/3016c30014064bd1c67d285bd23c9e232b9efb80/README.md?plain=1#L49-L67

With the crate rustdocs:
https://github.com/chronotope/chrono/blob/3016c30014064bd1c67d285bd23c9e232b9efb80/src/lib.rs#L20-L44

I see there are also some differences in the other features listed under "optional features" too.


Discussion / hints:
@edmorley Thank you for reporting this. Would you be interested in making a PR to straighten it out?
We should also document or at least mist the extra `rkyv` features.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 10901ff080..1e5182d961 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,7 +51,7 @@ js-sys = { version = "0.3", optional = true }       # contains FFI bindings for
 windows-targets = { version = "0.52", optional = true }
 
 [target.'cfg(windows)'.dev-dependencies]
-windows-bindgen = { version = "0.52" }
+windows-bindgen = { version = "0.53" } # The MSRV of its windows-metatada 0.53 dependency is 1.70
 
 [target.'cfg(unix)'.dependencies]
 iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] }
diff --git a/README.md b/README.md
index fbb96ae76d..64f7cf1591 100644
--- a/README.md
+++ b/README.md
@@ -50,21 +50,31 @@ See [docs.rs](https://docs.rs/chrono/latest/chrono/) for the API reference.
 
 Default features:
 
-* `alloc`: Enable features that depend on allocation (primarily string formatting)
+* `alloc`: Enable features that depend on allocation (primarily string formatting).
 * `std`: Enables functionality that depends on the standard library. This is a superset of `alloc`
   and adds interoperation with standard library types and traits.
 * `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`.
-* `now`: Enables reading the system time (`now`)
+* `now`: Enables reading the system time (`now`).
 * `wasmbind`: Interface with the JS Date API for the `wasm32` target.
 
 Optional features:
 
-* `serde`: Enable serialization/deserialization via serde.
-* `rkyv`: Enable serialization/deserialization via rkyv.
+* `serde`: Enable serialization/deserialization via [serde].
+* `rkyv`: Deprecated, use the `rkyv-*` features.
+* `rkyv-16`: Enable serialization/deserialization via [rkyv], using 16-bit integers for integral `*size` types.
+* `rkyv-32`: Enable serialization/deserialization via [rkyv], using 32-bit integers for integral `*size` types.
+* `rkyv-64`: Enable serialization/deserialization via [rkyv], using 64-bit integers for integral `*size` types.
+* `rkyv-validation`: Enable rkyv validation support using `bytecheck`.
 * `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated).
-* `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate.
+* `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate.
 * `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix.
   The implementation and API may change or even be removed in a patch release. Feedback welcome.
+* `oldtime`: This feature no longer has any effect; it used to offer compatibility with the `time` 0.1 crate.
+
+Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive.
+
+[serde]: https://github.com/serde-rs/serde
+[rkyv]: https://github.com/rkyv/rkyv
 
 ## Rust version requirements
 
diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs
index 753d44b3b7..22994b1f4f 100644
--- a/src/datetime/serde.rs
+++ b/src/datetime/serde.rs
@@ -3,7 +3,6 @@ use serde::{de, ser};
 
 use super::DateTime;
 use crate::format::{write_rfc3339, SecondsFormat};
-use crate::naive::datetime::serde::serde_from;
 #[cfg(feature = "clock")]
 use crate::offset::Local;
 use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
@@ -132,11 +131,9 @@ pub mod ts_nanoseconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use crate::offset::TimeZone;
+    use crate::serde::invalid_ts;
     use crate::{DateTime, Utc};
 
-    use super::serde_from;
-
     /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch
     ///
     /// Intended for use with `serde`s `serialize_with` attribute.
@@ -226,13 +223,11 @@ pub mod ts_nanoseconds {
         where
             E: de::Error,
         {
-            serde_from(
-                Utc.timestamp(
-                    value.div_euclid(1_000_000_000),
-                    (value.rem_euclid(1_000_000_000)) as u32,
-                ),
-                &value,
+            DateTime::from_timestamp(
+                value.div_euclid(1_000_000_000),
+                (value.rem_euclid(1_000_000_000)) as u32,
             )
+            .ok_or_else(|| invalid_ts(value))
         }
 
         /// Deserialize a timestamp in nanoseconds since the epoch
@@ -240,10 +235,8 @@ pub mod ts_nanoseconds {
         where
             E: de::Error,
         {
-            serde_from(
-                Utc.timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32),
-                &value,
-            )
+            DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32)
+                .ok_or_else(|| invalid_ts(value))
         }
     }
 }
@@ -434,8 +427,7 @@ pub mod ts_microseconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use super::serde_from;
-    use crate::offset::TimeZone;
+    use crate::serde::invalid_ts;
     use crate::{DateTime, Utc};
 
     /// Serialize a UTC datetime into an integer number of microseconds since the epoch
@@ -517,13 +509,11 @@ pub mod ts_microseconds {
         where
             E: de::Error,
         {
-            serde_from(
-                Utc.timestamp(
-                    value.div_euclid(1_000_000),
-                    (value.rem_euclid(1_000_000) * 1_000) as u32,
-                ),
-                &value,
+            DateTime::from_timestamp(
+                value.div_euclid(1_000_000),
+                (value.rem_euclid(1_000_000) * 1000) as u32,
             )
+            .ok_or_else(|| invalid_ts(value))
         }
 
         /// Deserialize a timestamp in milliseconds since the epoch
@@ -531,10 +521,11 @@ pub mod ts_microseconds {
         where
             E: de::Error,
         {
-            serde_from(
-                Utc.timestamp((value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32),
-                &value,
+            DateTime::from_timestamp(
+                (value / 1_000_000) as i64,
+                ((value % 1_000_000) * 1_000) as u32,
             )
+            .ok_or_else(|| invalid_ts(value))
         }
     }
 }
@@ -714,8 +705,7 @@ pub mod ts_milliseconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use super::serde_from;
-    use crate::offset::TimeZone;
+    use crate::serde::invalid_ts;
     use crate::{DateTime, Utc};
 
     /// Serialize a UTC datetime into an integer number of milliseconds since the epoch
@@ -797,7 +787,7 @@ pub mod ts_milliseconds {
         where
             E: de::Error,
         {
-            serde_from(Utc.timestamp_millis(value), &value)
+            DateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value))
         }
 
         /// Deserialize a timestamp in milliseconds since the epoch
@@ -805,10 +795,8 @@ pub mod ts_milliseconds {
         where
             E: de::Error,
         {
-            serde_from(
-                Utc.timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32),
-                &value,
-            )
+            DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32)
+                .ok_or_else(|| invalid_ts(value))
         }
     }
 }
@@ -995,8 +983,8 @@ pub mod ts_seconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use super::serde_from;
-    use crate::{DateTime, LocalResult, TimeZone, Utc};
+    use crate::serde::invalid_ts;
+    use crate::{DateTime, Utc};
 
     /// Serialize a UTC datetime into an integer number of seconds since the epoch
     ///
@@ -1067,7 +1055,7 @@ pub mod ts_seconds {
         where
             E: de::Error,
         {
-            serde_from(Utc.timestamp(value, 0), &value)
+            DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value))
         }
 
         /// Deserialize a timestamp in seconds since the epoch
@@ -1075,14 +1063,11 @@ pub mod ts_seconds {
         where
             E: de::Error,
         {
-            serde_from(
-                if value > i64::MAX as u64 {
-                    LocalResult::None
-                } else {
-                    Utc.timestamp(value as i64, 0)
-                },
-                &value,
-            )
+            if value > i64::MAX as u64 {
+                Err(invalid_ts(value))
+            } else {
+                DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value))
+            }
         }
     }
 }
diff --git a/src/format/parse.rs b/src/format/parse.rs
index 9b932dc576..cedd2d01e4 100644
--- a/src/format/parse.rs
+++ b/src/format/parse.rs
@@ -254,7 +254,7 @@ where
     match parse_internal(parsed, s, items) {
         Ok("") => Ok(()),
         Ok(_) => Err(TOO_LONG), // if there are trailing chars it is an error
-        Err((_, e)) => Err(e),
+        Err(e) => Err(e),
     }
 }
 
@@ -281,14 +281,14 @@ where
     I: Iterator<Item = B>,
     B: Borrow<Item<'a>>,
 {
-    parse_internal(parsed, s, items).map_err(|(_s, e)| e)
+    parse_internal(parsed, s, items)
 }
 
 fn parse_internal<'a, 'b, I, B>(
     parsed: &mut Parsed,
     mut s: &'b str,
     items: I,
-) -> Result<&'b str, (&'b str, ParseError)>
+) -> Result<&'b str, ParseError>
 where
     I: Iterator<Item = B>,
     B: Borrow<Item<'a>>,
@@ -300,7 +300,7 @@ where
                     s = s_;
                     v
                 }
-                Err(e) => return Err((s, e)),
+                Err(e) => return Err(e),
             }
         }};
     }
@@ -309,10 +309,10 @@ where
         match *item.borrow() {
             Item::Literal(prefix) => {
                 if s.len() < prefix.len() {
-                    return Err((s, TOO_SHORT));
+                    return Err(TOO_SHORT);
                 }
                 if !s.starts_with(prefix) {
-                    return Err((s, INVALID));
+                    return Err(INVALID);
                 }
                 s = &s[prefix.len()..];
             }
@@ -320,10 +320,10 @@ where
             #[cfg(feature = "alloc")]
             Item::OwnedLiteral(ref prefix) => {
                 if s.len() < prefix.len() {
-                    return Err((s, TOO_SHORT));
+                    return Err(TOO_SHORT);
                 }
                 if !s.starts_with(&prefix[..]) {
-                    return Err((s, INVALID));
+                    return Err(INVALID);
                 }
                 s = &s[prefix.len()..];
             }
@@ -371,7 +371,7 @@ where
                 let v = if signed {
                     if s.starts_with('-') {
                         let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
-                        0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))?
+                        0i64.checked_sub(v).ok_or(OUT_OF_RANGE)?
                     } else if s.starts_with('+') {
                         try_consume!(scan::number(&s[1..], 1, usize::MAX))
                     } else {
@@ -381,7 +381,7 @@ where
                 } else {
                     try_consume!(scan::number(s, 1, width))
                 };
-                set(parsed, v).map_err(|e| (s, e))?;
+                set(parsed, v)?;
             }
 
             Item::Fixed(ref spec) => {
@@ -390,66 +390,66 @@ where
                 match spec {
                     &ShortMonthName => {
                         let month0 = try_consume!(scan::short_month0(s));
-                        parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
+                        parsed.set_month(i64::from(month0) + 1)?;
                     }
 
                     &LongMonthName => {
                         let month0 = try_consume!(scan::short_or_long_month0(s));
-                        parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
+                        parsed.set_month(i64::from(month0) + 1)?;
                     }
 
                     &ShortWeekdayName => {
                         let weekday = try_consume!(scan::short_weekday(s));
-                        parsed.set_weekday(weekday).map_err(|e| (s, e))?;
+                        parsed.set_weekday(weekday)?;
                     }
 
                     &LongWeekdayName => {
                         let weekday = try_consume!(scan::short_or_long_weekday(s));
-                        parsed.set_weekday(weekday).map_err(|e| (s, e))?;
+                        parsed.set_weekday(weekday)?;
                     }
 
                     &LowerAmPm | &UpperAmPm => {
                         if s.len() < 2 {
-                            return Err((s, TOO_SHORT));
+                            return Err(TOO_SHORT);
                         }
                         let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) {
                             (b'a', b'm') => false,
                             (b'p', b'm') => true,
-                            _ => return Err((s, INVALID)),
+                            _ => return Err(INVALID),
                         };
-                        parsed.set_ampm(ampm).map_err(|e| (s, e))?;
+                        parsed.set_ampm(ampm)?;
                         s = &s[2..];
                     }
 
                     &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => {
                         if s.starts_with('.') {
                             let nano = try_consume!(scan::nanosecond(&s[1..]));
-                            parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+                            parsed.set_nanosecond(nano)?;
                         }
                     }
 
                     &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
                         if s.len() < 3 {
-                            return Err((s, TOO_SHORT));
+                            return Err(TOO_SHORT);
                         }
                         let nano = try_consume!(scan::nanosecond_fixed(s, 3));
-                        parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+                        parsed.set_nanosecond(nano)?;
                     }
 
                     &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
                         if s.len() < 6 {
-                            return Err((s, TOO_SHORT));
+                            return Err(TOO_SHORT);
                         }
                         let nano = try_consume!(scan::nanosecond_fixed(s, 6));
-                        parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+                        parsed.set_nanosecond(nano)?;
                     }
 
                     &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
                         if s.len() < 9 {
-                            return Err((s, TOO_SHORT));
+                            return Err(TOO_SHORT);
                         }
                         let nano = try_consume!(scan::nanosecond_fixed(s, 9));
-                        parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+                        parsed.set_nanosecond(nano)?;
                     }
 
                     &TimezoneName => {
@@ -467,7 +467,7 @@ where
                             false,
                             true,
                         ));
-                        parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
+                        parsed.set_offset(i64::from(offset))?;
                     }
 
                     &TimezoneOffsetColonZ | &TimezoneOffsetZ => {
@@ -478,7 +478,7 @@ where
                             false,
                             true,
                         ));
-                        parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
+                        parsed.set_offset(i64::from(offset))?;
                     }
                     &Internal(InternalFixed {
                         val: InternalInternal::TimezoneOffsetPermissive,
@@ -490,7 +490,7 @@ where
                             true,
                             true,
                         ));
-                        parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
+                        parsed.set_offset(i64::from(offset))?;
                     }
 
                     &RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
@@ -505,7 +505,7 @@ where
             }
 
             Item::Error => {
-                return Err((s, BAD_FORMAT));
+                return Err(BAD_FORMAT);
             }
         }
     }
@@ -567,7 +567,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult
         Item::Space(""),
     ];
 
-    s = parse_internal(parsed, s, DATE_ITEMS.iter()).map_err(|(_s, e)| e)?;
+    s = parse_internal(parsed, s, DATE_ITEMS.iter())?;
 
     s = match s.as_bytes().first() {
         Some(&b't' | &b'T' | &b' ') => &s[1..],
@@ -575,7 +575,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult
         None => return Err(TOO_SHORT),
     };
 
-    s = parse_internal(parsed, s, TIME_ITEMS.iter()).map_err(|(_s, e)| e)?;
+    s = parse_internal(parsed, s, TIME_ITEMS.iter())?;
     s = s.trim_start();
     let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) {
         (&s[3..], 0)
diff --git a/src/format/parsed.rs b/src/format/parsed.rs
index 0070b785b4..8480d0eb22 100644
--- a/src/format/parsed.rs
+++ b/src/format/parsed.rs
@@ -9,19 +9,123 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
 use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone};
 use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday};
 
-/// Parsed parts of date and time. There are two classes of methods:
+/// A type to hold parsed fields of date and time that can check all fields are consistent.
 ///
-/// - `set_*` methods try to set given field(s) while checking for the consistency.
-///   It may or may not check for the range constraint immediately (for efficiency reasons).
+/// There are two classes of methods:
+///
+/// - `set_*` methods to set fields you have available. They do a basic range check, and if the
+///   same field is set more than once it is checked for consistency.
 ///
 /// - `to_*` methods try to make a concrete date and time value out of set fields.
-///   It fully checks any remaining out-of-range conditions and inconsistent/impossible fields.
+///   They fully check that all fields are consistent and whether the date/datetime exists.
+///
+/// `Parsed` is used internally by all parsing functions in chrono. It is a public type so that it
+/// can be used to write custom parsers that reuse the resolving algorithm, or to inspect the
+/// results of a string parsed with chrono without converting it to concrete types.
+///
+/// # Resolving algorithm
+///
+/// Resolving date/time parts is littered with lots of corner cases, which is why common date/time
+/// parsers do not implement it correctly.
+///
+/// Chrono provides a complete resolution algorithm that checks all fields for consistency via the
+/// `Parsed` type.
+///
+/// As an easy example, consider RFC 2822. The [RFC 2822 date and time format] has a day of the week
+/// part, which should be consistent with the other date parts. But a `strptime`-based parse would
+/// happily accept inconsistent input:
+///
+/// ```python
+/// >>> import time
+/// >>> time.strptime('Wed, 31 Dec 2014 04:26:40 +0000',
+///                   '%a, %d %b %Y %H:%M:%S +0000')
+/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31,
+///                  tm_hour=4, tm_min=26, tm_sec=40,
+///                  tm_wday=2, tm_yday=365, tm_isdst=-1)
+/// >>> time.strptime('Thu, 31 Dec 2014 04:26:40 +0000',
+///                   '%a, %d %b %Y %H:%M:%S +0000')
+/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31,
+///                  tm_hour=4, tm_min=26, tm_sec=40,
+///                  tm_wday=3, tm_yday=365, tm_isdst=-1)
+/// ```
+///
+/// [RFC 2822 date and time format]: https://tools.ietf.org/html/rfc2822#section-3.3
+///
+/// # Example
+///
+/// Let's see how `Parsed` correctly detects the second RFC 2822 string from before is inconsistent.
+///
+#[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
+#[cfg_attr(feature = "alloc", doc = "```rust")]
+/// use chrono::format::{ParseErrorKind, Parsed};
+/// use chrono::Weekday;
+///
+/// let mut parsed = Parsed::new();
+/// parsed.set_weekday(Weekday::Wed)?;
+/// parsed.set_day(31)?;
+/// parsed.set_month(12)?;
+/// parsed.set_year(2014)?;
+/// parsed.set_hour(4)?;
+/// parsed.set_minute(26)?;
+/// parsed.set_second(40)?;
+/// parsed.set_offset(0)?;
+/// let dt = parsed.to_datetime()?;
+/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000");
+///
+/// let mut parsed = Parsed::new();
+/// parsed.set_weekday(Weekday::Thu)?; // changed to the wrong day
+/// parsed.set_day(31)?;
+/// parsed.set_month(12)?;
+/// parsed.set_year(2014)?;
+/// parsed.set_hour(4)?;
+/// parsed.set_minute(26)?;
+/// parsed.set_second(40)?;
+/// parsed.set_offset(0)?;
+/// let result = parsed.to_datetime();
+///
+/// assert!(result.is_err());
+/// if let Err(error) = result {
+///     assert_eq!(error.kind(), ParseErrorKind::Impossible);
+/// }
+/// # Ok::<(), chrono::ParseError>(())
+/// ```
+///
+/// The same using chrono's build-in parser for RFC 2822 (the [RFC2822 formatting item]) and
+/// [`format::parse()`] showing how to inspect a field on failure.
+///
+/// [RFC2822 formatting item]: crate::format::Fixed::RFC2822
+/// [`format::parse()`]: crate::format::parse()
+///
+#[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
+#[cfg_attr(feature = "alloc", doc = "```rust")]
+/// use chrono::format::{parse, Fixed, Item, Parsed};
+/// use chrono::Weekday;
+///
+/// let rfc_2822 = [Item::Fixed(Fixed::RFC2822)];
+///
+/// let mut parsed = Parsed::new();
+/// parse(&mut parsed, "Wed, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?;
+/// let dt = parsed.to_datetime()?;
+///
+/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000");
+///
+/// let mut parsed = Parsed::new();
+/// parse(&mut parsed, "Thu, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?;
+/// let result = parsed.to_datetime();
+///
+/// assert!(result.is_err());
+/// if result.is_err() {
+///     // What is the weekday?
+///     assert_eq!(parsed.weekday, Some(Weekday::Thu));
+/// }
+/// # Ok::<(), chrono::ParseError>(())
+/// ```
 #[non_exhaustive]
 #[derive(Clone, PartialEq, Eq, Debug, Default, Hash)]
 pub struct Parsed {
     /// Year.
     ///
-    /// This can be negative unlike [`year_div_100`](#structfield.year_div_100)
+    /// This can be negative, unlike [`year_div_100`](#structfield.year_div_100)
     /// and [`year_mod_100`](#structfield.year_mod_100) fields.
     pub year: Option<i32>,
 
@@ -37,7 +141,7 @@ pub struct Parsed {
 
     /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date).
     ///
-    /// This can be negative unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and
+    /// This can be negative, unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and
     /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields.
     pub isoyear: Option<i32>,
 
@@ -124,13 +228,27 @@ impl Parsed {
         Parsed::default()
     }
 
-    /// Tries to set the [`year`](#structfield.year) field from given value.
+    /// Set the 'year' field to the given value.
+    ///
+    /// The value can be negative, unlike the 'year divided by 100' and 'year modulo 100' fields.
+    ///
+    /// # Errors
+    ///
+    /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_year(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value.
+    /// Set the 'year divided by 100' field to the given value.
+    ///
+    /// # Errors
+    ///
+    /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> {
         if value < 0 {
@@ -139,7 +257,20 @@ impl Parsed {
         set_if_consistent(&mut self.year_div_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value.
+    /// Set the 'year modulo 100' field to the given value.
+    ///
+    /// When set it implies that the year is not negative.
+    ///
+    /// If this field is set while the 'year divided by 100' field is missing (and the full 'year'
+    /// field is also not set), it assumes a default value for the 'year divided by 100' field.
+    /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is negative or if it is greater than 99.
+    /// Currently only checks the value is positive and not out of range for an `i32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> {
         if value < 0 {
@@ -148,13 +279,32 @@ impl Parsed {
         set_if_consistent(&mut self.year_mod_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`isoyear`](#structfield.isoyear) field from given value.
+    /// Set the 'year' field that is part of an [ISO 8601 week date] to the given value.
+    ///
+    /// The value can be negative, unlike the 'year divided by 100' and 'year modulo 100' fields.
+    ///
+    /// [ISO 8601 week date]: crate::NaiveDate#week-date
+    ///
+    /// # Errors
+    ///
+    /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value.
+    /// Set the 'year divided by 100' field that is part of an [ISO 8601 week date] to the given
+    /// value.
+    ///
+    /// [ISO 8601 week date]: crate::NaiveDate#week-date
+    ///
+    /// # Errors
+    ///
+    /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> {
         if value < 0 {
@@ -166,7 +316,22 @@ impl Parsed {
         )
     }
 
-    /// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value.
+    /// Set the 'year modulo 100' that is part of an [ISO 8601 week date] field to the given value.
+    ///
+    /// When set it implies that the year is not negative.
+    ///
+    /// If this field is set while the 'year divided by 100' field is missing (and the full `year`
+    /// field is also not set), it assumes a default value for the 'year divided by 100' field.
+    /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise.
+    ///
+    /// [ISO 8601 week date]: crate::NaiveDate#week-date
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is negative or if it is greater than 99.
+    /// Currently only checks the value is positive and not out of range for an `i32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> {
         if value < 0 {
@@ -178,57 +343,122 @@ impl Parsed {
         )
     }
 
-    /// Tries to set the [`month`](#structfield.month) field from given value.
+    /// Set the 'month' field to the given value.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 1-12.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_month(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.month, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value.
+    /// Set the 'week number starting with Sunday' field to the given value.
+    ///
+    /// Week 1 starts at the first Sunday of January.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 0-53.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.week_from_sun, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value.
+    /// Set the 'week number starting with Monday' field to the given value.
+    ///
+    /// Week 1 starts at the first Monday of January.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 0-53.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.week_from_mon, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`isoweek`](#structfield.isoweek) field from given value.
+    /// Set the '[ISO 8601 week number]' field to the given value.
+    ///
+    /// [ISO 8601 week number]: crate::NaiveDate#week-date
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 1-53.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.isoweek, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`weekday`](#structfield.weekday) field from given value.
+    /// Set the 'day of the week' field to the given value.
+    ///
+    /// # Errors
+    ///
+    /// May return `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> {
         set_if_consistent(&mut self.weekday, value)
     }
 
-    /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value.
+    /// Set the 'ordinal' (day of the year) field to the given value.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 1-366.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.ordinal, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`day`](#structfield.day) field from given value.
+    /// Set the 'day of the month' field to the given value.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 1-31.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_day(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.day, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value.
-    /// (`false` for AM, `true` for PM)
+    /// Set the 'am/pm' field to the given value.
+    ///
+    /// `false` indicates AM and `true` indicates PM.
+    ///
+    /// # Errors
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> {
         set_if_consistent(&mut self.hour_div_12, u32::from(value))
     }
 
-    /// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from
-    /// given hour number in 12-hour clocks.
+    /// Set the 'hour number in 12-hour clocks' field to the given value.
+    ///
+    /// Value must be in the canonical range of 1-12.
+    /// It will internally be stored as 0-11 (`value % 12`).
+    ///
+    /// # Errors
+    ///
+    /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> {
         if !(1..=12).contains(&value) {
@@ -237,8 +467,16 @@ impl Parsed {
         set_if_consistent(&mut self.hour_mod_12, value as u32 % 12)
     }
 
-    /// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and
-    /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value.
+    /// Set the 'hour' field to the given value.
+    ///
+    /// Internally this sets the 'hour modulo 12' and 'am/pm' fields.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 0-23.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` one of the fields was already set to a different value.
     #[inline]
     pub fn set_hour(&mut self, value: i64) -> ParseResult<()> {
         let v = u32::try_from(value).map_err(|_| OUT_OF_RANGE)?;
@@ -247,31 +485,71 @@ impl Parsed {
         Ok(())
     }
 
-    /// Tries to set the [`minute`](#structfield.minute) field from given value.
+    /// Set the 'minute' field to the given value.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 0-59.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_minute(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.minute, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`second`](#structfield.second) field from given value.
+    /// Set the 'second' field to the given value.
+    ///
+    /// The value can be 60 in the case of a leap second.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 0-60.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_second(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.second, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value.
+    /// Set the 'nanosecond' field to the given value.
+    ///
+    /// This is the number of nanoseconds since the whole second.
+    ///
+    /// # Errors
+    ///
+    /// May return `OUT_OF_RANGE` if `value` is not in the range 0-999,999,999.
+    /// Currently only checks the value is not out of range for a `u32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.nanosecond, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
     }
 
-    /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value.
+    /// Set the 'timestamp' field to the given value.
+    ///
+    /// A Unix timestamp is defined as the number of non-leap seconds since midnight UTC on
+    /// January 1, 1970.
+    ///
+    /// # Errors
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.timestamp, value)
     }
 
-    /// Tries to set the [`offset`](#structfield.offset) field from given value.
+    /// Set the 'offset from local time to UTC' field to the given value.
+    ///
+    /// The offset is in seconds.
+    ///
+    /// # Errors
+    ///
+    /// Returns `OUT_OF_RANGE` if `value` is ouside the range of an `i32`.
+    ///
+    /// Returns `IMPOSSIBLE` if this field was already set to a different value.
     #[inline]
     pub fn set_offset(&mut self, value: i64) -> ParseResult<()> {
         set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
@@ -288,6 +566,18 @@ impl Parsed {
     ///
     /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted,
     /// the two-digit year is used to guess the century number then.
+    ///
+    /// It checks all given date fields are consistent with each other.
+    ///
+    /// # Errors
+    ///
+    /// This method returns:
+    /// - `IMPOSSIBLE` if any of the date fields conflict.
+    /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete date.
+    /// - `OUT_OF_RANGE`
+    ///   - if any of the date fields of `Parsed` are set to a value beyond their acceptable range.
+    ///   - if the value would be outside the range of a [`NaiveDate`].
+    ///   - if the date does not exist.
     pub fn to_naive_date(&self) -> ParseResult<NaiveDate> {
         fn resolve_year(
             y: Option<i32>,
@@ -305,7 +595,7 @@ impl Parsed {
                 // we should filter a negative full year first.
                 (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => {
                     if y < 0 {
-                        return Err(OUT_OF_RANGE);
+                        return Err(IMPOSSIBLE);
                     }
                     let q_ = y / 100;
                     let r_ = y % 100;
@@ -320,7 +610,7 @@ impl Parsed {
                 // reconstruct the full year. make sure that the result is always positive.
                 (None, Some(q), Some(r @ 0..=99)) => {
                     if q < 0 {
-                        return Err(OUT_OF_RANGE);
+                        return Err(IMPOSSIBLE);
                     }
                     let y = q.checked_mul(100).and_then(|v| v.checked_add(r));
                     Ok(Some(y.ok_or(OUT_OF_RANGE)?))
@@ -494,6 +784,14 @@ impl Parsed {
     /// - Hour, minute, second, nanosecond.
     ///
     /// It is able to handle leap seconds when given second is 60.
+    ///
+    /// # Errors
+    ///
+    /// This method returns:
+    /// - `OUT_OF_RANGE` if any of the time fields of `Parsed` are set to a value beyond
+    ///   their acceptable range.
+    /// - `NOT_ENOUGH` if an hour field is missing, if AM/PM is missing in a 12-hour clock,
+    ///   if minutes are missing, or if seconds are missing while the nanosecond field is present.
     pub fn to_naive_time(&self) -> ParseResult<NaiveTime> {
         let hour_div_12 = match self.hour_div_12 {
             Some(v @ 0..=1) => v,
@@ -529,13 +827,25 @@ impl Parsed {
         NaiveTime::from_hms_nano(hour, minute, second, nano).map_err(|_| OUT_OF_RANGE)
     }
 
-    /// Returns a parsed naive date and time out of given fields,
-    /// except for the [`offset`](#structfield.offset) field (assumed to have a given value).
-    /// This is required for parsing a local time or other known-timezone inputs.
+    /// Returns a parsed naive date and time out of given fields, except for the offset field.
+    ///
+    /// The offset is assumed to have a given value. It is not compared against the offset field set
+    /// in the `Parsed` type, so it is allowed to be inconsistent.
+    ///
+    /// This method is able to determine the combined date and time from date and time fields or
+    /// from a single timestamp field. It checks all fields are consistent with each other.
+    ///
+    /// # Errors
     ///
-    /// This method is able to determine the combined date and time
-    /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field.
-    /// Either way those fields have to be consistent to each other.
+    /// This method returns:
+    /// - `IMPOSSIBLE`  if any of the date fields conflict, or if a timestamp conflicts with any of
+    ///   the other fields.
+    /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime.
+    /// - `OUT_OF_RANGE`
+    ///   - if any of the date or time fields of `Parsed` are set to a value beyond their acceptable
+    ///     range.
+    ///   - if the value would be outside the range of a [`NaiveDateTime`].
+    ///   - if the date does not exist.
     pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult<NaiveDateTime> {
         let date = self.to_naive_date();
         let time = self.to_naive_time();
@@ -609,16 +919,34 @@ impl Parsed {
     }
 
     /// Returns a parsed fixed time zone offset out of given fields.
+    ///
+    /// # Errors
+    ///
+    /// This method returns:
+    /// - `OUT_OF_RANGE` if the offset is out of range for a `FixedOffset`.
+    /// - `NOT_ENOUGH` if the offset field is not set.
     pub fn to_fixed_offset(&self) -> ParseResult<FixedOffset> {
-        self.offset.and_then(FixedOffset::east).ok_or(OUT_OF_RANGE)
+        FixedOffset::east(self.offset.ok_or(NOT_ENOUGH)?).ok_or(OUT_OF_RANGE)
     }
 
     /// Returns a parsed timezone-aware date and time out of given fields.
     ///
-    /// This method is able to determine the combined date and time
-    /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field,
-    /// plus a time zone offset.
-    /// Either way those fields have to be consistent to each other.
+    /// This method is able to determine the combined date and time from date, time and offset
+    /// fields, and/or from a single timestamp field. It checks all fields are consistent with each
+    /// other.
+    ///
+    /// # Errors
+    ///
+    /// This method returns:
+    /// - `IMPOSSIBLE`  if any of the date fields conflict, or if a timestamp conflicts with any of
+    ///   the other fields.
+    /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime
+    ///   including offset from UTC.
+    /// - `OUT_OF_RANGE`
+    ///   - if any of the fields of `Parsed` are set to a value beyond their acceptable
+    ///   range.
+    ///   - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`].
+    ///   - if the date does not exist.
     pub fn to_datetime(&self) -> ParseResult<DateTime<FixedOffset>> {
         // If there is no explicit offset, consider a timestamp value as indication of a UTC value.
         let offset = match (self.offset, self.timestamp) {
@@ -637,14 +965,30 @@ impl Parsed {
     }
 
     /// Returns a parsed timezone-aware date and time out of given fields,
-    /// with an additional `TimeZone` used to interpret and validate the local date.
-    ///
-    /// This method is able to determine the combined date and time
-    /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field,
-    /// plus a time zone offset.
-    /// Either way those fields have to be consistent to each other.
-    /// If parsed fields include an UTC offset, it also has to be consistent to
-    /// [`offset`](#structfield.offset).
+    /// with an additional [`TimeZone`] used to interpret and validate the local date.
+    ///
+    /// This method is able to determine the combined date and time from date and time, and/or from
+    /// a single timestamp field. It checks all fields are consistent with each other.
+    ///
+    /// If the parsed fields include an UTC offset, it also has to be consistent with the offset in
+    /// the provided `tz` time zone for that datetime.
+    ///
+    /// # Errors
+    ///
+    /// This method returns:
+    /// - `IMPOSSIBLE`
+    ///   - if any of the date fields conflict, if a timestamp conflicts with any of the other
+    ///     fields, or if the offset field is set but differs from the offset at that time in the
+    ///     `tz` time zone.
+    ///   - if the local datetime does not exists in the provided time zone (because it falls in a
+    ///     transition due to for example DST).
+    /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime, or if
+    ///   the local time in the provided time zone is ambiguous (because it falls in a transition
+    ///   due to for example DST) while there is no offset field or timestamp field set.
+    /// - `OUT_OF_RANGE`
+    ///   - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`].
+    ///   - if any of the fields of `Parsed` are set to a value beyond their acceptable range.
+    ///   - if the date does not exist.
     pub fn to_datetime_with_timezone<Tz: TimeZone>(&self, tz: &Tz) -> ParseResult<DateTime<Tz>> {
         // if we have `timestamp` specified, guess an offset from that.
         let mut guessed_offset = 0;
@@ -824,7 +1168,7 @@ mod tests {
         );
         assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE));
         assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1));
-        assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(OUT_OF_RANGE));
+        assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(IMPOSSIBLE));
         let max_year = NaiveDate::MAX.year();
         assert_eq!(
             parse!(year_div_100: max_year / 100,
@@ -860,10 +1204,10 @@ mod tests {
         );
         assert_eq!(
             parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1),
-            Err(OUT_OF_RANGE)
+            Err(IMPOSSIBLE)
         );
-        assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(OUT_OF_RANGE));
-        assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE));
+        assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(IMPOSSIBLE));
+        assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(IMPOSSIBLE));
 
         // weekdates
         assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH));
diff --git a/src/lib.rs b/src/lib.rs
index fd8bcd7607..6e63c5c237 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -24,28 +24,34 @@
 //!
 //! Default features:
 //!
-//! - `alloc`: Enable features that depend on allocation (primarily string formatting)
+//! - `alloc`: Enable features that depend on allocation (primarily string formatting).
 //! - `std`: Enables functionality that depends on the standard library. This
 //!   is a superset of `alloc` and adds interoperation with standard library types
 //!   and traits.
-//! - `clock`: Enables reading the system time (`now`) that depends on the standard library for
-//! UNIX-like operating systems and the Windows API (`winapi`) for Windows.
+//! - `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`.
+//! - `now`: Enables reading the system time (`now`).
 //! - `wasmbind`: Interface with the JS Date API for the `wasm32` target.
 //!
 //! Optional features:
 //!
-//! - [`serde`][]: Enable serialization/deserialization via serde.
-//! - `rkyv`: Enable serialization/deserialization via rkyv.
-//! - `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate.
+//! - `serde`: Enable serialization/deserialization via [serde].
+//! - `rkyv`: Deprecated, use the `rkyv-*` features.
+//! - `rkyv-16`: Enable serialization/deserialization via [rkyv], using 16-bit integers for integral `*size` types.
+//! - `rkyv-32`: Enable serialization/deserialization via [rkyv], using 32-bit integers for integral `*size` types.
+//! - `rkyv-64`: Enable serialization/deserialization via [rkyv], using 64-bit integers for integral `*size` types.
+//! - `rkyv-validation`: Enable rkyv validation support using `bytecheck`.
+//! - `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated).
+//! - `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate.
 //! - `unstable-locales`: Enable localization. This adds various methods with a
 //!   `_localized` suffix. The implementation and API may change or even be
 //!   removed in a patch release. Feedback welcome.
 //!
-//! [`serde`]: https://github.com/serde-rs/serde
-//! [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen
+//! Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive.
 //!
-//! See the [cargo docs][] for examples of specifying features.
+//! See the [cargo docs] for examples of specifying features.
 //!
+//! [serde]: https://github.com/serde-rs/serde
+//! [rkyv]: https://github.com/rkyv/rkyv
 //! [cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features
 //!
 //! ## Overview
@@ -544,7 +550,33 @@ pub use naive::__BenchYearFlags;
 /// [2]: https://serde.rs/field-attrs.html#with
 #[cfg(feature = "serde")]
 pub mod serde {
+    use core::fmt;
+    use serde::de;
+
     pub use super::datetime::serde::*;
+
+    /// Create a custom `de::Error` with `SerdeError::InvalidTimestamp`.
+    pub(crate) fn invalid_ts<E, T>(value: T) -> E
+    where
+        E: de::Error,
+        T: fmt::Display,
+    {
+        E::custom(SerdeError::InvalidTimestamp(value))
+    }
+
+    enum SerdeError<T: fmt::Display> {
+        InvalidTimestamp(T),
+    }
+
+    impl<T: fmt::Display> fmt::Display for SerdeError<T> {
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            match self {
+                SerdeError::InvalidTimestamp(ts) => {
+                    write!(f, "value is not a legal timestamp: {}", ts)
+                }
+            }
+        }
+    }
 }
 
 /// Zero-copy serialization/deserialization with rkyv.
diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs
index fe35dc4d5e..2217f73051 100644
--- a/src/naive/date/mod.rs
+++ b/src/naive/date/mod.rs
@@ -263,33 +263,29 @@ impl NaiveDate {
     pub const fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> Option<NaiveDate> {
         let flags = YearFlags::from_year(year);
         let nweeks = flags.nisoweeks();
-        if 1 <= week && week <= nweeks {
-            // ordinal = week ordinal - delta
-            let weekord = week * 7 + weekday as u32;
-            let delta = flags.isoweek_delta();
-            if weekord <= delta {
-                // ordinal < 1, previous year
-                let prevflags = YearFlags::from_year(year - 1);
-                NaiveDate::from_ordinal_and_flags(
-                    year - 1,
-                    weekord + prevflags.ndays() - delta,
-                    prevflags,
-                )
+        if week == 0 || week > nweeks {
+            return None;
+        }
+        // ordinal = week ordinal - delta
+        let weekord = week * 7 + weekday as u32;
+        let delta = flags.isoweek_delta();
+        let (year, ordinal, flags) = if weekord <= delta {
+            // ordinal < 1, previous year
+            let prevflags = YearFlags::from_year(year - 1);
+            (year - 1, weekord + prevflags.ndays() - delta, prevflags)
+        } else {
+            let ordinal = weekord - delta;
+            let ndays = flags.ndays();
+            if ordinal <= ndays {
+                // this year
+                (year, ordinal, flags)
             } else {
-                let ordinal = weekord - delta;
-                let ndays = flags.ndays();
-                if ordinal <= ndays {
-                    // this year
-                    NaiveDate::from_ordinal_and_flags(year, ordinal, flags)
-                } else {
-                    // ordinal > ndays, next year
-                    let nextflags = YearFlags::from_year(year + 1);
-                    NaiveDate::from_ordinal_and_flags(year + 1, ordinal - ndays, nextflags)
-                }
+                // ordinal > ndays, next year
+                let nextflags = YearFlags::from_year(year + 1);
+                (year + 1, ordinal - ndays, nextflags)
             }
-        } else {
-            None
-        }
+        };
+        NaiveDate::from_ordinal_and_flags(year, ordinal, flags)
     }
 
     /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with
diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs
index d0830c05cd..57e29c9fbc 100644
--- a/src/naive/datetime/serde.rs
+++ b/src/naive/datetime/serde.rs
@@ -2,7 +2,6 @@ use core::fmt;
 use serde::{de, ser};
 
 use super::NaiveDateTime;
-use crate::offset::LocalResult;
 
 /// Serialize a `NaiveDateTime` as an RFC 3339 string
 ///
@@ -81,7 +80,7 @@ pub mod ts_nanoseconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use super::ne_timestamp;
+    use crate::serde::invalid_ts;
     use crate::NaiveDateTime;
 
     /// Serialize a datetime into an integer number of nanoseconds since the epoch
@@ -174,7 +173,7 @@ pub mod ts_nanoseconds {
                 value.div_euclid(1_000_000_000),
                 (value.rem_euclid(1_000_000_000)) as u32,
             )
-            .ok_or_else(|| E::custom(ne_timestamp(value)))
+            .ok_or_else(|| invalid_ts(value))
         }
 
         fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
@@ -185,7 +184,7 @@ pub mod ts_nanoseconds {
                 (value / 1_000_000_000) as i64,
                 (value % 1_000_000_000) as u32,
             )
-            .ok_or_else(|| E::custom(ne_timestamp(value)))
+            .ok_or_else(|| invalid_ts(value))
         }
     }
 }
@@ -361,7 +360,7 @@ pub mod ts_microseconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use super::ne_timestamp;
+    use crate::serde::invalid_ts;
     use crate::NaiveDateTime;
 
     /// Serialize a datetime into an integer number of microseconds since the epoch
@@ -437,8 +436,7 @@ pub mod ts_microseconds {
         where
             E: de::Error,
         {
-            NaiveDateTime::from_timestamp_micros(value)
-                .ok_or_else(|| E::custom(ne_timestamp(value)))
+            NaiveDateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value))
         }
 
         fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
@@ -449,7 +447,7 @@ pub mod ts_microseconds {
                 (value / 1_000_000) as i64,
                 ((value % 1_000_000) * 1_000) as u32,
             )
-            .ok_or_else(|| E::custom(ne_timestamp(value)))
+            .ok_or_else(|| invalid_ts(value))
         }
     }
 }
@@ -614,7 +612,7 @@ pub mod ts_milliseconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use super::ne_timestamp;
+    use crate::serde::invalid_ts;
     use crate::NaiveDateTime;
 
     /// Serialize a datetime into an integer number of milliseconds since the epoch
@@ -690,8 +688,7 @@ pub mod ts_milliseconds {
         where
             E: de::Error,
         {
-            NaiveDateTime::from_timestamp_millis(value)
-                .ok_or_else(|| E::custom(ne_timestamp(value)))
+            NaiveDateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value))
         }
 
         fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
@@ -702,7 +699,7 @@ pub mod ts_milliseconds {
                 (value / 1000) as i64,
                 ((value % 1000) * 1_000_000) as u32,
             )
-            .ok_or_else(|| E::custom(ne_timestamp(value)))
+            .ok_or_else(|| invalid_ts(value))
         }
     }
 }
@@ -867,7 +864,7 @@ pub mod ts_seconds {
     use core::fmt;
     use serde::{de, ser};
 
-    use super::ne_timestamp;
+    use crate::serde::invalid_ts;
     use crate::NaiveDateTime;
 
     /// Serialize a datetime into an integer number of seconds since the epoch
@@ -938,15 +935,18 @@ pub mod ts_seconds {
         where
             E: de::Error,
         {
-            NaiveDateTime::from_timestamp(value, 0).ok_or_else(|| E::custom(ne_timestamp(value)))
+            NaiveDateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value))
         }
 
         fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
         where
             E: de::Error,
         {
-            NaiveDateTime::from_timestamp(value as i64, 0)
-                .ok_or_else(|| E::custom(ne_timestamp(value)))
+            if value > i64::MAX as u64 {
+                Err(invalid_ts(value))
+            } else {
+                NaiveDateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value))
+            }
         }
     }
 }
@@ -1077,54 +1077,6 @@ pub mod ts_seconds_option {
     }
 }
 
-// lik? function to convert a LocalResult into a serde-ish Result
-pub(crate) fn serde_from<T, E, V>(me: LocalResult<T>, ts: &V) -> Result<T, E>
-where
-    E: de::Error,
-    V: fmt::Display,
-    T: fmt::Display,
-{
-    match me {
-        LocalResult::None => Err(E::custom(ne_timestamp(ts))),
-        LocalResult::Ambiguous(min, max) => {
-            Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max }))
-        }
-        LocalResult::Single(val) => Ok(val),
-    }
-}
-
-enum SerdeError<V: fmt::Display, D: fmt::Display> {
-    NonExistent { timestamp: V },
-    Ambiguous { timestamp: V, min: D, max: D },
-}
-
-/// Construct a [`SerdeError::NonExistent`]
-fn ne_timestamp<T: fmt::Display>(ts: T) -> SerdeError<T, u8> {
-    SerdeError::NonExistent::<T, u8> { timestamp: ts }
-}
-
-impl<V: fmt::Display, D: fmt::Display> fmt::Debug for SerdeError<V, D> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "ChronoSerdeError({})", self)
-    }
-}
-
-// impl<V: fmt::Display, D: fmt::Debug> core::error::Error for SerdeError<V, D> {}
-impl<V: fmt::Display, D: fmt::Display> fmt::Display for SerdeError<V, D> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            SerdeError::NonExistent { timestamp } => {
-                write!(f, "value is not a legal timestamp: {}", timestamp)
-            }
-            SerdeError::Ambiguous { timestamp, min, max } => write!(
-                f,
-                "value is an ambiguous timestamp: {}, could be either of {}, {}",
-                timestamp, min, max
-            ),
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use crate::naive::datetime::{test_decodable_json, test_encodable_json};
diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs
index 7574fb3317..9bb8ae6365 100644
--- a/src/offset/local/win_bindings.rs
+++ b/src/offset/local/win_bindings.rs
@@ -1,4 +1,4 @@
-// Bindings generated by `windows-bindgen` 0.52.0
+// Bindings generated by `windows-bindgen` 0.53.0
 
 #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)]
 ::windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL);


Test patch: