diff --git a/lib/oxsdatatypes/README.md b/lib/oxsdatatypes/README.md
index a164f282..f198d5ae 100644
--- a/lib/oxsdatatypes/README.md
+++ b/lib/oxsdatatypes/README.md
@@ -38,10 +38,10 @@ The `DateTime::now()` function needs special OS support.
 Currently:
 - If the `custom-now` feature is enabled, a function computing `now` must be set:
   ```rust
-  use oxsdatatypes::{DateTimeError, Duration};
+   use oxsdatatypes::Duration;
   
   #[no_mangle]
-  fn custom_ox_now() -> Result<Duration, DateTimeError> {
+  fn custom_ox_now() -> Duration {
     unimplemented!("now implementation")
   }
   ```
diff --git a/lib/oxsdatatypes/src/date_time.rs b/lib/oxsdatatypes/src/date_time.rs
index 0bfe29d1..671d00a8 100644
--- a/lib/oxsdatatypes/src/date_time.rs
+++ b/lib/oxsdatatypes/src/date_time.rs
@@ -1,14 +1,11 @@
-use super::{DayTimeDuration, Decimal, Duration, XsdParseError, YearMonthDuration};
-use crate::parser::{
-    parse_date, parse_date_time, parse_g_day, parse_g_month, parse_g_month_day, parse_g_year,
-    parse_g_year_month, parse_time,
-};
+#![allow(clippy::expect_used)]
+
+use crate::{DayTimeDuration, Decimal, Duration, YearMonthDuration};
 use std::cmp::{min, Ordering};
 use std::error::Error;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::str::FromStr;
-use std::time::SystemTimeError;
 
 /// [XML Schema `dateTime` datatype](https://www.w3.org/TR/xmlschema11-2/#dateTime)
 ///
@@ -29,7 +26,7 @@ impl DateTime {
         minute: u8,
         second: Decimal,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
                 year: Some(year),
@@ -45,10 +42,10 @@ impl DateTime {
 
     /// [fn:current-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-current-dateTime)
     #[inline]
-    pub fn now() -> Result<Self, DateTimeError> {
-        Ok(Self {
-            timestamp: Timestamp::now()?,
-        })
+    pub fn now() -> Self {
+        Self {
+            timestamp: Timestamp::now(),
+        }
     }
 
     #[inline]
@@ -134,6 +131,8 @@ impl DateTime {
     }
 
     /// [op:subtract-dateTimes](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dateTimes)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<DayTimeDuration> {
@@ -141,6 +140,8 @@ impl DateTime {
     }
 
     /// [op:add-yearMonthDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-dateTime)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_year_month_duration(
@@ -151,6 +152,8 @@ impl DateTime {
     }
 
     /// [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-dateTime)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_day_time_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
@@ -161,6 +164,8 @@ impl DateTime {
     }
 
     /// [op:add-yearMonthDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-dateTime) and [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-dateTime)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
@@ -176,6 +181,8 @@ impl DateTime {
     }
 
     /// [op:subtract-yearMonthDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-dateTime)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub_year_month_duration(
@@ -186,6 +193,8 @@ impl DateTime {
     }
 
     /// [op:subtract-dayTimeDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-dateTime)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
@@ -196,6 +205,8 @@ impl DateTime {
     }
 
     /// [op:subtract-yearMonthDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-dateTime) and [op:subtract-dayTimeDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-dateTime)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
@@ -214,6 +225,8 @@ impl DateTime {
     }
 
     /// [fn:adjust-dateTime-to-timezone](https://www.w3.org/TR/xpath-functions-31/#func-adjust-dateTime-to-timezone)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn adjust(self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
@@ -228,14 +241,22 @@ impl DateTime {
     pub fn is_identical_with(self, other: Self) -> bool {
         self.timestamp.is_identical_with(other.timestamp)
     }
+
+    pub const MIN: Self = Self {
+        timestamp: Timestamp::MIN,
+    };
+
+    pub const MAX: Self = Self {
+        timestamp: Timestamp::MAX,
+    };
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
 impl TryFrom<Date> for DateTime {
-    type Error = DateTimeError;
+    type Error = DateTimeOverflowError;
 
     #[inline]
-    fn try_from(date: Date) -> Result<Self, DateTimeError> {
+    fn try_from(date: Date) -> Result<Self, DateTimeOverflowError> {
         Self::new(
             date.year(),
             date.month(),
@@ -249,10 +270,10 @@ impl TryFrom<Date> for DateTime {
 }
 
 impl FromStr for DateTime {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_date_time(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, date_time_lexical_rep)
     }
 }
 
@@ -263,6 +284,7 @@ impl fmt::Display for DateTime {
         if year < 0 {
             write!(f, "-")?;
         }
+        let second = self.second();
         write!(
             f,
             "{:04}-{:02}-{:02}T{:02}:{:02}:{}{}",
@@ -271,12 +293,12 @@ impl fmt::Display for DateTime {
             self.day(),
             self.hour(),
             self.minute(),
-            if self.second().abs() >= 10.into() {
-                ""
-            } else {
+            if Decimal::from(-10) < second && second < Decimal::from(10) {
                 "0"
+            } else {
+                ""
             },
-            self.second()
+            second
         )?;
         if let Some(timezone_offset) = self.timezone_offset() {
             write!(f, "{timezone_offset}")?;
@@ -296,12 +318,12 @@ pub struct Time {
 
 impl Time {
     #[inline]
-    pub(super) fn new(
+    fn new(
         mut hour: u8,
         minute: u8,
         second: Decimal,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         if hour == 24 && minute == 0 && second == Decimal::default() {
             hour = 0;
         }
@@ -328,8 +350,10 @@ impl Time {
 
     /// [fn:current-time](https://www.w3.org/TR/xpath-functions-31/#func-current-time)
     #[inline]
-    pub fn now() -> Result<Self, DateTimeError> {
-        DateTime::now()?.try_into()
+    pub fn now() -> Self {
+        Self {
+            timestamp: Timestamp::now(),
+        }
     }
 
     /// [fn:hour-from-time](https://www.w3.org/TR/xpath-functions-31/#func-hours-from-time)
@@ -373,6 +397,8 @@ impl Time {
     }
 
     /// [op:subtract-times](https://www.w3.org/TR/xpath-functions-31/#func-subtract-times)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<DayTimeDuration> {
@@ -380,6 +406,8 @@ impl Time {
     }
 
     /// [op:add-dayTimeDuration-to-time](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-time)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
@@ -387,6 +415,8 @@ impl Time {
     }
 
     /// [op:add-dayTimeDuration-to-time](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-time)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
@@ -406,6 +436,8 @@ impl Time {
     }
 
     /// [op:subtract-dayTimeDuration-from-time](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-time)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
@@ -413,6 +445,8 @@ impl Time {
     }
 
     /// [op:subtract-dayTimeDuration-from-time](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-time)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
@@ -456,45 +490,61 @@ impl Time {
     pub fn is_identical_with(self, other: Self) -> bool {
         self.timestamp.is_identical_with(other.timestamp)
     }
+
+    #[cfg(test)]
+    const MIN: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(62_230_154_400),
+            timezone_offset: Some(TimezoneOffset::MAX),
+        },
+    };
+
+    #[cfg(test)]
+    const MAX: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(62_230_255_200),
+            timezone_offset: Some(TimezoneOffset::MIN),
+        },
+    };
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<DateTime> for Time {
-    type Error = DateTimeError;
-
+impl From<DateTime> for Time {
     #[inline]
-    fn try_from(date_time: DateTime) -> Result<Self, DateTimeError> {
+    fn from(date_time: DateTime) -> Self {
         Self::new(
             date_time.hour(),
             date_time.minute(),
             date_time.second(),
             date_time.timezone_offset(),
         )
+        .expect("Casting from xsd:dateTime to xsd:date can't fail")
     }
 }
 
 impl FromStr for Time {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_time(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, time_lexical_rep)
     }
 }
 
 impl fmt::Display for Time {
     #[inline]
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let second = self.second();
         write!(
             f,
             "{:02}:{:02}:{}{}",
             self.hour(),
             self.minute(),
-            if self.second().abs() >= 10.into() {
-                ""
-            } else {
+            if Decimal::from(-10) < second && second < Decimal::from(10) {
                 "0"
+            } else {
+                ""
             },
-            self.second()
+            second
         )?;
         if let Some(timezone_offset) = self.timezone_offset() {
             write!(f, "{timezone_offset}")?;
@@ -514,12 +564,12 @@ pub struct Date {
 
 impl Date {
     #[inline]
-    pub(super) fn new(
+    fn new(
         year: i64,
         month: u8,
         day: u8,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
                 year: Some(year),
@@ -543,8 +593,10 @@ impl Date {
 
     /// [fn:current-date](https://www.w3.org/TR/xpath-functions-31/#func-current-date)
     #[inline]
-    pub fn now() -> Result<Self, DateTimeError> {
-        DateTime::now()?.try_into()
+    pub fn now() -> Self {
+        DateTime::now()
+            .try_into()
+            .expect("The current time seems way in the future, it's strange")
     }
 
     /// [fn:year-from-date](https://www.w3.org/TR/xpath-functions-31/#func-year-from-date)
@@ -588,6 +640,8 @@ impl Date {
     }
 
     /// [op:subtract-dates](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dates)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<DayTimeDuration> {
@@ -595,6 +649,8 @@ impl Date {
     }
 
     /// [op:add-yearMonthDuration-to-date](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-date)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_year_month_duration(
@@ -605,6 +661,8 @@ impl Date {
     }
 
     /// [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-date)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
@@ -612,6 +670,8 @@ impl Date {
     }
 
     /// [op:add-yearMonthDuration-to-date](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-date) and [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-date)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_add_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
@@ -623,6 +683,8 @@ impl Date {
     }
 
     /// [op:subtract-yearMonthDuration-from-date](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-date)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub_year_month_duration(
@@ -633,6 +695,8 @@ impl Date {
     }
 
     /// [op:subtract-dayTimeDuration-from-date](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-date)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
     #[inline]
     #[must_use]
     pub fn checked_sub_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
@@ -675,14 +739,27 @@ impl Date {
     pub fn is_identical_with(self, other: Self) -> bool {
         self.timestamp.is_identical_with(other.timestamp)
     }
+
+    pub const MIN: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(-170_141_183_460_469_216_800),
+            timezone_offset: Some(TimezoneOffset::MIN),
+        },
+    };
+    pub const MAX: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(170_141_183_460_469_216_800),
+            timezone_offset: Some(TimezoneOffset::MAX),
+        },
+    };
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
 impl TryFrom<DateTime> for Date {
-    type Error = DateTimeError;
+    type Error = DateTimeOverflowError;
 
     #[inline]
-    fn try_from(date_time: DateTime) -> Result<Self, DateTimeError> {
+    fn try_from(date_time: DateTime) -> Result<Self, DateTimeOverflowError> {
         Self::new(
             date_time.year(),
             date_time.month(),
@@ -693,10 +770,10 @@ impl TryFrom<DateTime> for Date {
 }
 
 impl FromStr for Date {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_date(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, date_lexical_rep)
     }
 }
 
@@ -726,11 +803,11 @@ pub struct GYearMonth {
 
 impl GYearMonth {
     #[inline]
-    pub(super) fn new(
+    fn new(
         year: i64,
         month: u8,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
                 year: Some(year),
@@ -796,14 +873,27 @@ impl GYearMonth {
     pub fn is_identical_with(self, other: Self) -> bool {
         self.timestamp.is_identical_with(other.timestamp)
     }
+
+    pub const MIN: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(-170_141_183_460_466_970_400),
+            timezone_offset: Some(TimezoneOffset::MIN),
+        },
+    };
+    pub const MAX: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(170_141_183_460_469_216_800),
+            timezone_offset: Some(TimezoneOffset::MAX),
+        },
+    };
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
 impl TryFrom<DateTime> for GYearMonth {
-    type Error = DateTimeError;
+    type Error = DateTimeOverflowError;
 
     #[inline]
-    fn try_from(date_time: DateTime) -> Result<Self, DateTimeError> {
+    fn try_from(date_time: DateTime) -> Result<Self, DateTimeOverflowError> {
         Self::new(
             date_time.year(),
             date_time.month(),
@@ -813,20 +903,19 @@ impl TryFrom<DateTime> for GYearMonth {
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<Date> for GYearMonth {
-    type Error = DateTimeError;
-
+impl From<Date> for GYearMonth {
     #[inline]
-    fn try_from(date: Date) -> Result<Self, DateTimeError> {
+    fn from(date: Date) -> Self {
         Self::new(date.year(), date.month(), date.timezone_offset())
+            .expect("Casting from xsd:date to xsd:gYearMonth can't fail")
     }
 }
 
 impl FromStr for GYearMonth {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_g_year_month(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, g_year_month_lexical_rep)
     }
 }
 
@@ -856,10 +945,10 @@ pub struct GYear {
 
 impl GYear {
     #[inline]
-    pub(super) fn new(
+    fn new(
         year: i64,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
                 year: Some(year),
@@ -919,42 +1008,55 @@ impl GYear {
     pub fn is_identical_with(self, other: Self) -> bool {
         self.timestamp.is_identical_with(other.timestamp)
     }
+
+    pub const MIN: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(-170_141_183_460_461_700_000),
+            timezone_offset: Some(TimezoneOffset::MIN),
+        },
+    };
+    pub const MAX: Self = Self {
+        timestamp: Timestamp {
+            value: Decimal::new_from_i128_unchecked(170_141_183_460_461_440_800),
+            timezone_offset: Some(TimezoneOffset::MAX),
+        },
+    };
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
 impl TryFrom<DateTime> for GYear {
-    type Error = DateTimeError;
+    type Error = DateTimeOverflowError;
 
     #[inline]
-    fn try_from(date_time: DateTime) -> Result<Self, DateTimeError> {
+    fn try_from(date_time: DateTime) -> Result<Self, DateTimeOverflowError> {
         Self::new(date_time.year(), date_time.timezone_offset())
     }
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
 impl TryFrom<Date> for GYear {
-    type Error = DateTimeError;
+    type Error = DateTimeOverflowError;
 
     #[inline]
-    fn try_from(date: Date) -> Result<Self, DateTimeError> {
+    fn try_from(date: Date) -> Result<Self, DateTimeOverflowError> {
         Self::new(date.year(), date.timezone_offset())
     }
 }
 
 impl TryFrom<GYearMonth> for GYear {
-    type Error = DateTimeError;
+    type Error = DateTimeOverflowError;
 
     #[inline]
-    fn try_from(year_month: GYearMonth) -> Result<Self, DateTimeError> {
+    fn try_from(year_month: GYearMonth) -> Result<Self, DateTimeOverflowError> {
         Self::new(year_month.year(), year_month.timezone_offset())
     }
 }
 
 impl FromStr for GYear {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_g_year(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, g_year_lexical_rep)
     }
 }
 
@@ -984,11 +1086,11 @@ pub struct GMonthDay {
 
 impl GMonthDay {
     #[inline]
-    pub(super) fn new(
+    fn new(
         month: u8,
         day: u8,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
                 year: None,
@@ -1057,34 +1159,32 @@ impl GMonthDay {
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<DateTime> for GMonthDay {
-    type Error = DateTimeError;
-
+impl From<DateTime> for GMonthDay {
     #[inline]
-    fn try_from(date_time: DateTime) -> Result<Self, DateTimeError> {
+    fn from(date_time: DateTime) -> Self {
         Self::new(
             date_time.month(),
             date_time.day(),
             date_time.timezone_offset(),
         )
+        .expect("Casting from xsd:dateTime to xsd:gMonthDay can't fail")
     }
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<Date> for GMonthDay {
-    type Error = DateTimeError;
-
+impl From<Date> for GMonthDay {
     #[inline]
-    fn try_from(date: Date) -> Result<Self, DateTimeError> {
+    fn from(date: Date) -> Self {
         Self::new(date.month(), date.day(), date.timezone_offset())
+            .expect("Casting from xsd:date to xsd:gMonthDay can't fail")
     }
 }
 
 impl FromStr for GMonthDay {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_g_month_day(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, g_month_day_lexical_rep)
     }
 }
 
@@ -1110,10 +1210,10 @@ pub struct GMonth {
 
 impl GMonth {
     #[inline]
-    pub(super) fn new(
+    fn new(
         month: u8,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
                 year: None,
@@ -1176,48 +1276,44 @@ impl GMonth {
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<DateTime> for GMonth {
-    type Error = DateTimeError;
-
+impl From<DateTime> for GMonth {
     #[inline]
-    fn try_from(date_time: DateTime) -> Result<Self, DateTimeError> {
+    fn from(date_time: DateTime) -> Self {
         Self::new(date_time.month(), date_time.timezone_offset())
+            .expect("Casting from xsd:dateTime to xsd:gMonth can't fail")
     }
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<Date> for GMonth {
-    type Error = DateTimeError;
-
+impl From<Date> for GMonth {
     #[inline]
-    fn try_from(date: Date) -> Result<Self, DateTimeError> {
+    fn from(date: Date) -> Self {
         Self::new(date.month(), date.timezone_offset())
+            .expect("Casting from xsd:date to xsd:gMonth can't fail")
     }
 }
 
-impl TryFrom<GYearMonth> for GMonth {
-    type Error = DateTimeError;
-
+impl From<GYearMonth> for GMonth {
     #[inline]
-    fn try_from(year_month: GYearMonth) -> Result<Self, DateTimeError> {
+    fn from(year_month: GYearMonth) -> Self {
         Self::new(year_month.month(), year_month.timezone_offset())
+            .expect("Casting from xsd:gYearMonth to xsd:gMonth can't fail")
     }
 }
 
-impl TryFrom<GMonthDay> for GMonth {
-    type Error = DateTimeError;
-
+impl From<GMonthDay> for GMonth {
     #[inline]
-    fn try_from(month_day: GMonthDay) -> Result<Self, DateTimeError> {
+    fn from(month_day: GMonthDay) -> Self {
         Self::new(month_day.month(), month_day.timezone_offset())
+            .expect("Casting from xsd:gMonthDay to xsd:gMonth can't fail")
     }
 }
 
 impl FromStr for GMonth {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_g_month(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, g_month_lexical_rep)
     }
 }
 
@@ -1243,10 +1339,10 @@ pub struct GDay {
 
 impl GDay {
     #[inline]
-    pub(super) fn new(
+    fn new(
         day: u8,
         timezone_offset: Option<TimezoneOffset>,
-    ) -> Result<Self, DateTimeError> {
+    ) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
                 year: None,
@@ -1309,39 +1405,36 @@ impl GDay {
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<DateTime> for GDay {
-    type Error = DateTimeError;
-
+impl From<DateTime> for GDay {
     #[inline]
-    fn try_from(date_time: DateTime) -> Result<Self, DateTimeError> {
+    fn from(date_time: DateTime) -> Self {
         Self::new(date_time.day(), date_time.timezone_offset())
+            .expect("Casting from xsd:dateTime to xsd:gDay can't fail")
     }
 }
 
 /// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
-impl TryFrom<Date> for GDay {
-    type Error = DateTimeError;
-
+impl From<Date> for GDay {
     #[inline]
-    fn try_from(date: Date) -> Result<Self, DateTimeError> {
+    fn from(date: Date) -> Self {
         Self::new(date.day(), date.timezone_offset())
+            .expect("Casting from xsd:date to xsd:gDay can't fail")
     }
 }
 
-impl TryFrom<GMonthDay> for GDay {
-    type Error = DateTimeError;
-
+impl From<GMonthDay> for GDay {
     #[inline]
-    fn try_from(month_day: GMonthDay) -> Result<Self, DateTimeError> {
+    fn from(month_day: GMonthDay) -> Self {
         Self::new(month_day.day(), month_day.timezone_offset())
+            .expect("Casting from xsd:gMonthDay to xsd:gDay can't fail")
     }
 }
 
 impl FromStr for GDay {
-    type Err = XsdParseError;
+    type Err = ParseDateTimeError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_g_day(input)
+    fn from_str(input: &str) -> Result<Self, ParseDateTimeError> {
+        ensure_complete(input, g_day_lexical_rep)
     }
 }
 
@@ -1367,14 +1460,16 @@ pub struct TimezoneOffset {
 impl TimezoneOffset {
     /// From offset in minute with respect to UTC
     #[inline]
-    pub fn new(offset_in_minutes: i16) -> Result<Self, DateTimeError> {
+    pub fn new(offset_in_minutes: i16) -> Result<Self, InvalidTimezoneError> {
         let value = Self {
             offset: offset_in_minutes,
         };
         if Self::MIN <= value && value <= Self::MAX {
             Ok(value)
         } else {
-            Err(DATE_TIME_OVERFLOW)
+            Err(InvalidTimezoneError {
+                offset_in_minutes: offset_in_minutes.into(),
+            })
         }
     }
 
@@ -1398,31 +1493,34 @@ impl TimezoneOffset {
 }
 
 impl TryFrom<DayTimeDuration> for TimezoneOffset {
-    type Error = DateTimeError;
+    type Error = InvalidTimezoneError;
 
     #[inline]
-    fn try_from(value: DayTimeDuration) -> Result<Self, DateTimeError> {
+    fn try_from(value: DayTimeDuration) -> Result<Self, InvalidTimezoneError> {
+        let offset_in_minutes = value.minutes() + value.hours() * 60;
         let result = Self::new(
-            (value.minutes() + value.hours() * 60)
+            offset_in_minutes
                 .try_into()
-                .map_err(|_| DATE_TIME_OVERFLOW)?,
+                .map_err(|_| InvalidTimezoneError { offset_in_minutes })?,
         )?;
         if DayTimeDuration::from(result) == value {
             Ok(result)
         } else {
             // The value is not an integral number of minutes or overflow problems
-            Err(DATE_TIME_OVERFLOW)
+            Err(InvalidTimezoneError { offset_in_minutes })
         }
     }
 }
 
 impl TryFrom<Duration> for TimezoneOffset {
-    type Error = DateTimeError;
+    type Error = InvalidTimezoneError;
 
     #[inline]
-    fn try_from(value: Duration) -> Result<Self, DateTimeError> {
+    fn try_from(value: Duration) -> Result<Self, InvalidTimezoneError> {
         DayTimeDuration::try_from(value)
-            .map_err(|_| DATE_TIME_OVERFLOW)?
+            .map_err(|_| InvalidTimezoneError {
+                offset_in_minutes: 0,
+            })?
             .try_into()
     }
 }
@@ -1522,28 +1620,18 @@ impl Hash for Timestamp {
 
 impl Timestamp {
     #[inline]
-    fn new(props: &DateTimeSevenPropertyModel) -> Result<Self, DateTimeError> {
-        // Validation
-        if let (Some(day), Some(month)) = (props.day, props.month) {
-            // Constraint: Day-of-month Values
-            if day > days_in_month(props.year, month) {
-                return Err(DateTimeError {
-                    kind: DateTimeErrorKind::InvalidDayOfMonth { day, month },
-                });
-            }
-        }
-
+    fn new(props: &DateTimeSevenPropertyModel) -> Result<Self, DateTimeOverflowError> {
         Ok(Self {
             timezone_offset: props.timezone_offset,
-            value: time_on_timeline(props).ok_or(DATE_TIME_OVERFLOW)?,
+            value: time_on_timeline(props).ok_or(DateTimeOverflowError)?,
         })
     }
 
     #[inline]
-    fn now() -> Result<Self, DateTimeError> {
+    fn now() -> Self {
         Self::new(
             &date_time_plus_duration(
-                since_unix_epoch()?,
+                since_unix_epoch(),
                 &DateTimeSevenPropertyModel {
                     year: Some(1970),
                     month: Some(1),
@@ -1554,8 +1642,9 @@ impl Timestamp {
                     timezone_offset: Some(TimezoneOffset::UTC),
                 },
             )
-            .ok_or(DATE_TIME_OVERFLOW)?,
+            .expect("The current time seems way in the future, it's strange"),
         )
+        .expect("The current time seems way in the future, it's strange")
     }
 
     #[inline]
@@ -1669,7 +1758,11 @@ impl Timestamp {
     #[inline]
     #[must_use]
     fn second(&self) -> Decimal {
-        self.value.checked_rem_euclid(60).unwrap().abs()
+        self.value
+            .checked_rem_euclid(60)
+            .unwrap()
+            .checked_abs()
+            .unwrap()
     }
 
     #[inline]
@@ -1755,13 +1848,23 @@ impl Timestamp {
     pub fn is_identical_with(self, other: Self) -> bool {
         self.value == other.value && self.timezone_offset == other.timezone_offset
     }
+
+    pub const MIN: Self = Self {
+        value: Decimal::MIN,
+        timezone_offset: Some(TimezoneOffset::MIN),
+    };
+
+    pub const MAX: Self = Self {
+        value: Decimal::MAX,
+        timezone_offset: Some(TimezoneOffset::MAX),
+    };
 }
 
 #[cfg(feature = "custom-now")]
 #[allow(unsafe_code)]
-pub fn since_unix_epoch() -> Result<Duration, DateTimeError> {
+pub fn since_unix_epoch() -> Duration {
     extern "Rust" {
-        fn custom_ox_now() -> Result<Duration, DateTimeError>;
+        fn custom_ox_now() -> Duration;
     }
 
     unsafe { custom_ox_now() }
@@ -1773,25 +1876,26 @@ pub fn since_unix_epoch() -> Result<Duration, DateTimeError> {
     target_family = "wasm",
     target_os = "unknown"
 ))]
-fn since_unix_epoch() -> Result<Duration, DateTimeError> {
-    Ok(Duration::new(
+fn since_unix_epoch() -> Duration {
+    Duration::new(
         0,
         Decimal::try_from(crate::Double::from(js_sys::Date::now() / 1000.))
-            .map_err(|_| DATE_TIME_OVERFLOW)?,
-    ))
+            .expect("The current time seems way in the future, it's strange"),
+    )
 }
 
 #[cfg(not(any(
     feature = "custom-now",
     all(feature = "js", target_family = "wasm", target_os = "unknown")
 )))]
-fn since_unix_epoch() -> Result<Duration, DateTimeError> {
+fn since_unix_epoch() -> Duration {
     use std::time::SystemTime;
 
     SystemTime::now()
-        .duration_since(SystemTime::UNIX_EPOCH)?
+        .duration_since(SystemTime::UNIX_EPOCH)
+        .expect("System time before UNIX epoch")
         .try_into()
-        .map_err(|_| DATE_TIME_OVERFLOW)
+        .expect("The current time seems way in the future, it's strange")
 }
 
 /// The [normalizeMonth](https://www.w3.org/TR/xmlschema11-2/#f-dt-normMo) function
@@ -1933,61 +2037,424 @@ fn time_on_timeline(props: &DateTimeSevenPropertyModel) -> Option<Decimal> {
     .checked_add(se)
 }
 
-/// An error when doing [`DateTime`] operations.
+/// A parsing error
 #[derive(Debug, Clone)]
-pub struct DateTimeError {
-    kind: DateTimeErrorKind,
+pub struct ParseDateTimeError {
+    kind: ParseDateTimeErrorKind,
 }
 
 #[derive(Debug, Clone)]
-enum DateTimeErrorKind {
+enum ParseDateTimeErrorKind {
     InvalidDayOfMonth { day: u8, month: u8 },
-    Overflow,
-    SystemTime(SystemTimeError),
+    Overflow(DateTimeOverflowError),
+    InvalidTimezone(InvalidTimezoneError),
+    Message(&'static str),
 }
 
-impl fmt::Display for DateTimeError {
-    #[inline]
+impl fmt::Display for ParseDateTimeError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match &self.kind {
-            DateTimeErrorKind::InvalidDayOfMonth { day, month } => {
+            ParseDateTimeErrorKind::InvalidDayOfMonth { day, month } => {
                 write!(f, "{day} is not a valid day of {month}")
             }
-            DateTimeErrorKind::Overflow => write!(f, "Overflow during date time normalization"),
-            DateTimeErrorKind::SystemTime(error) => error.fmt(f),
+            ParseDateTimeErrorKind::Overflow(error) => error.fmt(f),
+            ParseDateTimeErrorKind::InvalidTimezone(error) => error.fmt(f),
+            ParseDateTimeErrorKind::Message(msg) => write!(f, "{msg}"),
         }
     }
 }
 
-impl Error for DateTimeError {
-    #[inline]
-    fn source(&self) -> Option<&(dyn Error + 'static)> {
-        match &self.kind {
-            DateTimeErrorKind::SystemTime(error) => Some(error),
-            _ => None,
+impl ParseDateTimeError {
+    const fn msg(message: &'static str) -> Self {
+        Self {
+            kind: ParseDateTimeErrorKind::Message(message),
         }
     }
 }
 
-impl From<SystemTimeError> for DateTimeError {
-    #[inline]
-    fn from(error: SystemTimeError) -> Self {
-        Self {
-            kind: DateTimeErrorKind::SystemTime(error),
+impl Error for ParseDateTimeError {}
+
+// [16]   dateTimeLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag 'T' ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
+fn date_time_lexical_rep(input: &str) -> Result<(DateTime, &str), ParseDateTimeError> {
+    let (year, input) = year_frag(input)?;
+    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
+    let (month, input) = month_frag(input)?;
+    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
+    let (day, input) = day_frag(input)?;
+    let input = expect_char(input, 'T', "The date and time must be separated by 'T'")?;
+    let (hour, input) = hour_frag(input)?;
+    let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
+    let (minute, input) = minute_frag(input)?;
+    let input = expect_char(
+        input,
+        ':',
+        "The minutes and seconds must be separated by ':'",
+    )?;
+    let (second, input) = second_frag(input)?;
+    // We validate 24:00:00
+    if hour == 24 && minute != 0 && second != Decimal::from(0) {
+        return Err(ParseDateTimeError::msg(
+            "Times are not allowed to be after 24:00:00",
+        ));
+    }
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    validate_day_of_month(Some(year), month, day)?;
+    Ok((
+        DateTime::new(year, month, day, hour, minute, second, timezone_offset)?,
+        input,
+    ))
+}
+
+// [17]   timeLexicalRep ::= ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
+fn time_lexical_rep(input: &str) -> Result<(Time, &str), ParseDateTimeError> {
+    let (hour, input) = hour_frag(input)?;
+    let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
+    let (minute, input) = minute_frag(input)?;
+    let input = expect_char(
+        input,
+        ':',
+        "The minutes and seconds must be separated by ':'",
+    )?;
+    let (second, input) = second_frag(input)?;
+    // We validate 24:00:00
+    if hour == 24 && minute != 0 && second != Decimal::from(0) {
+        return Err(ParseDateTimeError::msg(
+            "Times are not allowed to be after 24:00:00",
+        ));
+    }
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    Ok((Time::new(hour, minute, second, timezone_offset)?, input))
+}
+
+// [18]   dateLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag timezoneFrag?   Constraint:  Day-of-month Representations
+fn date_lexical_rep(input: &str) -> Result<(Date, &str), ParseDateTimeError> {
+    let (year, input) = year_frag(input)?;
+    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
+    let (month, input) = month_frag(input)?;
+    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
+    let (day, input) = day_frag(input)?;
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    validate_day_of_month(Some(year), month, day)?;
+    Ok((Date::new(year, month, day, timezone_offset)?, input))
+}
+
+// [19]   gYearMonthLexicalRep ::= yearFrag '-' monthFrag timezoneFrag?
+fn g_year_month_lexical_rep(input: &str) -> Result<(GYearMonth, &str), ParseDateTimeError> {
+    let (year, input) = year_frag(input)?;
+    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
+    let (month, input) = month_frag(input)?;
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    Ok((GYearMonth::new(year, month, timezone_offset)?, input))
+}
+
+// [20]   gYearLexicalRep ::= yearFrag timezoneFrag?
+fn g_year_lexical_rep(input: &str) -> Result<(GYear, &str), ParseDateTimeError> {
+    let (year, input) = year_frag(input)?;
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    Ok((GYear::new(year, timezone_offset)?, input))
+}
+
+// [21]   gMonthDayLexicalRep ::= '--' monthFrag '-' dayFrag timezoneFrag?   Constraint:  Day-of-month Representations
+fn g_month_day_lexical_rep(input: &str) -> Result<(GMonthDay, &str), ParseDateTimeError> {
+    let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
+    let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
+    let (month, input) = month_frag(input)?;
+    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
+    let (day, input) = day_frag(input)?;
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    validate_day_of_month(None, month, day)?;
+    Ok((GMonthDay::new(month, day, timezone_offset)?, input))
+}
+
+// [22]   gDayLexicalRep ::= '---' dayFrag timezoneFrag?
+fn g_day_lexical_rep(input: &str) -> Result<(GDay, &str), ParseDateTimeError> {
+    let input = expect_char(input, '-', "gDay values must start with '---'")?;
+    let input = expect_char(input, '-', "gDay values must start with '---'")?;
+    let input = expect_char(input, '-', "gDay values must start with '---'")?;
+    let (day, input) = day_frag(input)?;
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    Ok((GDay::new(day, timezone_offset)?, input))
+}
+
+// [23]   gMonthLexicalRep ::= '--' monthFrag timezoneFrag?
+fn g_month_lexical_rep(input: &str) -> Result<(GMonth, &str), ParseDateTimeError> {
+    let input = expect_char(input, '-', "gMonth values must start with '--'")?;
+    let input = expect_char(input, '-', "gMonth values must start with '--'")?;
+    let (month, input) = month_frag(input)?;
+    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
+    Ok((GMonth::new(month, timezone_offset)?, input))
+}
+
+// [56]   yearFrag ::= '-'? (([1-9] digit digit digit+)) | ('0' digit digit digit))
+fn year_frag(input: &str) -> Result<(i64, &str), ParseDateTimeError> {
+    let (sign, input) = if let Some(left) = input.strip_prefix('-') {
+        (-1, left)
+    } else {
+        (1, input)
+    };
+    let (number_str, input) = integer_prefix(input);
+    if number_str.len() < 4 {
+        return Err(ParseDateTimeError::msg(
+            "The year should be encoded on 4 digits",
+        ));
+    }
+    if number_str.len() > 4 && number_str.starts_with('0') {
+        return Err(ParseDateTimeError::msg(
+            "The years value must not start with 0 if it can be encoded in at least 4 digits",
+        ));
+    }
+    let number = i64::from_str(number_str).expect("valid integer");
+    Ok((sign * number, input))
+}
+
+// [57]   monthFrag ::= ('0' [1-9]) | ('1' [0-2])
+fn month_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
+    let (number_str, input) = integer_prefix(input);
+    if number_str.len() != 2 {
+        return Err(ParseDateTimeError::msg(
+            "Month must be encoded with two digits",
+        ));
+    }
+    let number = u8::from_str(number_str).expect("valid integer");
+    if !(1..=12).contains(&number) {
+        return Err(ParseDateTimeError::msg("Month must be between 01 and 12"));
+    }
+    Ok((number, input))
+}
+
+// [58]   dayFrag ::= ('0' [1-9]) | ([12] digit) | ('3' [01])
+fn day_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
+    let (number_str, input) = integer_prefix(input);
+    if number_str.len() != 2 {
+        return Err(ParseDateTimeError::msg(
+            "Day must be encoded with two digits",
+        ));
+    }
+    let number = u8::from_str(number_str).expect("valid integer");
+    if !(1..=31).contains(&number) {
+        return Err(ParseDateTimeError::msg("Day must be between 01 and 31"));
+    }
+    Ok((number, input))
+}
+
+// [59]   hourFrag ::= ([01] digit) | ('2' [0-3])
+// We also allow 24 for ease of parsing
+fn hour_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
+    let (number_str, input) = integer_prefix(input);
+    if number_str.len() != 2 {
+        return Err(ParseDateTimeError::msg(
+            "Hours must be encoded with two digits",
+        ));
+    }
+    let number = u8::from_str(number_str).expect("valid integer");
+    if !(0..=24).contains(&number) {
+        return Err(ParseDateTimeError::msg("Hours must be between 00 and 24"));
+    }
+    Ok((number, input))
+}
+
+// [60]   minuteFrag ::= [0-5] digit
+fn minute_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
+    let (number_str, input) = integer_prefix(input);
+    if number_str.len() != 2 {
+        return Err(ParseDateTimeError::msg(
+            "Minutes must be encoded with two digits",
+        ));
+    }
+    let number = u8::from_str(number_str).expect("valid integer");
+    if !(0..=59).contains(&number) {
+        return Err(ParseDateTimeError::msg("Minutes must be between 00 and 59"));
+    }
+    Ok((number, input))
+}
+
+// [61]   secondFrag ::= ([0-5] digit) ('.' digit+)?
+fn second_frag(input: &str) -> Result<(Decimal, &str), ParseDateTimeError> {
+    let (number_str, input) = decimal_prefix(input);
+    let (before_dot_str, _) = number_str.split_once('.').unwrap_or((number_str, ""));
+    if before_dot_str.len() != 2 {
+        return Err(ParseDateTimeError::msg(
+            "Seconds must be encoded with two digits",
+        ));
+    }
+    let number = Decimal::from_str(number_str)
+        .map_err(|_| ParseDateTimeError::msg("The second precision is too large"))?;
+    if number < Decimal::from(0) || number >= Decimal::from(60) {
+        return Err(ParseDateTimeError::msg("Seconds must be between 00 and 60"));
+    }
+    if number_str.ends_with('.') {
+        return Err(ParseDateTimeError::msg(
+            "Seconds are not allowed to end with a dot",
+        ));
+    }
+    Ok((number, input))
+}
+
+// [63]   timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00')
+fn timezone_frag(input: &str) -> Result<(TimezoneOffset, &str), ParseDateTimeError> {
+    if let Some(left) = input.strip_prefix('Z') {
+        return Ok((TimezoneOffset::UTC, left));
+    }
+    let (sign, input) = if let Some(left) = input.strip_prefix('-') {
+        (-1, left)
+    } else if let Some(left) = input.strip_prefix('+') {
+        (1, left)
+    } else {
+        (1, input)
+    };
+
+    let (hour_str, input) = integer_prefix(input);
+    if hour_str.len() != 2 {
+        return Err(ParseDateTimeError::msg(
+            "The timezone hours must be encoded with two digits",
+        ));
+    }
+    let hours = i16::from_str(hour_str).expect("valid integer");
+
+    let input = expect_char(
+        input,
+        ':',
+        "The timezone hours and minutes must be separated by ':'",
+    )?;
+    let (minutes, input) = minute_frag(input)?;
+
+    if hours > 13 && !(hours == 14 && minutes == 0) {
+        return Err(ParseDateTimeError::msg(
+            "The timezone hours must be between 00 and 13",
+        ));
+    }
+
+    Ok((
+        TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes))).map_err(|e| {
+            ParseDateTimeError {
+                kind: ParseDateTimeErrorKind::InvalidTimezone(e),
+            }
+        })?,
+        input,
+    ))
+}
+
+fn ensure_complete<T>(
+    input: &str,
+    parse: impl FnOnce(&str) -> Result<(T, &str), ParseDateTimeError>,
+) -> Result<T, ParseDateTimeError> {
+    let (result, left) = parse(input)?;
+    if !left.is_empty() {
+        return Err(ParseDateTimeError::msg("Unrecognized value suffix"));
+    }
+    Ok(result)
+}
+
+fn expect_char<'a>(
+    input: &'a str,
+    constant: char,
+    error_message: &'static str,
+) -> Result<&'a str, ParseDateTimeError> {
+    if let Some(left) = input.strip_prefix(constant) {
+        Ok(left)
+    } else {
+        Err(ParseDateTimeError::msg(error_message))
+    }
+}
+
+fn integer_prefix(input: &str) -> (&str, &str) {
+    let mut end = input.len();
+    for (i, c) in input.char_indices() {
+        if !c.is_ascii_digit() {
+            end = i;
+            break;
+        }
+    }
+    input.split_at(end)
+}
+
+fn decimal_prefix(input: &str) -> (&str, &str) {
+    let mut end = input.len();
+    let mut dot_seen = false;
+    for (i, c) in input.char_indices() {
+        if c.is_ascii_digit() {
+            // Ok
+        } else if c == '.' && !dot_seen {
+            dot_seen = true;
+        } else {
+            end = i;
+            break;
+        }
+    }
+    input.split_at(end)
+}
+
+fn optional_end<T>(
+    input: &str,
+    parse: impl FnOnce(&str) -> Result<(T, &str), ParseDateTimeError>,
+) -> Result<(Option<T>, &str), ParseDateTimeError> {
+    Ok(if input.is_empty() {
+        (None, input)
+    } else {
+        let (result, input) = parse(input)?;
+        (Some(result), input)
+    })
+}
+
+fn validate_day_of_month(year: Option<i64>, month: u8, day: u8) -> Result<(), ParseDateTimeError> {
+    // Constraint: Day-of-month Values
+    if day > days_in_month(year, month) {
+        return Err(ParseDateTimeError {
+            kind: ParseDateTimeErrorKind::InvalidDayOfMonth { day, month },
+        });
+    }
+    Ok(())
+}
+
+/// An overflow during [`DateTime`]-related operations.
+///
+/// Matches XPath [`FODT0001` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001).
+#[derive(Debug, Clone, Copy)]
+pub struct DateTimeOverflowError;
+
+impl fmt::Display for DateTimeOverflowError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "overflow during xsd:dateTime computation")
+    }
+}
+
+impl Error for DateTimeOverflowError {}
+
+impl From<DateTimeOverflowError> for ParseDateTimeError {
+    fn from(error: DateTimeOverflowError) -> Self {
+        ParseDateTimeError {
+            kind: ParseDateTimeErrorKind::Overflow(error),
         }
     }
 }
 
-const DATE_TIME_OVERFLOW: DateTimeError = DateTimeError {
-    kind: DateTimeErrorKind::Overflow,
-};
+/// The value provided as timezone is not valid.
+///
+/// Matches XPath [`FODT0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0003).
+#[derive(Debug, Clone, Copy)]
+pub struct InvalidTimezoneError {
+    offset_in_minutes: i64,
+}
+
+impl fmt::Display for InvalidTimezoneError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "invalid timezone offset {}:{}",
+            self.offset_in_minutes / 60,
+            self.offset_in_minutes.abs() % 60
+        )
+    }
+}
+
+impl Error for InvalidTimezoneError {}
 
 #[cfg(test)]
 mod tests {
     use super::*;
 
     #[test]
-    fn from_str() -> Result<(), XsdParseError> {
+    fn from_str() -> Result<(), ParseDateTimeError> {
         assert_eq!(Time::from_str("00:00:00Z")?.to_string(), "00:00:00Z");
         assert_eq!(Time::from_str("00:00:00+00:00")?.to_string(), "00:00:00Z");
         assert_eq!(Time::from_str("00:00:00-00:00")?.to_string(), "00:00:00Z");
@@ -2148,11 +2615,57 @@ mod tests {
         assert!(GYear::from_str("02020").is_err());
         assert!(GYear::from_str("+2020").is_err());
         assert!(GYear::from_str("33").is_err());
+
+        assert_eq!(Time::from_str("00:00:00+14:00")?, Time::MIN);
+        assert_eq!(Time::from_str("24:00:00-14:00")?, Time::MAX);
+        Ok(())
+    }
+
+    #[test]
+    fn to_be_bytes() -> Result<(), ParseDateTimeError> {
+        assert_eq!(
+            DateTime::from_be_bytes(DateTime::MIN.to_be_bytes()),
+            DateTime::MIN
+        );
+        assert_eq!(
+            DateTime::from_be_bytes(DateTime::MAX.to_be_bytes()),
+            DateTime::MAX
+        );
+        assert_eq!(
+            DateTime::from_be_bytes(DateTime::from_str("2022-01-03T01:02:03")?.to_be_bytes()),
+            DateTime::from_str("2022-01-03T01:02:03")?
+        );
+        assert_eq!(Date::from_be_bytes(Date::MIN.to_be_bytes()), Date::MIN);
+        assert_eq!(Date::from_be_bytes(Date::MAX.to_be_bytes()), Date::MAX);
+        assert_eq!(
+            Date::from_be_bytes(Date::from_str("2022-01-03")?.to_be_bytes()),
+            Date::from_str("2022-01-03")?
+        );
+        assert_eq!(Time::from_be_bytes(Time::MIN.to_be_bytes()), Time::MIN);
+        assert_eq!(Time::from_be_bytes(Time::MAX.to_be_bytes()), Time::MAX);
+        assert_eq!(
+            Time::from_be_bytes(Time::from_str("01:02:03")?.to_be_bytes()),
+            Time::from_str("01:02:03")?
+        );
+        assert_eq!(
+            Time::from_be_bytes(Time::from_str("01:02:03")?.to_be_bytes()),
+            Time::from_str("01:02:03")?
+        );
+        assert_eq!(
+            GYearMonth::from_be_bytes(GYearMonth::MIN.to_be_bytes()),
+            GYearMonth::MIN
+        );
+        assert_eq!(
+            GYearMonth::from_be_bytes(GYearMonth::MAX.to_be_bytes()),
+            GYearMonth::MAX
+        );
+        assert_eq!(GYear::from_be_bytes(GYear::MIN.to_be_bytes()), GYear::MIN);
+        assert_eq!(GYear::from_be_bytes(GYear::MAX.to_be_bytes()), GYear::MAX);
         Ok(())
     }
 
     #[test]
-    fn equals() -> Result<(), XsdParseError> {
+    fn equals() -> Result<(), ParseDateTimeError> {
         assert_eq!(
             DateTime::from_str("2002-04-02T12:00:00-01:00")?,
             DateTime::from_str("2002-04-02T17:00:00+04:00")?
@@ -2253,7 +2766,7 @@ mod tests {
 
     #[test]
     #[allow(clippy::neg_cmp_op_on_partial_ord)]
-    fn cmp() -> Result<(), XsdParseError> {
+    fn cmp() -> Result<(), ParseDateTimeError> {
         assert!(Date::from_str("2004-12-25Z")? < Date::from_str("2004-12-25-05:00")?);
         assert!(!(Date::from_str("2004-12-25-12:00")? < Date::from_str("2004-12-26+12:00")?));
 
@@ -2280,7 +2793,7 @@ mod tests {
     }
 
     #[test]
-    fn year() -> Result<(), XsdParseError> {
+    fn year() -> Result<(), ParseDateTimeError> {
         assert_eq!(
             DateTime::from_str("1999-05-31T13:20:00-05:00")?.year(),
             1999
@@ -2303,7 +2816,7 @@ mod tests {
     }
 
     #[test]
-    fn month() -> Result<(), XsdParseError> {
+    fn month() -> Result<(), ParseDateTimeError> {
         assert_eq!(DateTime::from_str("1999-05-31T13:20:00-05:00")?.month(), 5);
         assert_eq!(DateTime::from_str("1999-12-31T19:20:00-05:00")?.month(), 12);
 
@@ -2317,7 +2830,7 @@ mod tests {
     }
 
     #[test]
-    fn day() -> Result<(), XsdParseError> {
+    fn day() -> Result<(), ParseDateTimeError> {
         assert_eq!(DateTime::from_str("1999-05-31T13:20:00-05:00")?.day(), 31);
         assert_eq!(DateTime::from_str("1999-12-31T20:00:00-05:00")?.day(), 31);
 
@@ -2330,7 +2843,7 @@ mod tests {
     }
 
     #[test]
-    fn hour() -> Result<(), XsdParseError> {
+    fn hour() -> Result<(), ParseDateTimeError> {
         assert_eq!(DateTime::from_str("1999-05-31T08:20:00-05:00")?.hour(), 8);
         assert_eq!(DateTime::from_str("1999-12-31T21:20:00-05:00")?.hour(), 21);
         assert_eq!(DateTime::from_str("1999-12-31T12:00:00")?.hour(), 12);
@@ -2344,7 +2857,7 @@ mod tests {
     }
 
     #[test]
-    fn minute() -> Result<(), XsdParseError> {
+    fn minute() -> Result<(), ParseDateTimeError> {
         assert_eq!(
             DateTime::from_str("1999-05-31T13:20:00-05:00")?.minute(),
             20
@@ -2359,7 +2872,7 @@ mod tests {
     }
 
     #[test]
-    fn second() -> Result<(), XsdParseError> {
+    fn second() -> Result<(), Box<dyn Error>> {
         assert_eq!(
             DateTime::from_str("1999-05-31T13:20:00-05:00")?.second(),
             Decimal::from(0)
@@ -2373,7 +2886,7 @@ mod tests {
     }
 
     #[test]
-    fn timezone() -> Result<(), XsdParseError> {
+    fn timezone() -> Result<(), Box<dyn Error>> {
         assert_eq!(
             DateTime::from_str("1999-05-31T13:20:00-05:00")?.timezone(),
             Some(DayTimeDuration::from_str("-PT5H")?)
@@ -2402,7 +2915,7 @@ mod tests {
     }
 
     #[test]
-    fn sub() -> Result<(), XsdParseError> {
+    fn sub() -> Result<(), Box<dyn Error>> {
         assert_eq!(
             DateTime::from_str("2000-10-30T06:12:00-05:00")?
                 .checked_sub(DateTime::from_str("1999-11-28T09:00:00Z")?),
@@ -2442,7 +2955,7 @@ mod tests {
     }
 
     #[test]
-    fn add_duration() -> Result<(), XsdParseError> {
+    fn add_duration() -> Result<(), Box<dyn Error>> {
         assert_eq!(
             DateTime::from_str("2000-01-12T12:13:14Z")?
                 .checked_add_duration(Duration::from_str("P1Y3M5DT7H10M3.3S")?),
@@ -2506,7 +3019,7 @@ mod tests {
     }
 
     #[test]
-    fn sub_duration() -> Result<(), XsdParseError> {
+    fn sub_duration() -> Result<(), Box<dyn Error>> {
         assert_eq!(
             DateTime::from_str("2000-10-30T11:12:00")?
                 .checked_sub_duration(Duration::from_str("P1Y2M")?),
@@ -2548,17 +3061,15 @@ mod tests {
     }
 
     #[test]
-    fn adjust() -> Result<(), XsdParseError> {
+    fn adjust() -> Result<(), Box<dyn Error>> {
         assert_eq!(
-            DateTime::from_str("2002-03-07T10:00:00-07:00")?.adjust(Some(
-                DayTimeDuration::from_str("PT10H")?.try_into().unwrap()
-            )),
+            DateTime::from_str("2002-03-07T10:00:00-07:00")?
+                .adjust(Some(DayTimeDuration::from_str("PT10H")?.try_into()?)),
             Some(DateTime::from_str("2002-03-08T03:00:00+10:00")?)
         );
         assert_eq!(
-            DateTime::from_str("2002-03-07T00:00:00+01:00")?.adjust(Some(
-                DayTimeDuration::from_str("-PT8H")?.try_into().unwrap()
-            )),
+            DateTime::from_str("2002-03-07T00:00:00+01:00")?
+                .adjust(Some(DayTimeDuration::from_str("-PT8H")?.try_into()?)),
             Some(DateTime::from_str("2002-03-06T15:00:00-08:00")?)
         );
         assert_eq!(
@@ -2571,18 +3082,13 @@ mod tests {
         );
 
         assert_eq!(
-            Date::from_str("2002-03-07")?.adjust(Some(
-                DayTimeDuration::from_str("-PT10H")?.try_into().unwrap()
-            )),
+            Date::from_str("2002-03-07")?
+                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
             Some(Date::from_str("2002-03-07-10:00")?)
         );
         assert_eq!(
-            Date::from_str("2002-03-07-07:00")?.adjust(Some(
-                DayTimeDuration::from_str("-PT10H")
-                    .unwrap()
-                    .try_into()
-                    .unwrap()
-            )),
+            Date::from_str("2002-03-07-07:00")?
+                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
             Some(Date::from_str("2002-03-06-10:00")?)
         );
         assert_eq!(
@@ -2595,15 +3101,13 @@ mod tests {
         );
 
         assert_eq!(
-            Time::from_str("10:00:00")?.adjust(Some(
-                DayTimeDuration::from_str("-PT10H")?.try_into().unwrap()
-            )),
+            Time::from_str("10:00:00")?
+                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
             Some(Time::from_str("10:00:00-10:00")?)
         );
         assert_eq!(
-            Time::from_str("10:00:00-07:00")?.adjust(Some(
-                DayTimeDuration::from_str("-PT10H")?.try_into().unwrap()
-            )),
+            Time::from_str("10:00:00-07:00")?
+                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
             Some(Time::from_str("07:00:00-10:00")?)
         );
         assert_eq!(
@@ -2615,35 +3119,78 @@ mod tests {
             Some(Time::from_str("10:00:00")?)
         );
         assert_eq!(
-            Time::from_str("10:00:00-07:00")?.adjust(Some(
-                DayTimeDuration::from_str("PT10H")?.try_into().unwrap()
-            )),
+            Time::from_str("10:00:00-07:00")?
+                .adjust(Some(DayTimeDuration::from_str("PT10H")?.try_into()?)),
             Some(Time::from_str("03:00:00+10:00")?)
         );
         Ok(())
     }
 
+    #[test]
+    fn time_from_datetime() -> Result<(), ParseDateTimeError> {
+        assert_eq!(
+            Time::from(DateTime::MIN),
+            Time::from_str("19:51:08.312696284115894272-14:00")?
+        );
+        assert_eq!(
+            Time::from(DateTime::MAX),
+            Time::from_str("04:08:51.687303715884105727+14:00")?
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn date_from_datetime() -> Result<(), Box<dyn Error>> {
+        assert_eq!(
+            Date::try_from(
+                DateTime::MIN
+                    .checked_add_day_time_duration(DayTimeDuration::from_str("P1D")?)
+                    .unwrap()
+            )?,
+            Date::MIN
+        );
+        assert_eq!(Date::try_from(DateTime::MAX)?, Date::MAX);
+        Ok(())
+    }
+
+    #[test]
+    fn g_year_month_from_date() -> Result<(), ParseDateTimeError> {
+        assert_eq!(GYearMonth::from(Date::MIN), GYearMonth::MIN);
+        assert_eq!(GYearMonth::from(Date::MAX), GYearMonth::MAX);
+        Ok(())
+    }
+
+    #[test]
+    fn g_year_from_g_year_month() -> Result<(), ParseDateTimeError> {
+        assert_eq!(GYear::try_from(GYearMonth::MIN)?, GYear::MIN);
+        assert_eq!(
+            GYear::try_from(GYearMonth::from_str("5391559471918-12+14:00")?)?,
+            GYear::MAX
+        );
+        Ok(())
+    }
+
     #[cfg(feature = "custom-now")]
     #[test]
     fn custom_now() {
         #[no_mangle]
-        fn custom_ox_now() -> Result<Duration, DateTimeError> {
-            Ok(Duration::default())
+        fn custom_ox_now() -> Duration {
+            Duration::default()
         }
-        assert!(DateTime::now().is_ok());
+        DateTime::now();
     }
 
     #[cfg(not(feature = "custom-now"))]
     #[test]
-    fn now() -> Result<(), XsdParseError> {
-        let now = DateTime::now().unwrap();
+    fn now() -> Result<(), ParseDateTimeError> {
+        let now = DateTime::now();
         assert!(DateTime::from_str("2022-01-01T00:00:00Z")? < now);
         assert!(now < DateTime::from_str("2100-01-01T00:00:00Z")?);
         Ok(())
     }
 
     #[test]
-    fn minimally_conformant() -> Result<(), XsdParseError> {
+    fn minimally_conformant() -> Result<(), ParseDateTimeError> {
         // All minimally conforming processors must support nonnegative year values less than 10000
         // (i.e., those expressible with four digits) in all datatypes which
         // use the seven-property model defined in The Seven-property Model (§D.2.1)
diff --git a/lib/oxsdatatypes/src/decimal.rs b/lib/oxsdatatypes/src/decimal.rs
index f3880dcb..6a309108 100644
--- a/lib/oxsdatatypes/src/decimal.rs
+++ b/lib/oxsdatatypes/src/decimal.rs
@@ -1,4 +1,4 @@
-use crate::{Boolean, Double, Float, Integer};
+use crate::{Boolean, Double, Float, Integer, TooLargeForIntegerError};
 use std::error::Error;
 use std::fmt;
 use std::fmt::Write;
@@ -21,15 +21,20 @@ pub struct Decimal {
 impl Decimal {
     /// Constructs the decimal i / 10^n
     #[inline]
-    pub fn new(i: i128, n: u32) -> Result<Self, DecimalOverflowError> {
-        let shift = DECIMAL_PART_DIGITS
-            .checked_sub(n)
-            .ok_or(DecimalOverflowError)?;
-        Ok(Self {
-            value: i
-                .checked_mul(10_i128.pow(shift))
-                .ok_or(DecimalOverflowError)?,
-        })
+    pub const fn new(i: i128, n: u32) -> Result<Self, TooLargeForDecimalError> {
+        let Some(shift) = DECIMAL_PART_DIGITS.checked_sub(n) else {
+            return Err(TooLargeForDecimalError);
+        };
+        let Some(value) = i.checked_mul(10_i128.pow(shift)) else {
+             return Err(TooLargeForDecimalError);
+         };
+        Ok(Self { value })
+    }
+
+    pub(crate) const fn new_from_i128_unchecked(value: i128) -> Self {
+        Self {
+            value: value * DECIMAL_PART_POW,
+        }
     }
 
     #[inline]
@@ -47,6 +52,8 @@ impl Decimal {
     }
 
     /// [op:numeric-add](https://www.w3.org/TR/xpath-functions-31/#func-numeric-add)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -56,6 +63,8 @@ impl Decimal {
     }
 
     /// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -65,6 +74,8 @@ impl Decimal {
     }
 
     /// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_mul(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -98,6 +109,8 @@ impl Decimal {
     }
 
     /// [op:numeric-divide](https://www.w3.org/TR/xpath-functions-31/#func-numeric-divide)
+    ///
+    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_div(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -132,6 +145,8 @@ impl Decimal {
     }
 
     /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod)
+    ///
+    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_rem(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -140,6 +155,9 @@ impl Decimal {
         })
     }
 
+    /// Euclidean remainder
+    ///
+    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_rem_euclid(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -149,6 +167,8 @@ impl Decimal {
     }
 
     /// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_neg(self) -> Option<Self> {
@@ -158,52 +178,63 @@ impl Decimal {
     }
 
     /// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
-    pub const fn abs(self) -> Self {
-        Self {
-            value: self.value.abs(),
-        }
+    pub fn checked_abs(self) -> Option<Self> {
+        Some(Self {
+            value: self.value.checked_abs()?,
+        })
     }
 
     /// [fn:round](https://www.w3.org/TR/xpath-functions-31/#func-round)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
-    pub fn round(self) -> Self {
+    pub fn checked_round(self) -> Option<Self> {
         let value = self.value / DECIMAL_PART_POW_MINUS_ONE;
-        Self {
+        Some(Self {
             value: if value >= 0 {
-                (value / 10 + i128::from(value % 10 >= 5)) * DECIMAL_PART_POW
+                value / 10 + i128::from(value % 10 >= 5)
             } else {
-                (value / 10 - i128::from(-value % 10 > 5)) * DECIMAL_PART_POW
-            },
-        }
+                value / 10 - i128::from(-value % 10 > 5)
+            }
+            .checked_mul(DECIMAL_PART_POW)?,
+        })
     }
 
     /// [fn:ceiling](https://www.w3.org/TR/xpath-functions-31/#func-ceiling)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
-    pub fn ceil(self) -> Self {
-        Self {
-            value: if self.value >= 0 && self.value % DECIMAL_PART_POW != 0 {
-                (self.value / DECIMAL_PART_POW + 1) * DECIMAL_PART_POW
+    pub fn checked_ceil(self) -> Option<Self> {
+        Some(Self {
+            value: if self.value > 0 && self.value % DECIMAL_PART_POW != 0 {
+                self.value / DECIMAL_PART_POW + 1
             } else {
-                (self.value / DECIMAL_PART_POW) * DECIMAL_PART_POW
-            },
-        }
+                self.value / DECIMAL_PART_POW
+            }
+            .checked_mul(DECIMAL_PART_POW)?,
+        })
     }
 
     /// [fn:floor](https://www.w3.org/TR/xpath-functions-31/#func-floor)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
-    pub fn floor(self) -> Self {
-        Self {
+    pub fn checked_floor(self) -> Option<Self> {
+        Some(Self {
             value: if self.value >= 0 || self.value % DECIMAL_PART_POW == 0 {
-                (self.value / DECIMAL_PART_POW) * DECIMAL_PART_POW
+                self.value / DECIMAL_PART_POW
             } else {
-                (self.value / DECIMAL_PART_POW - 1) * DECIMAL_PART_POW
-            },
-        }
+                self.value / DECIMAL_PART_POW - 1
+            }
+            .checked_mul(DECIMAL_PART_POW)?,
+        })
     }
 
     #[inline]
@@ -328,28 +359,28 @@ impl From<Integer> for Decimal {
 }
 
 impl TryFrom<i128> for Decimal {
-    type Error = DecimalOverflowError;
+    type Error = TooLargeForDecimalError;
 
     #[inline]
-    fn try_from(value: i128) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: i128) -> Result<Self, TooLargeForDecimalError> {
         Ok(Self {
             value: value
                 .checked_mul(DECIMAL_PART_POW)
-                .ok_or(DecimalOverflowError)?,
+                .ok_or(TooLargeForDecimalError)?,
         })
     }
 }
 
 impl TryFrom<u128> for Decimal {
-    type Error = DecimalOverflowError;
+    type Error = TooLargeForDecimalError;
 
     #[inline]
-    fn try_from(value: u128) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: u128) -> Result<Self, TooLargeForDecimalError> {
         Ok(Self {
             value: i128::try_from(value)
-                .map_err(|_| DecimalOverflowError)?
+                .map_err(|_| TooLargeForDecimalError)?
                 .checked_mul(DECIMAL_PART_POW)
-                .ok_or(DecimalOverflowError)?,
+                .ok_or(TooLargeForDecimalError)?,
         })
     }
 }
@@ -362,27 +393,27 @@ impl From<Boolean> for Decimal {
 }
 
 impl TryFrom<Float> for Decimal {
-    type Error = DecimalOverflowError;
+    type Error = TooLargeForDecimalError;
 
     #[inline]
-    fn try_from(value: Float) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: Float) -> Result<Self, TooLargeForDecimalError> {
         Double::from(value).try_into()
     }
 }
 
 impl TryFrom<Double> for Decimal {
-    type Error = DecimalOverflowError;
+    type Error = TooLargeForDecimalError;
 
     #[inline]
     #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
-    fn try_from(value: Double) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: Double) -> Result<Self, TooLargeForDecimalError> {
         let shifted = f64::from(value) * (DECIMAL_PART_POW as f64);
-        if shifted.is_finite() && (i128::MIN as f64) <= shifted && shifted <= (i128::MAX as f64) {
+        if (i128::MIN as f64) <= shifted && shifted <= (i128::MAX as f64) {
             Ok(Self {
                 value: shifted as i128,
             })
         } else {
-            Err(DecimalOverflowError)
+            Err(TooLargeForDecimalError)
         }
     }
 }
@@ -415,17 +446,17 @@ impl From<Decimal> for Double {
 }
 
 impl TryFrom<Decimal> for Integer {
-    type Error = DecimalOverflowError;
+    type Error = TooLargeForIntegerError;
 
     #[inline]
-    fn try_from(value: Decimal) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: Decimal) -> Result<Self, TooLargeForIntegerError> {
         Ok(i64::try_from(
             value
                 .value
                 .checked_div(DECIMAL_PART_POW)
-                .ok_or(DecimalOverflowError)?,
+                .ok_or(TooLargeForIntegerError)?,
         )
-        .map_err(|_| DecimalOverflowError)?
+        .map_err(|_| TooLargeForIntegerError)?
         .into())
     }
 }
@@ -620,25 +651,27 @@ impl fmt::Display for ParseDecimalError {
 
 impl Error for ParseDecimalError {}
 
-impl From<DecimalOverflowError> for ParseDecimalError {
-    fn from(_: DecimalOverflowError) -> Self {
+impl From<TooLargeForDecimalError> for ParseDecimalError {
+    fn from(_: TooLargeForDecimalError) -> Self {
         Self {
             kind: DecimalParseErrorKind::Overflow,
         }
     }
 }
 
-/// An overflow in [`Decimal`] computations.
+/// The input is too large to fit into a [`Decimal`].
+///
+/// Matches XPath [`FOCA0001` error](https://www.w3.org/TR/xpath-functions-31/#ERRFOCA0001).
 #[derive(Debug, Clone, Copy)]
-pub struct DecimalOverflowError;
+pub struct TooLargeForDecimalError;
 
-impl fmt::Display for DecimalOverflowError {
+impl fmt::Display for TooLargeForDecimalError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "Value overflow")
+        write!(f, "Value too large for xsd:decimal internal representation")
     }
 }
 
-impl Error for DecimalOverflowError {}
+impl Error for TooLargeForDecimalError {}
 
 #[cfg(test)]
 mod tests {
@@ -797,45 +830,153 @@ mod tests {
             Some(Decimal::from_str("0.9")?)
         );
         assert_eq!(Decimal::from(1).checked_rem(0), None);
+        assert_eq!(
+            Decimal::MAX.checked_rem(1),
+            Some(Decimal::from_str("0.687303715884105727")?)
+        );
+        assert_eq!(
+            Decimal::MIN.checked_rem(1),
+            Some(Decimal::from_str("-0.687303715884105728")?)
+        );
+        assert_eq!(
+            Decimal::MAX.checked_rem(Decimal::STEP),
+            Some(Decimal::default())
+        );
+        assert_eq!(
+            Decimal::MIN.checked_rem(Decimal::STEP),
+            Some(Decimal::default())
+        );
+        assert_eq!(
+            Decimal::MAX.checked_rem(Decimal::MAX),
+            Some(Decimal::default())
+        );
+        assert_eq!(
+            Decimal::MIN.checked_rem(Decimal::MIN),
+            Some(Decimal::default())
+        );
         Ok(())
     }
 
     #[test]
     fn round() -> Result<(), ParseDecimalError> {
-        assert_eq!(Decimal::from(10).round(), Decimal::from(10));
-        assert_eq!(Decimal::from(-10).round(), Decimal::from(-10));
-        assert_eq!(Decimal::from_str("2.5")?.round(), Decimal::from(3));
-        assert_eq!(Decimal::from_str("2.4999")?.round(), Decimal::from(2));
-        assert_eq!(Decimal::from_str("-2.5")?.round(), Decimal::from(-2));
-        assert_eq!(Decimal::from(i64::MIN).round(), Decimal::from(i64::MIN));
-        assert_eq!(Decimal::from(i64::MAX).round(), Decimal::from(i64::MAX));
+        assert_eq!(Decimal::from(10).checked_round(), Some(Decimal::from(10)));
+        assert_eq!(Decimal::from(-10).checked_round(), Some(Decimal::from(-10)));
+        assert_eq!(
+            Decimal::from(i64::MIN).checked_round(),
+            Some(Decimal::from(i64::MIN))
+        );
+        assert_eq!(
+            Decimal::from(i64::MAX).checked_round(),
+            Some(Decimal::from(i64::MAX))
+        );
+        assert_eq!(
+            Decimal::from_str("2.5")?.checked_round(),
+            Some(Decimal::from(3))
+        );
+        assert_eq!(
+            Decimal::from_str("2.4999")?.checked_round(),
+            Some(Decimal::from(2))
+        );
+        assert_eq!(
+            Decimal::from_str("-2.5")?.checked_round(),
+            Some(Decimal::from(-2))
+        );
+        assert_eq!(Decimal::MAX.checked_round(), None);
+        assert_eq!(
+            (Decimal::MAX.checked_sub(Decimal::from_str("0.5")?))
+                .unwrap()
+                .checked_round(),
+            Some(Decimal::from_str("170141183460469231731")?)
+        );
+        assert_eq!(Decimal::MIN.checked_round(), None);
+        assert_eq!(
+            (Decimal::MIN.checked_add(Decimal::from_str("0.5")?))
+                .unwrap()
+                .checked_round(),
+            Some(Decimal::from_str("-170141183460469231731")?)
+        );
         Ok(())
     }
 
     #[test]
     fn ceil() -> Result<(), ParseDecimalError> {
-        assert_eq!(Decimal::from(10).ceil(), Decimal::from(10));
-        assert_eq!(Decimal::from(-10).ceil(), Decimal::from(-10));
-        assert_eq!(Decimal::from_str("10.5")?.ceil(), Decimal::from(11));
-        assert_eq!(Decimal::from_str("-10.5")?.ceil(), Decimal::from(-10));
-        assert_eq!(Decimal::from(i64::MIN).ceil(), Decimal::from(i64::MIN));
-        assert_eq!(Decimal::from(i64::MAX).ceil(), Decimal::from(i64::MAX));
+        assert_eq!(Decimal::from(10).checked_ceil(), Some(Decimal::from(10)));
+        assert_eq!(Decimal::from(-10).checked_ceil(), Some(Decimal::from(-10)));
+        assert_eq!(
+            Decimal::from_str("10.5")?.checked_ceil(),
+            Some(Decimal::from(11))
+        );
+        assert_eq!(
+            Decimal::from_str("-10.5")?.checked_ceil(),
+            Some(Decimal::from(-10))
+        );
+        assert_eq!(
+            Decimal::from(i64::MIN).checked_ceil(),
+            Some(Decimal::from(i64::MIN))
+        );
+        assert_eq!(
+            Decimal::from(i64::MAX).checked_ceil(),
+            Some(Decimal::from(i64::MAX))
+        );
+        assert_eq!(Decimal::MAX.checked_ceil(), None);
+        assert_eq!(
+            Decimal::MAX
+                .checked_sub(Decimal::from(1))
+                .unwrap()
+                .checked_ceil(),
+            Some(Decimal::from_str("170141183460469231731")?)
+        );
+        assert_eq!(
+            Decimal::MIN.checked_ceil(),
+            Some(Decimal::from_str("-170141183460469231731")?)
+        );
         Ok(())
     }
 
     #[test]
     fn floor() -> Result<(), ParseDecimalError> {
-        assert_eq!(Decimal::from(10).ceil(), Decimal::from(10));
-        assert_eq!(Decimal::from(-10).ceil(), Decimal::from(-10));
-        assert_eq!(Decimal::from_str("10.5")?.floor(), Decimal::from(10));
-        assert_eq!(Decimal::from_str("-10.5")?.floor(), Decimal::from(-11));
-        assert_eq!(Decimal::from(i64::MIN).floor(), Decimal::from(i64::MIN));
-        assert_eq!(Decimal::from(i64::MAX).floor(), Decimal::from(i64::MAX));
+        assert_eq!(Decimal::from(10).checked_floor(), Some(Decimal::from(10)));
+        assert_eq!(Decimal::from(-10).checked_floor(), Some(Decimal::from(-10)));
+        assert_eq!(
+            Decimal::from_str("10.5")?.checked_floor(),
+            Some(Decimal::from(10))
+        );
+        assert_eq!(
+            Decimal::from_str("-10.5")?.checked_floor(),
+            Some(Decimal::from(-11))
+        );
+        assert_eq!(
+            Decimal::from(i64::MIN).checked_floor(),
+            Some(Decimal::from(i64::MIN))
+        );
+        assert_eq!(
+            Decimal::from(i64::MAX).checked_floor(),
+            Some(Decimal::from(i64::MAX))
+        );
+        assert_eq!(
+            Decimal::MAX.checked_floor(),
+            Some(Decimal::from_str("170141183460469231731")?)
+        );
+        assert_eq!(Decimal::MIN.checked_floor(), None);
+        assert_eq!(
+            (Decimal::MIN.checked_add(Decimal::from_str("1")?))
+                .unwrap()
+                .checked_floor(),
+            Some(Decimal::from_str("-170141183460469231731")?)
+        );
         Ok(())
     }
 
     #[test]
     fn to_be_bytes() -> Result<(), ParseDecimalError> {
+        assert_eq!(
+            Decimal::from_be_bytes(Decimal::MIN.to_be_bytes()),
+            Decimal::MIN
+        );
+        assert_eq!(
+            Decimal::from_be_bytes(Decimal::MAX.to_be_bytes()),
+            Decimal::MAX
+        );
         assert_eq!(
             Decimal::from_be_bytes(Decimal::from(i64::MIN).to_be_bytes()),
             Decimal::from(i64::MIN)
@@ -889,7 +1030,8 @@ mod tests {
                 .unwrap()
                 .checked_sub(Decimal::from(1_672_507_293_696_i64))
                 .unwrap()
-                .abs()
+                .checked_abs()
+                .unwrap()
                 < Decimal::from(1)
         );
         Ok(())
@@ -914,7 +1056,8 @@ mod tests {
                 .unwrap()
                 .checked_sub(Decimal::from(1_672_507_302_466_i64))
                 .unwrap()
-                .abs()
+                .checked_abs()
+                .unwrap()
                 < Decimal::from(1)
         );
         assert!(Decimal::try_from(Double::from(f64::NAN)).is_err());
diff --git a/lib/oxsdatatypes/src/duration.rs b/lib/oxsdatatypes/src/duration.rs
index 27ce5d97..87be5a22 100644
--- a/lib/oxsdatatypes/src/duration.rs
+++ b/lib/oxsdatatypes/src/duration.rs
@@ -1,7 +1,6 @@
-use super::decimal::DecimalOverflowError;
-use super::parser::*;
-use super::*;
+use crate::{DateTime, Decimal};
 use std::cmp::Ordering;
+use std::error::Error;
 use std::fmt;
 use std::str::FromStr;
 use std::time::Duration as StdDuration;
@@ -78,13 +77,13 @@ impl Duration {
 
     #[inline]
     #[must_use]
-    pub(super) const fn all_months(self) -> i64 {
+    pub(crate) const fn all_months(self) -> i64 {
         self.year_month.all_months()
     }
 
     #[inline]
     #[must_use]
-    pub(super) const fn all_seconds(self) -> Decimal {
+    pub(crate) const fn all_seconds(self) -> Decimal {
         self.day_time.as_seconds()
     }
 
@@ -98,6 +97,8 @@ impl Duration {
     }
 
     /// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations) and [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     #[must_use]
     pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -109,6 +110,8 @@ impl Duration {
     }
 
     /// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations) and [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     #[must_use]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -119,6 +122,9 @@ impl Duration {
         })
     }
 
+    /// Unary negation.
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     #[must_use]
     pub fn checked_neg(self) -> Option<Self> {
@@ -134,22 +140,39 @@ impl Duration {
     pub fn is_identical_with(self, other: Self) -> bool {
         self == other
     }
+
+    pub const MIN: Self = Self {
+        year_month: YearMonthDuration::MIN,
+        day_time: DayTimeDuration::MIN,
+    };
+
+    pub const MAX: Self = Self {
+        year_month: YearMonthDuration::MAX,
+        day_time: DayTimeDuration::MAX,
+    };
 }
 
 impl TryFrom<StdDuration> for Duration {
-    type Error = DecimalOverflowError;
+    type Error = DurationOverflowError;
 
     #[inline]
-    fn try_from(value: StdDuration) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: StdDuration) -> Result<Self, DurationOverflowError> {
         Ok(DayTimeDuration::try_from(value)?.into())
     }
 }
 
 impl FromStr for Duration {
-    type Err = XsdParseError;
+    type Err = ParseDurationError;
 
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_duration(input)
+    fn from_str(input: &str) -> Result<Self, ParseDurationError> {
+        let parts = ensure_complete(input, duration_parts)?;
+        if parts.year_month.is_none() && parts.day_time.is_none() {
+            return Err(ParseDurationError::msg("Empty duration"));
+        }
+        Ok(Self::new(
+            parts.year_month.unwrap_or(0),
+            parts.day_time.unwrap_or_default(),
+        ))
     }
 }
 
@@ -208,7 +231,7 @@ impl fmt::Display for Duration {
                     write!(f, "{}M", m.abs())?;
                 }
                 if s != 0.into() {
-                    write!(f, "{}S", s.abs())?;
+                    write!(f, "{}S", s.checked_abs().ok_or(fmt::Error)?)?;
                 }
             }
         }
@@ -282,7 +305,7 @@ impl YearMonthDuration {
     }
 
     #[inline]
-    pub(super) const fn all_months(self) -> i64 {
+    pub(crate) const fn all_months(self) -> i64 {
         self.months
     }
 
@@ -292,6 +315,8 @@ impl YearMonthDuration {
     }
 
     /// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
         let rhs = rhs.into();
@@ -301,6 +326,8 @@ impl YearMonthDuration {
     }
 
     /// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
         let rhs = rhs.into();
@@ -309,6 +336,9 @@ impl YearMonthDuration {
         })
     }
 
+    /// Unary negation.
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     pub fn checked_neg(self) -> Option<Self> {
         Some(Self {
@@ -321,6 +351,10 @@ impl YearMonthDuration {
     pub fn is_identical_with(self, other: Self) -> bool {
         self == other
     }
+
+    pub const MIN: Self = Self { months: i64::MIN };
+
+    pub const MAX: Self = Self { months: i64::MAX };
 }
 
 impl From<YearMonthDuration> for Duration {
@@ -334,23 +368,31 @@ impl From<YearMonthDuration> for Duration {
 }
 
 impl TryFrom<Duration> for YearMonthDuration {
-    type Error = DecimalOverflowError;
+    type Error = DurationOverflowError;
 
     #[inline]
-    fn try_from(value: Duration) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: Duration) -> Result<Self, DurationOverflowError> {
         if value.day_time == DayTimeDuration::default() {
             Ok(value.year_month)
         } else {
-            Err(DecimalOverflowError {})
+            Err(DurationOverflowError)
         }
     }
 }
 
 impl FromStr for YearMonthDuration {
-    type Err = XsdParseError;
-
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_year_month_duration(input)
+    type Err = ParseDurationError;
+
+    fn from_str(input: &str) -> Result<Self, ParseDurationError> {
+        let parts = ensure_complete(input, duration_parts)?;
+        if parts.day_time.is_some() {
+            return Err(ParseDurationError::msg(
+                "There must not be any day or time component in a yearMonthDuration",
+            ));
+        }
+        Ok(Self::new(parts.year_month.ok_or(
+            ParseDurationError::msg("No year and month values found"),
+        )?))
     }
 }
 
@@ -455,6 +497,8 @@ impl DayTimeDuration {
     }
 
     /// [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
         let rhs = rhs.into();
@@ -464,6 +508,8 @@ impl DayTimeDuration {
     }
 
     /// [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations)
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
         let rhs = rhs.into();
@@ -472,6 +518,9 @@ impl DayTimeDuration {
         })
     }
 
+    /// Unary negation.
+    ///
+    /// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
     #[inline]
     pub fn checked_neg(self) -> Option<Self> {
         Some(Self {
@@ -484,6 +533,14 @@ impl DayTimeDuration {
     pub fn is_identical_with(self, other: Self) -> bool {
         self == other
     }
+
+    pub const MIN: Self = Self {
+        seconds: Decimal::MIN,
+    };
+
+    pub const MAX: Self = Self {
+        seconds: Decimal::MAX,
+    };
 }
 
 impl From<DayTimeDuration> for Duration {
@@ -497,65 +554,75 @@ impl From<DayTimeDuration> for Duration {
 }
 
 impl TryFrom<Duration> for DayTimeDuration {
-    type Error = DecimalOverflowError;
+    type Error = DurationOverflowError;
 
     #[inline]
-    fn try_from(value: Duration) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: Duration) -> Result<Self, DurationOverflowError> {
         if value.year_month == YearMonthDuration::default() {
             Ok(value.day_time)
         } else {
-            Err(DecimalOverflowError {})
+            Err(DurationOverflowError)
         }
     }
 }
 
 impl TryFrom<StdDuration> for DayTimeDuration {
-    type Error = DecimalOverflowError;
+    type Error = DurationOverflowError;
 
     #[inline]
-    fn try_from(value: StdDuration) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: StdDuration) -> Result<Self, DurationOverflowError> {
         Ok(Self {
             seconds: Decimal::new(
-                i128::try_from(value.as_nanos()).map_err(|_| DecimalOverflowError)?,
+                i128::try_from(value.as_nanos()).map_err(|_| DurationOverflowError)?,
                 9,
-            )?,
+            )
+            .map_err(|_| DurationOverflowError)?,
         })
     }
 }
 
 impl TryFrom<DayTimeDuration> for StdDuration {
-    type Error = DecimalOverflowError;
+    type Error = DurationOverflowError;
 
     #[inline]
-    fn try_from(value: DayTimeDuration) -> Result<Self, DecimalOverflowError> {
+    fn try_from(value: DayTimeDuration) -> Result<Self, DurationOverflowError> {
         if value.seconds.is_negative() {
-            return Err(DecimalOverflowError);
+            return Err(DurationOverflowError);
         }
-        let secs = value.seconds.floor();
+        let secs = value.seconds.checked_floor().ok_or(DurationOverflowError)?;
         let nanos = value
             .seconds
             .checked_sub(secs)
-            .ok_or(DecimalOverflowError)?
+            .ok_or(DurationOverflowError)?
             .checked_mul(1_000_000_000)
-            .ok_or(DecimalOverflowError)?
-            .floor();
+            .ok_or(DurationOverflowError)?
+            .checked_floor()
+            .ok_or(DurationOverflowError)?;
         Ok(StdDuration::new(
             secs.as_i128()
                 .try_into()
-                .map_err(|_| DecimalOverflowError)?,
+                .map_err(|_| DurationOverflowError)?,
             nanos
                 .as_i128()
                 .try_into()
-                .map_err(|_| DecimalOverflowError)?,
+                .map_err(|_| DurationOverflowError)?,
         ))
     }
 }
 
 impl FromStr for DayTimeDuration {
-    type Err = XsdParseError;
-
-    fn from_str(input: &str) -> Result<Self, XsdParseError> {
-        parse_day_time_duration(input)
+    type Err = ParseDurationError;
+
+    fn from_str(input: &str) -> Result<Self, ParseDurationError> {
+        let parts = ensure_complete(input, duration_parts)?;
+        if parts.year_month.is_some() {
+            return Err(ParseDurationError::msg(
+                "There must not be any year or month component in a dayTimeDuration",
+            ));
+        }
+        Ok(Self::new(parts.day_time.ok_or(ParseDurationError::msg(
+            "No day or time values found",
+        ))?))
     }
 }
 
@@ -622,12 +689,273 @@ impl PartialOrd<DayTimeDuration> for YearMonthDuration {
     }
 }
 
+// [6]   duYearFrag ::= unsignedNoDecimalPtNumeral 'Y'
+// [7]   duMonthFrag ::= unsignedNoDecimalPtNumeral 'M'
+// [8]   duDayFrag ::= unsignedNoDecimalPtNumeral 'D'
+// [9]   duHourFrag ::= unsignedNoDecimalPtNumeral 'H'
+// [10]   duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M'
+// [11]   duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S'
+// [12]   duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag
+// [13]   duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag)
+// [14]   duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag
+// [15]   durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag)
+struct DurationParts {
+    year_month: Option<i64>,
+    day_time: Option<Decimal>,
+}
+
+fn duration_parts(input: &str) -> Result<(DurationParts, &str), ParseDurationError> {
+    // States
+    const START: u32 = 0;
+    const AFTER_YEAR: u32 = 1;
+    const AFTER_MONTH: u32 = 2;
+    const AFTER_DAY: u32 = 3;
+    const AFTER_T: u32 = 4;
+    const AFTER_HOUR: u32 = 5;
+    const AFTER_MINUTE: u32 = 6;
+    const AFTER_SECOND: u32 = 7;
+
+    let (is_negative, input) = if let Some(left) = input.strip_prefix('-') {
+        (true, left)
+    } else {
+        (false, input)
+    };
+    let mut input = expect_char(input, 'P', "Durations must start with 'P'")?;
+    let mut state = START;
+    let mut year_month: Option<i64> = None;
+    let mut day_time: Option<Decimal> = None;
+    while !input.is_empty() {
+        if let Some(left) = input.strip_prefix('T') {
+            if state >= AFTER_T {
+                return Err(ParseDurationError::msg("Duplicated time separator 'T'"));
+            }
+            state = AFTER_T;
+            input = left;
+        } else {
+            let (number_str, left) = decimal_prefix(input);
+            match left.chars().next() {
+                Some('Y') if state < AFTER_YEAR => {
+                    year_month = Some(
+                        year_month
+                            .unwrap_or_default()
+                            .checked_add(
+                                apply_i64_neg(
+                                    i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
+                                    is_negative,
+                                )?
+                                .checked_mul(12)
+                                .ok_or(OVERFLOW_ERROR)?,
+                            )
+                            .ok_or(OVERFLOW_ERROR)?,
+                    );
+                    state = AFTER_YEAR;
+                }
+                Some('M') if state < AFTER_MONTH => {
+                    year_month = Some(
+                        year_month
+                            .unwrap_or_default()
+                            .checked_add(apply_i64_neg(
+                                i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
+                                is_negative,
+                            )?)
+                            .ok_or(OVERFLOW_ERROR)?,
+                    );
+                    state = AFTER_MONTH;
+                }
+                Some('D') if state < AFTER_DAY => {
+                    if number_str.contains('.') {
+                        return Err(ParseDurationError::msg(
+                            "Decimal numbers are not allowed for days",
+                        ));
+                    }
+                    day_time = Some(
+                        day_time
+                            .unwrap_or_default()
+                            .checked_add(
+                                apply_decimal_neg(
+                                    Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
+                                    is_negative,
+                                )?
+                                .checked_mul(86400)
+                                .ok_or(OVERFLOW_ERROR)?,
+                            )
+                            .ok_or(OVERFLOW_ERROR)?,
+                    );
+                    state = AFTER_DAY;
+                }
+                Some('H') if state == AFTER_T => {
+                    if number_str.contains('.') {
+                        return Err(ParseDurationError::msg(
+                            "Decimal numbers are not allowed for hours",
+                        ));
+                    }
+                    day_time = Some(
+                        day_time
+                            .unwrap_or_default()
+                            .checked_add(
+                                apply_decimal_neg(
+                                    Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
+                                    is_negative,
+                                )?
+                                .checked_mul(3600)
+                                .ok_or(OVERFLOW_ERROR)?,
+                            )
+                            .ok_or(OVERFLOW_ERROR)?,
+                    );
+                    state = AFTER_HOUR;
+                }
+                Some('M') if (AFTER_T..AFTER_MINUTE).contains(&state) => {
+                    if number_str.contains('.') {
+                        return Err(ParseDurationError::msg(
+                            "Decimal numbers are not allowed for minutes",
+                        ));
+                    }
+                    day_time = Some(
+                        day_time
+                            .unwrap_or_default()
+                            .checked_add(
+                                apply_decimal_neg(
+                                    Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
+                                    is_negative,
+                                )?
+                                .checked_mul(60)
+                                .ok_or(OVERFLOW_ERROR)?,
+                            )
+                            .ok_or(OVERFLOW_ERROR)?,
+                    );
+                    state = AFTER_MINUTE;
+                }
+                Some('S') if (AFTER_T..AFTER_SECOND).contains(&state) => {
+                    day_time = Some(
+                        day_time
+                            .unwrap_or_default()
+                            .checked_add(apply_decimal_neg(
+                                Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
+                                is_negative,
+                            )?)
+                            .ok_or(OVERFLOW_ERROR)?,
+                    );
+                    state = AFTER_SECOND;
+                }
+                Some(_) => return Err(ParseDurationError::msg("Unexpected type character")),
+                None => {
+                    return Err(ParseDurationError::msg(
+                        "Numbers in durations must be followed by a type character",
+                    ))
+                }
+            }
+            input = &left[1..];
+        }
+    }
+
+    Ok((
+        DurationParts {
+            year_month,
+            day_time,
+        },
+        input,
+    ))
+}
+
+fn apply_i64_neg(value: i64, is_negative: bool) -> Result<i64, ParseDurationError> {
+    if is_negative {
+        value.checked_neg().ok_or(OVERFLOW_ERROR)
+    } else {
+        Ok(value)
+    }
+}
+
+fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result<Decimal, ParseDurationError> {
+    if is_negative {
+        value.checked_neg().ok_or(OVERFLOW_ERROR)
+    } else {
+        Ok(value)
+    }
+}
+
+fn ensure_complete<T>(
+    input: &str,
+    parse: impl FnOnce(&str) -> Result<(T, &str), ParseDurationError>,
+) -> Result<T, ParseDurationError> {
+    let (result, left) = parse(input)?;
+    if !left.is_empty() {
+        return Err(ParseDurationError::msg("Unrecognized value suffix"));
+    }
+    Ok(result)
+}
+
+fn expect_char<'a>(
+    input: &'a str,
+    constant: char,
+    error_message: &'static str,
+) -> Result<&'a str, ParseDurationError> {
+    if let Some(left) = input.strip_prefix(constant) {
+        Ok(left)
+    } else {
+        Err(ParseDurationError::msg(error_message))
+    }
+}
+
+fn decimal_prefix(input: &str) -> (&str, &str) {
+    let mut end = input.len();
+    let mut dot_seen = false;
+    for (i, c) in input.char_indices() {
+        if c.is_ascii_digit() {
+            // Ok
+        } else if c == '.' && !dot_seen {
+            dot_seen = true;
+        } else {
+            end = i;
+            break;
+        }
+    }
+    input.split_at(end)
+}
+
+/// A parsing error
+#[derive(Debug, Clone)]
+pub struct ParseDurationError {
+    msg: &'static str,
+}
+
+const OVERFLOW_ERROR: ParseDurationError = ParseDurationError {
+    msg: "Overflow error",
+};
+
+impl fmt::Display for ParseDurationError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.msg)
+    }
+}
+
+impl ParseDurationError {
+    const fn msg(msg: &'static str) -> Self {
+        Self { msg }
+    }
+}
+
+impl Error for ParseDurationError {}
+
+/// An overflow during [`Duration`]-related operations.
+///
+/// Matches XPath [`FODT0002` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002).
+#[derive(Debug, Clone, Copy)]
+pub struct DurationOverflowError;
+
+impl fmt::Display for DurationOverflowError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "overflow during xsd:duration computation")
+    }
+}
+
+impl Error for DurationOverflowError {}
+
 #[cfg(test)]
 mod tests {
     use super::*;
 
     #[test]
-    fn from_str() -> Result<(), XsdParseError> {
+    fn from_str() -> Result<(), ParseDurationError> {
         let min = Duration::new(i64::MIN, Decimal::MIN);
         let max = Duration::new(i64::MAX, Decimal::MAX);
 
@@ -667,25 +995,52 @@ mod tests {
     }
 
     #[test]
-    fn from_std() {
+    fn from_std() -> Result<(), DurationOverflowError> {
         assert_eq!(
-            Duration::try_from(StdDuration::new(10, 10))
-                .unwrap()
-                .to_string(),
+            Duration::try_from(StdDuration::new(10, 10))?.to_string(),
             "PT10.00000001S"
         );
+        Ok(())
     }
 
     #[test]
-    fn to_std() -> Result<(), XsdParseError> {
-        let duration = StdDuration::try_from(DayTimeDuration::from_str("PT10.00000001S")?).unwrap();
+    fn to_std() -> Result<(), Box<dyn Error>> {
+        let duration = StdDuration::try_from(DayTimeDuration::from_str("PT10.00000001S")?)?;
         assert_eq!(duration.as_secs(), 10);
         assert_eq!(duration.subsec_nanos(), 10);
         Ok(())
     }
 
     #[test]
-    fn equals() -> Result<(), XsdParseError> {
+    fn to_be_bytes() {
+        assert_eq!(
+            Duration::from_be_bytes(Duration::MIN.to_be_bytes()),
+            Duration::MIN
+        );
+        assert_eq!(
+            Duration::from_be_bytes(Duration::MAX.to_be_bytes()),
+            Duration::MAX
+        );
+        assert_eq!(
+            YearMonthDuration::from_be_bytes(YearMonthDuration::MIN.to_be_bytes()),
+            YearMonthDuration::MIN
+        );
+        assert_eq!(
+            YearMonthDuration::from_be_bytes(YearMonthDuration::MAX.to_be_bytes()),
+            YearMonthDuration::MAX
+        );
+        assert_eq!(
+            DayTimeDuration::from_be_bytes(DayTimeDuration::MIN.to_be_bytes()),
+            DayTimeDuration::MIN
+        );
+        assert_eq!(
+            DayTimeDuration::from_be_bytes(DayTimeDuration::MAX.to_be_bytes()),
+            DayTimeDuration::MAX
+        );
+    }
+
+    #[test]
+    fn equals() -> Result<(), ParseDurationError> {
         assert_eq!(
             YearMonthDuration::from_str("P1Y")?,
             YearMonthDuration::from_str("P12M")?
@@ -730,7 +1085,24 @@ mod tests {
     }
 
     #[test]
-    fn years() -> Result<(), XsdParseError> {
+    #[allow(clippy::neg_cmp_op_on_partial_ord)]
+    fn cmp() -> Result<(), ParseDurationError> {
+        assert!(Duration::from_str("P1Y1D")? < Duration::from_str("P13MT25H")?);
+        assert!(YearMonthDuration::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?);
+        assert!(Duration::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?);
+        assert!(YearMonthDuration::from_str("P1Y")? < Duration::from_str("P13M")?);
+        assert!(DayTimeDuration::from_str("P1D")? < DayTimeDuration::from_str("PT25H")?);
+        assert!(DayTimeDuration::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?);
+        assert!(DayTimeDuration::from_str("PT1M")? < DayTimeDuration::from_str("PT61S")?);
+        assert!(Duration::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?);
+        assert!(DayTimeDuration::from_str("PT1H")? < Duration::from_str("PT61M")?);
+        assert!(YearMonthDuration::from_str("P1M")? < DayTimeDuration::from_str("P40D")?);
+        assert!(DayTimeDuration::from_str("P25D")? < YearMonthDuration::from_str("P1M")?);
+        Ok(())
+    }
+
+    #[test]
+    fn years() -> Result<(), ParseDurationError> {
         assert_eq!(Duration::from_str("P20Y15M")?.years(), 21);
         assert_eq!(Duration::from_str("-P15M")?.years(), -1);
         assert_eq!(Duration::from_str("-P2DT15H")?.years(), 0);
@@ -738,7 +1110,7 @@ mod tests {
     }
 
     #[test]
-    fn months() -> Result<(), XsdParseError> {
+    fn months() -> Result<(), ParseDurationError> {
         assert_eq!(Duration::from_str("P20Y15M")?.months(), 3);
         assert_eq!(Duration::from_str("-P20Y18M")?.months(), -6);
         assert_eq!(Duration::from_str("-P2DT15H0M0S")?.months(), 0);
@@ -746,7 +1118,7 @@ mod tests {
     }
 
     #[test]
-    fn days() -> Result<(), XsdParseError> {
+    fn days() -> Result<(), ParseDurationError> {
         assert_eq!(Duration::from_str("P3DT10H")?.days(), 3);
         assert_eq!(Duration::from_str("P3DT55H")?.days(), 5);
         assert_eq!(Duration::from_str("P3Y5M")?.days(), 0);
@@ -754,7 +1126,7 @@ mod tests {
     }
 
     #[test]
-    fn hours() -> Result<(), XsdParseError> {
+    fn hours() -> Result<(), ParseDurationError> {
         assert_eq!(Duration::from_str("P3DT10H")?.hours(), 10);
         assert_eq!(Duration::from_str("P3DT12H32M12S")?.hours(), 12);
         assert_eq!(Duration::from_str("PT123H")?.hours(), 3);
@@ -763,14 +1135,14 @@ mod tests {
     }
 
     #[test]
-    fn minutes() -> Result<(), XsdParseError> {
+    fn minutes() -> Result<(), ParseDurationError> {
         assert_eq!(Duration::from_str("P3DT10H")?.minutes(), 0);
         assert_eq!(Duration::from_str("-P5DT12H30M")?.minutes(), -30);
         Ok(())
     }
 
     #[test]
-    fn seconds() -> Result<(), XsdParseError> {
+    fn seconds() -> Result<(), Box<dyn Error>> {
         assert_eq!(
             Duration::from_str("P3DT10H12.5S")?.seconds(),
             Decimal::from_str("12.5")?
@@ -783,7 +1155,7 @@ mod tests {
     }
 
     #[test]
-    fn add() -> Result<(), XsdParseError> {
+    fn add() -> Result<(), ParseDurationError> {
         assert_eq!(
             Duration::from_str("P2Y11M")?.checked_add(Duration::from_str("P3Y3M")?),
             Some(Duration::from_str("P6Y2M")?)
@@ -796,7 +1168,7 @@ mod tests {
     }
 
     #[test]
-    fn sub() -> Result<(), XsdParseError> {
+    fn sub() -> Result<(), ParseDurationError> {
         assert_eq!(
             Duration::from_str("P2Y11M")?.checked_sub(Duration::from_str("P3Y3M")?),
             Some(Duration::from_str("-P4M")?)
@@ -809,7 +1181,7 @@ mod tests {
     }
 
     #[test]
-    fn minimally_conformant() -> Result<(), XsdParseError> {
+    fn minimally_conformant() -> Result<(), ParseDurationError> {
         // All minimally conforming processors must support fractional-second duration values
         // to milliseconds (i.e. those expressible with three fraction digits).
         assert_eq!(Duration::from_str("PT0.001S")?.to_string(), "PT0.001S");
diff --git a/lib/oxsdatatypes/src/integer.rs b/lib/oxsdatatypes/src/integer.rs
index f376a57d..352e521a 100644
--- a/lib/oxsdatatypes/src/integer.rs
+++ b/lib/oxsdatatypes/src/integer.rs
@@ -1,4 +1,5 @@
-use crate::{Boolean, Decimal, DecimalOverflowError, Double, Float};
+use crate::{Boolean, Decimal, Double, Float};
+use std::error::Error;
 use std::fmt;
 use std::num::ParseIntError;
 use std::str::FromStr;
@@ -28,6 +29,8 @@ impl Integer {
     }
 
     /// [op:numeric-add](https://www.w3.org/TR/xpath-functions-31/#func-numeric-add)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -37,6 +40,8 @@ impl Integer {
     }
 
     /// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -46,6 +51,8 @@ impl Integer {
     }
 
     /// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_mul(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -55,6 +62,8 @@ impl Integer {
     }
 
     /// [op:numeric-integer-divide](https://www.w3.org/TR/xpath-functions-31/#func-numeric-integer-divide)
+    ///
+    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_div(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -64,6 +73,8 @@ impl Integer {
     }
 
     /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod)
+    ///
+    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_rem(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -72,6 +83,9 @@ impl Integer {
         })
     }
 
+    /// Euclidean remainder
+    ///
+    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_rem_euclid(self, rhs: impl Into<Self>) -> Option<Self> {
@@ -81,6 +95,8 @@ impl Integer {
     }
 
     /// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
     pub fn checked_neg(self) -> Option<Self> {
@@ -90,12 +106,14 @@ impl Integer {
     }
 
     /// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs)
+    ///
+    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
     #[inline]
     #[must_use]
-    pub const fn abs(self) -> Self {
-        Self {
-            value: self.value.abs(),
-        }
+    pub fn checked_abs(self) -> Option<Self> {
+        Some(Self {
+            value: self.value.checked_abs()?,
+        })
     }
 
     #[inline]
@@ -223,23 +241,41 @@ impl fmt::Display for Integer {
 }
 
 impl TryFrom<Float> for Integer {
-    type Error = DecimalOverflowError;
+    type Error = TooLargeForIntegerError;
 
     #[inline]
-    fn try_from(value: Float) -> Result<Self, DecimalOverflowError> {
-        Decimal::try_from(value)?.try_into()
+    fn try_from(value: Float) -> Result<Self, TooLargeForIntegerError> {
+        Decimal::try_from(value)
+            .map_err(|_| TooLargeForIntegerError)?
+            .try_into()
     }
 }
 
 impl TryFrom<Double> for Integer {
-    type Error = DecimalOverflowError;
+    type Error = TooLargeForIntegerError;
 
     #[inline]
-    fn try_from(value: Double) -> Result<Self, DecimalOverflowError> {
-        Decimal::try_from(value)?.try_into()
+    fn try_from(value: Double) -> Result<Self, TooLargeForIntegerError> {
+        Decimal::try_from(value)
+            .map_err(|_| TooLargeForIntegerError)?
+            .try_into()
     }
 }
 
+/// The input is too large to fit into an [`Integer`].
+///
+/// Matches XPath [`FOCA0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFOCA0003).
+#[derive(Debug, Clone, Copy)]
+pub struct TooLargeForIntegerError;
+
+impl fmt::Display for TooLargeForIntegerError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "Value too large for xsd:integer internal representation")
+    }
+}
+
+impl Error for TooLargeForIntegerError {}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -278,7 +314,8 @@ mod tests {
                 .unwrap()
                 .checked_sub(Integer::from_str("1672507300000")?)
                 .unwrap()
-                .abs()
+                .checked_abs()
+                .unwrap()
                 < Integer::from(1_000_000)
         );
         Ok(())
@@ -303,7 +340,8 @@ mod tests {
                 .unwrap()
                 .checked_sub(Integer::from_str("1672507300000").unwrap())
                 .unwrap()
-                .abs()
+                .checked_abs()
+                .unwrap()
                 < Integer::from(10)
         );
         assert!(Integer::try_from(Double::from(f64::NAN)).is_err());
diff --git a/lib/oxsdatatypes/src/lib.rs b/lib/oxsdatatypes/src/lib.rs
index 6e1cd28f..a31caf61 100644
--- a/lib/oxsdatatypes/src/lib.rs
+++ b/lib/oxsdatatypes/src/lib.rs
@@ -11,15 +11,16 @@ mod double;
 mod duration;
 mod float;
 mod integer;
-mod parser;
 
 pub use self::boolean::Boolean;
 pub use self::date_time::{
-    Date, DateTime, DateTimeError, GDay, GMonth, GMonthDay, GYear, GYearMonth, Time, TimezoneOffset,
+    Date, DateTime, DateTimeOverflowError, GDay, GMonth, GMonthDay, GYear, GYearMonth,
+    InvalidTimezoneError, ParseDateTimeError, Time, TimezoneOffset,
 };
-pub use self::decimal::{Decimal, DecimalOverflowError, ParseDecimalError};
+pub use self::decimal::{Decimal, ParseDecimalError, TooLargeForDecimalError};
 pub use self::double::Double;
-pub use self::duration::{DayTimeDuration, Duration, YearMonthDuration};
+pub use self::duration::{
+    DayTimeDuration, Duration, DurationOverflowError, ParseDurationError, YearMonthDuration,
+};
 pub use self::float::Float;
-pub use self::integer::Integer;
-pub use self::parser::XsdParseError;
+pub use self::integer::{Integer, TooLargeForIntegerError};
diff --git a/lib/oxsdatatypes/src/parser.rs b/lib/oxsdatatypes/src/parser.rs
deleted file mode 100644
index 942c71e4..00000000
--- a/lib/oxsdatatypes/src/parser.rs
+++ /dev/null
@@ -1,626 +0,0 @@
-use super::date_time::{DateTimeError, GDay, GMonth, GMonthDay, GYear, GYearMonth, TimezoneOffset};
-use super::decimal::ParseDecimalError;
-use super::duration::{DayTimeDuration, YearMonthDuration};
-use super::*;
-use std::error::Error;
-use std::fmt;
-use std::num::ParseIntError;
-use std::str::FromStr;
-
-/// A parsing error
-#[derive(Debug, Clone)]
-pub struct XsdParseError {
-    kind: XsdParseErrorKind,
-}
-
-#[derive(Debug, Clone)]
-enum XsdParseErrorKind {
-    ParseInt(ParseIntError),
-    ParseDecimal(ParseDecimalError),
-    DateTime(DateTimeError),
-    Message(&'static str),
-}
-
-const OVERFLOW_ERROR: XsdParseError = XsdParseError {
-    kind: XsdParseErrorKind::Message("Overflow error"),
-};
-
-impl fmt::Display for XsdParseError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match &self.kind {
-            XsdParseErrorKind::ParseInt(error) => {
-                write!(f, "Error while parsing integer: {error}")
-            }
-            XsdParseErrorKind::ParseDecimal(error) => {
-                write!(f, "Error while parsing decimal: {error}")
-            }
-            XsdParseErrorKind::DateTime(error) => error.fmt(f),
-            XsdParseErrorKind::Message(msg) => write!(f, "{msg}"),
-        }
-    }
-}
-
-impl XsdParseError {
-    const fn msg(message: &'static str) -> Self {
-        Self {
-            kind: XsdParseErrorKind::Message(message),
-        }
-    }
-}
-
-impl Error for XsdParseError {
-    fn source(&self) -> Option<&(dyn Error + 'static)> {
-        match &self.kind {
-            XsdParseErrorKind::ParseInt(error) => Some(error),
-            XsdParseErrorKind::ParseDecimal(error) => Some(error),
-            XsdParseErrorKind::DateTime(error) => Some(error),
-            XsdParseErrorKind::Message(_) => None,
-        }
-    }
-}
-
-impl From<ParseIntError> for XsdParseError {
-    fn from(error: ParseIntError) -> Self {
-        Self {
-            kind: XsdParseErrorKind::ParseInt(error),
-        }
-    }
-}
-
-impl From<ParseDecimalError> for XsdParseError {
-    fn from(error: ParseDecimalError) -> Self {
-        Self {
-            kind: XsdParseErrorKind::ParseDecimal(error),
-        }
-    }
-}
-
-impl From<DateTimeError> for XsdParseError {
-    fn from(error: DateTimeError) -> Self {
-        Self {
-            kind: XsdParseErrorKind::DateTime(error),
-        }
-    }
-}
-
-// [6]   duYearFrag ::= unsignedNoDecimalPtNumeral 'Y'
-// [7]   duMonthFrag ::= unsignedNoDecimalPtNumeral 'M'
-// [8]   duDayFrag ::= unsignedNoDecimalPtNumeral 'D'
-// [9]   duHourFrag ::= unsignedNoDecimalPtNumeral 'H'
-// [10]   duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M'
-// [11]   duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S'
-// [12]   duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag
-// [13]   duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag)
-// [14]   duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag
-// [15]   durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag)
-struct DurationParts {
-    year_month: Option<i64>,
-    day_time: Option<Decimal>,
-}
-
-fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
-    // States
-    const START: u32 = 0;
-    const AFTER_YEAR: u32 = 1;
-    const AFTER_MONTH: u32 = 2;
-    const AFTER_DAY: u32 = 3;
-    const AFTER_T: u32 = 4;
-    const AFTER_HOUR: u32 = 5;
-    const AFTER_MINUTE: u32 = 6;
-    const AFTER_SECOND: u32 = 7;
-
-    let (is_negative, input) = if let Some(left) = input.strip_prefix('-') {
-        (true, left)
-    } else {
-        (false, input)
-    };
-    let mut input = expect_char(input, 'P', "Durations must start with 'P'")?;
-    let mut state = START;
-    let mut year_month: Option<i64> = None;
-    let mut day_time: Option<Decimal> = None;
-    while !input.is_empty() {
-        if let Some(left) = input.strip_prefix('T') {
-            if state >= AFTER_T {
-                return Err(XsdParseError::msg("Duplicated time separator 'T'"));
-            }
-            state = AFTER_T;
-            input = left;
-        } else {
-            let (number_str, left) = decimal_prefix(input);
-            match left.chars().next() {
-                Some('Y') if state < AFTER_YEAR => {
-                    year_month = Some(
-                        year_month
-                            .unwrap_or_default()
-                            .checked_add(
-                                apply_i64_neg(i64::from_str(number_str)?, is_negative)?
-                                    .checked_mul(12)
-                                    .ok_or(OVERFLOW_ERROR)?,
-                            )
-                            .ok_or(OVERFLOW_ERROR)?,
-                    );
-                    state = AFTER_YEAR;
-                }
-                Some('M') if state < AFTER_MONTH => {
-                    year_month = Some(
-                        year_month
-                            .unwrap_or_default()
-                            .checked_add(apply_i64_neg(i64::from_str(number_str)?, is_negative)?)
-                            .ok_or(OVERFLOW_ERROR)?,
-                    );
-                    state = AFTER_MONTH;
-                }
-                Some('D') if state < AFTER_DAY => {
-                    if number_str.contains('.') {
-                        return Err(XsdParseError::msg(
-                            "Decimal numbers are not allowed for days",
-                        ));
-                    }
-                    day_time = Some(
-                        day_time
-                            .unwrap_or_default()
-                            .checked_add(
-                                apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
-                                    .checked_mul(86400)
-                                    .ok_or(OVERFLOW_ERROR)?,
-                            )
-                            .ok_or(OVERFLOW_ERROR)?,
-                    );
-                    state = AFTER_DAY;
-                }
-                Some('H') if state == AFTER_T => {
-                    if number_str.contains('.') {
-                        return Err(XsdParseError::msg(
-                            "Decimal numbers are not allowed for hours",
-                        ));
-                    }
-                    day_time = Some(
-                        day_time
-                            .unwrap_or_default()
-                            .checked_add(
-                                apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
-                                    .checked_mul(3600)
-                                    .ok_or(OVERFLOW_ERROR)?,
-                            )
-                            .ok_or(OVERFLOW_ERROR)?,
-                    );
-                    state = AFTER_HOUR;
-                }
-                Some('M') if (AFTER_T..AFTER_MINUTE).contains(&state) => {
-                    if number_str.contains('.') {
-                        return Err(XsdParseError::msg(
-                            "Decimal numbers are not allowed for minutes",
-                        ));
-                    }
-                    day_time = Some(
-                        day_time
-                            .unwrap_or_default()
-                            .checked_add(
-                                apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
-                                    .checked_mul(60)
-                                    .ok_or(OVERFLOW_ERROR)?,
-                            )
-                            .ok_or(OVERFLOW_ERROR)?,
-                    );
-                    state = AFTER_MINUTE;
-                }
-                Some('S') if (AFTER_T..AFTER_SECOND).contains(&state) => {
-                    day_time = Some(
-                        day_time
-                            .unwrap_or_default()
-                            .checked_add(apply_decimal_neg(
-                                Decimal::from_str(number_str)?,
-                                is_negative,
-                            )?)
-                            .ok_or(OVERFLOW_ERROR)?,
-                    );
-                    state = AFTER_SECOND;
-                }
-                Some(_) => return Err(XsdParseError::msg("Unexpected type character")),
-                None => {
-                    return Err(XsdParseError::msg(
-                        "Numbers in durations must be followed by a type character",
-                    ))
-                }
-            }
-            input = &left[1..];
-        }
-    }
-
-    Ok((
-        DurationParts {
-            year_month,
-            day_time,
-        },
-        input,
-    ))
-}
-
-fn apply_i64_neg(value: i64, is_negative: bool) -> Result<i64, XsdParseError> {
-    if is_negative {
-        value.checked_neg().ok_or(OVERFLOW_ERROR)
-    } else {
-        Ok(value)
-    }
-}
-
-fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result<Decimal, XsdParseError> {
-    if is_negative {
-        value.checked_neg().ok_or(OVERFLOW_ERROR)
-    } else {
-        Ok(value)
-    }
-}
-
-pub fn parse_duration(input: &str) -> Result<Duration, XsdParseError> {
-    let parts = ensure_complete(input, duration_parts)?;
-    if parts.year_month.is_none() && parts.day_time.is_none() {
-        return Err(XsdParseError::msg("Empty duration"));
-    }
-    Ok(Duration::new(
-        parts.year_month.unwrap_or(0),
-        parts.day_time.unwrap_or_default(),
-    ))
-}
-
-pub fn parse_year_month_duration(input: &str) -> Result<YearMonthDuration, XsdParseError> {
-    let parts = ensure_complete(input, duration_parts)?;
-    if parts.day_time.is_some() {
-        return Err(XsdParseError::msg(
-            "There must not be any day or time component in a yearMonthDuration",
-        ));
-    }
-    Ok(YearMonthDuration::new(parts.year_month.ok_or(
-        XsdParseError::msg("No year and month values found"),
-    )?))
-}
-
-pub fn parse_day_time_duration(input: &str) -> Result<DayTimeDuration, XsdParseError> {
-    let parts = ensure_complete(input, duration_parts)?;
-    if parts.year_month.is_some() {
-        return Err(XsdParseError::msg(
-            "There must not be any year or month component in a dayTimeDuration",
-        ));
-    }
-    Ok(DayTimeDuration::new(parts.day_time.ok_or(
-        XsdParseError::msg("No day or time values found"),
-    )?))
-}
-
-// [16]   dateTimeLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag 'T' ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
-fn date_time_lexical_rep(input: &str) -> Result<(DateTime, &str), XsdParseError> {
-    let (year, input) = year_frag(input)?;
-    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
-    let (month, input) = month_frag(input)?;
-    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
-    let (day, input) = day_frag(input)?;
-    let input = expect_char(input, 'T', "The date and time must be separated by 'T'")?;
-    let (hour, input) = hour_frag(input)?;
-    let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
-    let (minute, input) = minute_frag(input)?;
-    let input = expect_char(
-        input,
-        ':',
-        "The minutes and seconds must be separated by ':'",
-    )?;
-    let (second, input) = second_frag(input)?;
-    // We validate 24:00:00
-    if hour == 24 && minute != 0 && second != Decimal::from(0) {
-        return Err(XsdParseError::msg(
-            "Times are not allowed to be after 24:00:00",
-        ));
-    }
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((
-        DateTime::new(year, month, day, hour, minute, second, timezone_offset)?,
-        input,
-    ))
-}
-
-pub fn parse_date_time(input: &str) -> Result<DateTime, XsdParseError> {
-    ensure_complete(input, date_time_lexical_rep)
-}
-
-// [17]   timeLexicalRep ::= ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
-fn time_lexical_rep(input: &str) -> Result<(Time, &str), XsdParseError> {
-    let (hour, input) = hour_frag(input)?;
-    let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
-    let (minute, input) = minute_frag(input)?;
-    let input = expect_char(
-        input,
-        ':',
-        "The minutes and seconds must be separated by ':'",
-    )?;
-    let (second, input) = second_frag(input)?;
-    // We validate 24:00:00
-    if hour == 24 && minute != 0 && second != Decimal::from(0) {
-        return Err(XsdParseError::msg(
-            "Times are not allowed to be after 24:00:00",
-        ));
-    }
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((Time::new(hour, minute, second, timezone_offset)?, input))
-}
-
-pub fn parse_time(input: &str) -> Result<Time, XsdParseError> {
-    ensure_complete(input, time_lexical_rep)
-}
-
-// [18]   dateLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag timezoneFrag?   Constraint:  Day-of-month Representations
-fn date_lexical_rep(input: &str) -> Result<(Date, &str), XsdParseError> {
-    let (year, input) = year_frag(input)?;
-    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
-    let (month, input) = month_frag(input)?;
-    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
-    let (day, input) = day_frag(input)?;
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((Date::new(year, month, day, timezone_offset)?, input))
-}
-
-pub fn parse_date(input: &str) -> Result<Date, XsdParseError> {
-    ensure_complete(input, date_lexical_rep)
-}
-
-// [19]   gYearMonthLexicalRep ::= yearFrag '-' monthFrag timezoneFrag?
-fn g_year_month_lexical_rep(input: &str) -> Result<(GYearMonth, &str), XsdParseError> {
-    let (year, input) = year_frag(input)?;
-    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
-    let (month, input) = month_frag(input)?;
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((GYearMonth::new(year, month, timezone_offset)?, input))
-}
-
-pub fn parse_g_year_month(input: &str) -> Result<GYearMonth, XsdParseError> {
-    ensure_complete(input, g_year_month_lexical_rep)
-}
-
-// [20]   gYearLexicalRep ::= yearFrag timezoneFrag?
-fn g_year_lexical_rep(input: &str) -> Result<(GYear, &str), XsdParseError> {
-    let (year, input) = year_frag(input)?;
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((GYear::new(year, timezone_offset)?, input))
-}
-
-pub fn parse_g_year(input: &str) -> Result<GYear, XsdParseError> {
-    ensure_complete(input, g_year_lexical_rep)
-}
-
-// [21]   gMonthDayLexicalRep ::= '--' monthFrag '-' dayFrag timezoneFrag?   Constraint:  Day-of-month Representations
-fn g_month_day_lexical_rep(input: &str) -> Result<(GMonthDay, &str), XsdParseError> {
-    let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
-    let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
-    let (month, input) = month_frag(input)?;
-    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
-    let (day, input) = day_frag(input)?;
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((GMonthDay::new(month, day, timezone_offset)?, input))
-}
-
-pub fn parse_g_month_day(input: &str) -> Result<GMonthDay, XsdParseError> {
-    ensure_complete(input, g_month_day_lexical_rep)
-}
-
-// [22]   gDayLexicalRep ::= '---' dayFrag timezoneFrag?
-fn g_day_lexical_rep(input: &str) -> Result<(GDay, &str), XsdParseError> {
-    let input = expect_char(input, '-', "gDay values must start with '---'")?;
-    let input = expect_char(input, '-', "gDay values must start with '---'")?;
-    let input = expect_char(input, '-', "gDay values must start with '---'")?;
-    let (day, input) = day_frag(input)?;
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((GDay::new(day, timezone_offset)?, input))
-}
-
-pub fn parse_g_day(input: &str) -> Result<GDay, XsdParseError> {
-    ensure_complete(input, g_day_lexical_rep)
-}
-
-// [23]   gMonthLexicalRep ::= '--' monthFrag timezoneFrag?
-fn g_month_lexical_rep(input: &str) -> Result<(GMonth, &str), XsdParseError> {
-    let input = expect_char(input, '-', "gMonth values must start with '--'")?;
-    let input = expect_char(input, '-', "gMonth values must start with '--'")?;
-    let (month, input) = month_frag(input)?;
-    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
-    Ok((GMonth::new(month, timezone_offset)?, input))
-}
-
-pub fn parse_g_month(input: &str) -> Result<GMonth, XsdParseError> {
-    ensure_complete(input, g_month_lexical_rep)
-}
-
-// [56]   yearFrag ::= '-'? (([1-9] digit digit digit+)) | ('0' digit digit digit))
-fn year_frag(input: &str) -> Result<(i64, &str), XsdParseError> {
-    let (sign, input) = if let Some(left) = input.strip_prefix('-') {
-        (-1, left)
-    } else {
-        (1, input)
-    };
-    let (number_str, input) = integer_prefix(input);
-    if number_str.len() < 4 {
-        return Err(XsdParseError::msg("The year should be encoded on 4 digits"));
-    }
-    if number_str.len() > 4 && number_str.starts_with('0') {
-        return Err(XsdParseError::msg(
-            "The years value must not start with 0 if it can be encoded in at least 4 digits",
-        ));
-    }
-    let number = i64::from_str(number_str)?;
-    Ok((sign * number, input))
-}
-
-// [57]   monthFrag ::= ('0' [1-9]) | ('1' [0-2])
-fn month_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
-    let (number_str, input) = integer_prefix(input);
-    if number_str.len() != 2 {
-        return Err(XsdParseError::msg("Month must be encoded with two digits"));
-    }
-    let number = u8::from_str(number_str)?;
-    if !(1..=12).contains(&number) {
-        return Err(XsdParseError::msg("Month must be between 01 and 12"));
-    }
-    Ok((number, input))
-}
-
-// [58]   dayFrag ::= ('0' [1-9]) | ([12] digit) | ('3' [01])
-fn day_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
-    let (number_str, input) = integer_prefix(input);
-    if number_str.len() != 2 {
-        return Err(XsdParseError::msg("Day must be encoded with two digits"));
-    }
-    let number = u8::from_str(number_str)?;
-    if !(1..=31).contains(&number) {
-        return Err(XsdParseError::msg("Day must be between 01 and 31"));
-    }
-    Ok((number, input))
-}
-
-// [59]   hourFrag ::= ([01] digit) | ('2' [0-3])
-// We also allow 24 for ease of parsing
-fn hour_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
-    let (number_str, input) = integer_prefix(input);
-    if number_str.len() != 2 {
-        return Err(XsdParseError::msg("Hours must be encoded with two digits"));
-    }
-    let number = u8::from_str(number_str)?;
-    if !(0..=24).contains(&number) {
-        return Err(XsdParseError::msg("Hours must be between 00 and 24"));
-    }
-    Ok((number, input))
-}
-
-// [60]   minuteFrag ::= [0-5] digit
-fn minute_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
-    let (number_str, input) = integer_prefix(input);
-    if number_str.len() != 2 {
-        return Err(XsdParseError::msg(
-            "Minutes must be encoded with two digits",
-        ));
-    }
-    let number = u8::from_str(number_str)?;
-    if !(0..=59).contains(&number) {
-        return Err(XsdParseError::msg("Minutes must be between 00 and 59"));
-    }
-    Ok((number, input))
-}
-
-// [61]   secondFrag ::= ([0-5] digit) ('.' digit+)?
-fn second_frag(input: &str) -> Result<(Decimal, &str), XsdParseError> {
-    let (number_str, input) = decimal_prefix(input);
-    let (before_dot_str, _) = number_str.split_once('.').unwrap_or((number_str, ""));
-    if before_dot_str.len() != 2 {
-        return Err(XsdParseError::msg(
-            "Seconds must be encoded with two digits",
-        ));
-    }
-    let number = Decimal::from_str(number_str)?;
-    if number < Decimal::from(0) || number >= Decimal::from(60) {
-        return Err(XsdParseError::msg("Seconds must be between 00 and 60"));
-    }
-    if number_str.ends_with('.') {
-        return Err(XsdParseError::msg(
-            "Seconds are not allowed to end with a dot",
-        ));
-    }
-    Ok((number, input))
-}
-
-// [63]   timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00')
-fn timezone_frag(input: &str) -> Result<(TimezoneOffset, &str), XsdParseError> {
-    if let Some(left) = input.strip_prefix('Z') {
-        return Ok((TimezoneOffset::UTC, left));
-    }
-    let (sign, input) = if let Some(left) = input.strip_prefix('-') {
-        (-1, left)
-    } else if let Some(left) = input.strip_prefix('+') {
-        (1, left)
-    } else {
-        (1, input)
-    };
-
-    let (hour_str, input) = integer_prefix(input);
-    if hour_str.len() != 2 {
-        return Err(XsdParseError::msg(
-            "The timezone hours must be encoded with two digits",
-        ));
-    }
-    let hours = i16::from_str(hour_str)?;
-
-    let input = expect_char(
-        input,
-        ':',
-        "The timezone hours and minutes must be separated by ':'",
-    )?;
-    let (minutes, input) = minute_frag(input)?;
-
-    if hours > 13 && !(hours == 14 && minutes == 0) {
-        return Err(XsdParseError::msg(
-            "The timezone hours must be between 00 and 13",
-        ));
-    }
-
-    Ok((
-        TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes)))?,
-        input,
-    ))
-}
-
-fn ensure_complete<T>(
-    input: &str,
-    parse: impl FnOnce(&str) -> Result<(T, &str), XsdParseError>,
-) -> Result<T, XsdParseError> {
-    let (result, left) = parse(input)?;
-    if !left.is_empty() {
-        return Err(XsdParseError::msg("Unrecognized value suffix"));
-    }
-    Ok(result)
-}
-
-fn expect_char<'a>(
-    input: &'a str,
-    constant: char,
-    error_message: &'static str,
-) -> Result<&'a str, XsdParseError> {
-    if let Some(left) = input.strip_prefix(constant) {
-        Ok(left)
-    } else {
-        Err(XsdParseError::msg(error_message))
-    }
-}
-
-fn integer_prefix(input: &str) -> (&str, &str) {
-    let mut end = input.len();
-    for (i, c) in input.char_indices() {
-        if !c.is_ascii_digit() {
-            end = i;
-            break;
-        }
-    }
-    input.split_at(end)
-}
-
-fn decimal_prefix(input: &str) -> (&str, &str) {
-    let mut end = input.len();
-    let mut dot_seen = false;
-    for (i, c) in input.char_indices() {
-        if c.is_ascii_digit() {
-            // Ok
-        } else if c == '.' && !dot_seen {
-            dot_seen = true;
-        } else {
-            end = i;
-            break;
-        }
-    }
-    input.split_at(end)
-}
-
-fn optional_end<T>(
-    input: &str,
-    parse: impl FnOnce(&str) -> Result<(T, &str), XsdParseError>,
-) -> Result<(Option<T>, &str), XsdParseError> {
-    Ok(if input.is_empty() {
-        (None, input)
-    } else {
-        let (result, input) = parse(input)?;
-        (Some(result), input)
-    })
-}
diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs
index 90a52efa..ad50cd2f 100644
--- a/lib/src/sparql/eval.rs
+++ b/lib/src/sparql/eval.rs
@@ -142,7 +142,7 @@ impl SimpleEvaluator {
         Self {
             dataset,
             base_iri,
-            now: DateTime::now().unwrap(),
+            now: DateTime::now(),
             service_handler,
             custom_functions,
             run_stats,
@@ -1605,8 +1605,8 @@ impl SimpleEvaluator {
                             stat_children,
                         );
                         Rc::new(move |tuple| match e(tuple)? {
-                            EncodedTerm::IntegerLiteral(value) => Some(value.abs().into()),
-                            EncodedTerm::DecimalLiteral(value) => Some(value.abs().into()),
+                            EncodedTerm::IntegerLiteral(value) => Some(value.checked_abs()?.into()),
+                            EncodedTerm::DecimalLiteral(value) => Some(value.checked_abs()?.into()),
                             EncodedTerm::FloatLiteral(value) => Some(value.abs().into()),
                             EncodedTerm::DoubleLiteral(value) => Some(value.abs().into()),
                             _ => None,
@@ -1620,7 +1620,9 @@ impl SimpleEvaluator {
                         );
                         Rc::new(move |tuple| match e(tuple)? {
                             EncodedTerm::IntegerLiteral(value) => Some(value.into()),
-                            EncodedTerm::DecimalLiteral(value) => Some(value.ceil().into()),
+                            EncodedTerm::DecimalLiteral(value) => {
+                                Some(value.checked_ceil()?.into())
+                            }
                             EncodedTerm::FloatLiteral(value) => Some(value.ceil().into()),
                             EncodedTerm::DoubleLiteral(value) => Some(value.ceil().into()),
                             _ => None,
@@ -1634,7 +1636,9 @@ impl SimpleEvaluator {
                         );
                         Rc::new(move |tuple| match e(tuple)? {
                             EncodedTerm::IntegerLiteral(value) => Some(value.into()),
-                            EncodedTerm::DecimalLiteral(value) => Some(value.floor().into()),
+                            EncodedTerm::DecimalLiteral(value) => {
+                                Some(value.checked_floor()?.into())
+                            }
                             EncodedTerm::FloatLiteral(value) => Some(value.floor().into()),
                             EncodedTerm::DoubleLiteral(value) => Some(value.floor().into()),
                             _ => None,
@@ -1648,7 +1652,9 @@ impl SimpleEvaluator {
                         );
                         Rc::new(move |tuple| match e(tuple)? {
                             EncodedTerm::IntegerLiteral(value) => Some(value.into()),
-                            EncodedTerm::DecimalLiteral(value) => Some(value.round().into()),
+                            EncodedTerm::DecimalLiteral(value) => {
+                                Some(value.checked_round()?.into())
+                            }
                             EncodedTerm::FloatLiteral(value) => Some(value.round().into()),
                             EncodedTerm::DoubleLiteral(value) => Some(value.round().into()),
                             _ => None,
@@ -5851,18 +5857,18 @@ fn format_list<T: ToString>(values: impl IntoIterator<Item = T>) -> String {
 }
 
 pub struct Timer {
-    start: Option<DateTime>,
+    start: DateTime,
 }
 
 impl Timer {
     pub fn now() -> Self {
         Self {
-            start: DateTime::now().ok(),
+            start: DateTime::now(),
         }
     }
 
     pub fn elapsed(&self) -> Option<DayTimeDuration> {
-        DateTime::now().ok()?.checked_sub(self.start?)
+        DateTime::now().checked_sub(self.start)
     }
 }