Repository: toml-rs/toml (language: rust)

Issue:
Should we define a custom datetime?
I suspect most people are not dealing with datetimes and so having a dependency on `chrono` is extra bloat.

`toml-rs` takes an interesting approach to this problem and just made it public

Should we do similar?
- This would mean going back on `Value` having a variant per case.
- There is the non-sense case of only having an offset that would fail encoding

```rust
#[derive(PartialEq, Clone)]
pub struct Datetime {
    /// Optional date.
    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Date*.
    pub date: Option<Date>,

    /// Optional time.
    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Time*.
    pub time: Option<Time>,

    /// Optional offset.
    /// Required for: *Offset Date-Time*.
    pub offset: Option<Offset>,
}

#[derive(PartialEq, Clone)]
pub struct Date {
    /// Year: four digits
    pub year: u16,
    /// Month: 1 to 12
    pub month: u8,
    /// Day: 1 to {28, 29, 30, 31} (based on month/year)
    pub day: u8,
}

pub struct Time {
    /// Hour: 0 to 23
    pub hour: u8,
    /// Minute: 0 to 59
    pub minute: u8,
    /// Second: 0 to {58, 59, 60} (based on leap second rules)
    pub second: u8,
    /// Nanosecond: 0 to 999_999_999
    pub nanosecond: u32,
}

#[derive(PartialEq, Clone)]
pub enum Offset {
    /// > A suffix which, when applied to a time, denotes a UTC offset of 00:00;
    /// > often spoken "Zulu" from the ICAO phonetic alphabet representation of
    /// > the letter "Z". --- [RFC 3339 section 2]
    ///
    /// [RFC 3339 section 2]: https://datatracker.ietf.org/doc/html/rfc3339#section-2
    Z,

    /// Offset between local time and UTC
    Custom {
        /// Hours: -12 to +12
        hours: i8,

        /// Minutes: 0 to 59
        minutes: u8,
    },
}
```


Discussion / hints:
> I suspect most people are not dealing with datetimes and so having a dependency on chrono is extra bloat.

yeah, I suspect that too. Creating a custom simple type for that if that would not complicate parsing (I think we're not relying much on `chrono` for parsing) that is similar to toml-rs is probably a good idea.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index ad298637..d6995ad5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,6 @@ default = []
 easy = ["serde"]
 
 [dependencies]
-chrono = "0.4.10"
 linked-hash-map = "0.5.2"
 combine = "4.5.2"
 vec1 = "1"
diff --git a/src/datetime.rs b/src/datetime.rs
index 63100738..818686bb 100644
--- a/src/datetime.rs
+++ b/src/datetime.rs
@@ -1,135 +1,346 @@
+use std::convert::TryFrom;
+use std::fmt;
 use std::str::FromStr;
 
+use combine::stream::position::Stream;
+
 use crate::parser;
 
-/// A RFC-3339 formatted TOML date/time w/ timezone
+/// A parsed TOML datetime value
 ///
-/// # Examples
+/// This structure is intended to represent the datetime primitive type that can
+/// be encoded into TOML documents. This type is a parsed version that contains
+/// all metadata internally.
 ///
-/// ```rust
-/// let raw = "1996-12-19T16:39:57-08:00";
-/// let toml: toml_edit::OffsetDateTime = raw.parse().unwrap();
-/// let backagain = toml.to_string();
-/// assert_eq!(raw, backagain);
-/// ```
-#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Hash)]
-pub struct OffsetDateTime {
-    pub(crate) inner: chrono::DateTime<chrono::FixedOffset>,
-}
+/// Currently this type is intentionally conservative and only supports
+/// `to_string` as an accessor. Over time though it's intended that it'll grow
+/// more support!
+///
+/// Note that if you're using `Deserialize` to deserialize a TOML document, you
+/// can use this as a placeholder for where you're expecting a datetime to be
+/// specified.
+///
+/// Also note though that while this type implements `Serialize` and
+/// `Deserialize` it's only recommended to use this type with the TOML format,
+/// otherwise encoded in other formats it may look a little odd.
+///
+/// Depending on how the option values are used, this struct will correspond
+/// with one of the following four datetimes from the [TOML v1.0.0 spec]:
+///
+/// | `date`    | `time`    | `offset`  | TOML type          |
+/// | --------- | --------- | --------- | ------------------ |
+/// | `Some(_)` | `Some(_)` | `Some(_)` | [Offset Date-Time] |
+/// | `Some(_)` | `Some(_)` | `None`    | [Local Date-Time]  |
+/// | `Some(_)` | `None`    | `None`    | [Local Date]       |
+/// | `None`    | `Some(_)` | `None`    | [Local Time]       |
+///
+/// **1. Offset Date-Time**: If all the optional values are used, `Datetime`
+/// corresponds to an [Offset Date-Time]. From the TOML v1.0.0 spec:
+///
+/// > To unambiguously represent a specific instant in time, you may use an
+/// > RFC 3339 formatted date-time with offset.
+/// >
+/// > ```toml
+/// > odt1 = 1979-05-27T07:32:00Z
+/// > odt2 = 1979-05-27T00:32:00-07:00
+/// > odt3 = 1979-05-27T00:32:00.999999-07:00
+/// > ```
+/// >
+/// > For the sake of readability, you may replace the T delimiter between date
+/// > and time with a space character (as permitted by RFC 3339 section 5.6).
+/// >
+/// > ```toml
+/// > odt4 = 1979-05-27 07:32:00Z
+/// > ```
+///
+/// **2. Local Date-Time**: If `date` and `time` are given but `offset` is
+/// `None`, `Datetime` corresponds to a [Local Date-Time]. From the spec:
+///
+/// > If you omit the offset from an RFC 3339 formatted date-time, it will
+/// > represent the given date-time without any relation to an offset or
+/// > timezone. It cannot be converted to an instant in time without additional
+/// > information. Conversion to an instant, if required, is implementation-
+/// > specific.
+/// >
+/// > ```toml
+/// > ldt1 = 1979-05-27T07:32:00
+/// > ldt2 = 1979-05-27T00:32:00.999999
+/// > ```
+///
+/// **3. Local Date**: If only `date` is given, `Datetime` corresponds to a
+/// [Local Date]; see the docs for [`Date`].
+///
+/// **4. Local Time**: If only `time` is given, `Datetime` corresponds to a
+/// [Local Time]; see the docs for [`Time`].
+///
+/// [TOML v1.0.0 spec]: https://toml.io/en/v1.0.0
+/// [Offset Date-Time]: https://toml.io/en/v1.0.0#offset-date-time
+/// [Local Date-Time]: https://toml.io/en/v1.0.0#local-date-time
+/// [Local Date]: https://toml.io/en/v1.0.0#local-date
+/// [Local Time]: https://toml.io/en/v1.0.0#local-time
+#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "serde", serde(try_from = "String"))]
+pub struct Datetime {
+    /// Optional date.
+    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Date*.
+    pub date: Option<Date>,
 
-impl FromStr for OffsetDateTime {
-    type Err = parser::TomlError;
+    /// Optional time.
+    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Time*.
+    pub time: Option<Time>,
 
-    /// Parses a value from a &str
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let inner = chrono::DateTime::parse_from_rfc3339(s)
-            .map_err(|e| parser::TomlError::custom(e.to_string()))?;
-        Ok(Self { inner })
+    /// Optional offset.
+    /// Required for: *Offset Date-Time*.
+    pub offset: Option<Offset>,
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for Datetime {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
     }
 }
 
-impl std::fmt::Display for OffsetDateTime {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        let s = self.inner.to_rfc3339();
-        s.fmt(f)
+impl From<Date> for Datetime {
+    fn from(other: Date) -> Self {
+        Datetime {
+            date: Some(other),
+            time: None,
+            offset: None,
+        }
     }
 }
 
-/// A RFC-3339 formatted TOML date/time w/o timezone
-///
-/// # Examples
-///
-/// ```rust
-/// let raw = "1996-12-19T16:39:57";
-/// let toml: toml_edit::LocalDateTime = raw.parse().unwrap();
-/// let backagain = toml.to_string();
-/// assert_eq!(raw, backagain);
-/// ```
-#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Hash)]
-pub struct LocalDateTime {
-    pub(crate) inner: chrono::NaiveDateTime,
+impl From<Time> for Datetime {
+    fn from(other: Time) -> Self {
+        Datetime {
+            date: None,
+            time: Some(other),
+            offset: None,
+        }
+    }
 }
 
-impl FromStr for LocalDateTime {
+impl FromStr for Datetime {
     type Err = parser::TomlError;
 
     /// Parses a value from a &str
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let inner = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f")
-            .map_err(|e| parser::TomlError::custom(e.to_string()))?;
-        Ok(Self { inner })
+        use combine::EasyParser;
+        let result = parser::datetime::date_time().easy_parse(Stream::new(s));
+        match result {
+            Ok((_, ref rest)) if !rest.input.is_empty() => {
+                Err(parser::TomlError::from_unparsed(rest.positioner, s))
+            }
+            Ok((dt, _)) => Ok(dt),
+            Err(e) => Err(parser::TomlError::new(e, s)),
+        }
     }
 }
 
-impl std::fmt::Display for LocalDateTime {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        let s = self.inner.format("%Y-%m-%dT%H:%M:%S%.f");
-        s.fmt(f)
+impl<'a> TryFrom<&'a str> for Datetime {
+    type Error = parser::TomlError;
+
+    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
+        s.parse()
     }
 }
 
-/// A RFC-3339 formatted TOML date
+impl TryFrom<String> for Datetime {
+    type Error = parser::TomlError;
+
+    fn try_from(s: String) -> Result<Self, Self::Error> {
+        s.parse()
+    }
+}
+
+impl fmt::Display for Datetime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(ref date) = self.date {
+            write!(f, "{}", date)?;
+        }
+        if let Some(ref time) = self.time {
+            if self.date.is_some() {
+                write!(f, "T")?;
+            }
+            write!(f, "{}", time)?;
+        }
+        if let Some(ref offset) = self.offset {
+            write!(f, "{}", offset)?;
+        }
+        Ok(())
+    }
+}
+
+/// A parsed TOML date value
+///
+/// May be part of a [`Datetime`]. Alone, `Date` corresponds to a [Local Date].
+/// From the TOML v1.0.0 spec:
 ///
-/// # Examples
+/// > If you include only the date portion of an RFC 3339 formatted date-time,
+/// > it will represent that entire day without any relation to an offset or
+/// > timezone.
+/// >
+/// > ```toml
+/// > ld1 = 1979-05-27
+/// > ```
 ///
-/// ```rust
-/// let raw = "1996-12-19";
-/// let toml: toml_edit::LocalDate = raw.parse().unwrap();
-/// let backagain = toml.to_string();
-/// assert_eq!(raw, backagain);
-/// ```
-#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Hash)]
-pub struct LocalDate {
-    pub(crate) inner: chrono::NaiveDate,
+/// [Local Date]: https://toml.io/en/v1.0.0#local-date
+#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
+pub struct Date {
+    /// Year: four digits
+    pub year: u16,
+    /// Month: 1 to 12
+    pub month: u8,
+    /// Day: 1 to {28, 29, 30, 31} (based on month/year)
+    pub day: u8,
 }
 
-impl FromStr for LocalDate {
+impl FromStr for Date {
     type Err = parser::TomlError;
 
     /// Parses a value from a &str
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let inner = s
-            .parse::<chrono::NaiveDate>()
-            .map_err(|e| parser::TomlError::custom(e.to_string()))?;
-        Ok(Self { inner })
+        use combine::EasyParser;
+        let result = parser::datetime::full_date().easy_parse(Stream::new(s));
+        match result {
+            Ok((_, ref rest)) if !rest.input.is_empty() => {
+                Err(parser::TomlError::from_unparsed(rest.positioner, s))
+            }
+            Ok((dt, _)) => Ok(dt),
+            Err(e) => Err(parser::TomlError::new(e, s)),
+        }
+    }
+}
+
+impl<'a> TryFrom<&'a str> for Date {
+    type Error = parser::TomlError;
+
+    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
+        s.parse()
+    }
+}
+
+impl TryFrom<String> for Date {
+    type Error = parser::TomlError;
+
+    fn try_from(s: String) -> Result<Self, Self::Error> {
+        s.parse()
     }
 }
 
-impl std::fmt::Display for LocalDate {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        self.inner.fmt(f)
+impl fmt::Display for Date {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
     }
 }
 
-/// A RFC-3339 formatted TOML time
+/// A parsed TOML time value
+///
+/// May be part of a [`Datetime`]. Alone, `Time` corresponds to a [Local Time].
+/// From the TOML v1.0.0 spec:
 ///
-/// # Examples
+/// > If you include only the time portion of an RFC 3339 formatted date-time,
+/// > it will represent that time of day without any relation to a specific
+/// > day or any offset or timezone.
+/// >
+/// > ```toml
+/// > lt1 = 07:32:00
+/// > lt2 = 00:32:00.999999
+/// > ```
+/// >
+/// > Millisecond precision is required. Further precision of fractional
+/// > seconds is implementation-specific. If the value contains greater
+/// > precision than the implementation can support, the additional precision
+/// > must be truncated, not rounded.
 ///
-/// ```rust
-/// let raw = "23:56:04.000123456";
-/// let toml: toml_edit::LocalTime = raw.parse().unwrap();
-/// let backagain = toml.to_string();
-/// assert_eq!(raw, backagain);
-/// ```
-#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Hash)]
-pub struct LocalTime {
-    pub(crate) inner: chrono::NaiveTime,
+/// [Local Time]: https://toml.io/en/v1.0.0#local-time
+#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
+pub struct Time {
+    /// Hour: 0 to 23
+    pub hour: u8,
+    /// Minute: 0 to 59
+    pub minute: u8,
+    /// Second: 0 to {58, 59, 60} (based on leap second rules)
+    pub second: u8,
+    /// Nanosecond: 0 to 999_999_999
+    pub nanosecond: u32,
 }
 
-impl FromStr for LocalTime {
+impl FromStr for Time {
     type Err = parser::TomlError;
 
     /// Parses a value from a &str
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let inner = s
-            .parse::<chrono::NaiveTime>()
-            .map_err(|e| parser::TomlError::custom(e.to_string()))?;
-        Ok(Self { inner })
+        use combine::EasyParser;
+        let result = parser::datetime::partial_time().easy_parse(Stream::new(s));
+        match result {
+            Ok((_, ref rest)) if !rest.input.is_empty() => {
+                Err(parser::TomlError::from_unparsed(rest.positioner, s))
+            }
+            Ok((dt, _)) => Ok(dt),
+            Err(e) => Err(parser::TomlError::new(e, s)),
+        }
     }
 }
 
-impl std::fmt::Display for LocalTime {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        self.inner.fmt(f)
+impl<'a> TryFrom<&'a str> for Time {
+    type Error = parser::TomlError;
+
+    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
+        s.parse()
+    }
+}
+
+impl TryFrom<String> for Time {
+    type Error = parser::TomlError;
+
+    fn try_from(s: String) -> Result<Self, Self::Error> {
+        s.parse()
+    }
+}
+
+impl fmt::Display for Time {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
+        if self.nanosecond != 0 {
+            let s = format!("{:09}", self.nanosecond);
+            write!(f, ".{}", s.trim_end_matches('0'))?;
+        }
+        Ok(())
+    }
+}
+
+/// A parsed TOML time offset
+///
+#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
+pub enum Offset {
+    /// > A suffix which, when applied to a time, denotes a UTC offset of 00:00;
+    /// > often spoken "Zulu" from the ICAO phonetic alphabet representation of
+    /// > the letter "Z". --- [RFC 3339 section 2]
+    ///
+    /// [RFC 3339 section 2]: https://datatracker.ietf.org/doc/html/rfc3339#section-2
+    Z,
+
+    /// Offset between local time and UTC
+    Custom {
+        /// Hours: -12 to +12
+        hours: i8,
+
+        /// Minutes: 0 to 59
+        minutes: u8,
+    },
+}
+
+impl fmt::Display for Offset {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match *self {
+            Offset::Z => write!(f, "Z"),
+            Offset::Custom { hours, minutes } => write!(f, "{:+03}:{:02}", hours, minutes),
+        }
     }
 }
diff --git a/src/easy/datetime.rs b/src/easy/datetime.rs
index 57dee1e9..acbffbf7 100644
--- a/src/easy/datetime.rs
+++ b/src/easy/datetime.rs
@@ -1,56 +1,1 @@
 pub use crate::datetime::*;
-
-/// A parsed TOML datetime value
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
-#[serde(untagged)]
-pub enum Datetime {
-    /// An RFC 3339 formatted date-time with offset.
-    #[serde(with = "string")]
-    OffsetDateTime(OffsetDateTime),
-    /// An RFC 3339 formatted date-time without offset.
-    #[serde(with = "string")]
-    LocalDateTime(LocalDateTime),
-    /// Date portion of an RFC 3339 formatted date-time.
-    #[serde(with = "string")]
-    LocalDate(LocalDate),
-    /// Time portion of an RFC 3339 formatted date-time.
-    #[serde(with = "string")]
-    LocalTime(LocalTime),
-}
-
-impl std::fmt::Display for Datetime {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        match self {
-            Datetime::OffsetDateTime(v) => v.fmt(f),
-            Datetime::LocalDateTime(v) => v.fmt(f),
-            Datetime::LocalDate(v) => v.fmt(f),
-            Datetime::LocalTime(v) => v.fmt(f),
-        }
-    }
-}
-
-mod string {
-    use std::fmt::Display;
-    use std::str::FromStr;
-
-    use serde::{de, Deserialize, Deserializer, Serializer};
-
-    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        T: Display,
-        S: Serializer,
-    {
-        serializer.collect_str(value)
-    }
-
-    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
-    where
-        T: FromStr,
-        T::Err: Display,
-        D: Deserializer<'de>,
-    {
-        String::deserialize(deserializer)?
-            .parse()
-            .map_err(de::Error::custom)
-    }
-}
diff --git a/src/easy/de/value.rs b/src/easy/de/value.rs
index b5e6618b..2e37faad 100644
--- a/src/easy/de/value.rs
+++ b/src/easy/de/value.rs
@@ -24,10 +24,7 @@ impl<'de, 'a> serde::Deserializer<'de> for ValueDeserializer {
             crate::Value::Integer(v) => visitor.visit_i64(v.into_value()),
             crate::Value::Float(v) => visitor.visit_f64(v.into_value()),
             crate::Value::Boolean(v) => visitor.visit_bool(v.into_value()),
-            crate::Value::OffsetDateTime(v) => visitor.visit_string(v.into_value().to_string()),
-            crate::Value::LocalDateTime(v) => visitor.visit_string(v.into_value().to_string()),
-            crate::Value::LocalDate(v) => visitor.visit_string(v.into_value().to_string()),
-            crate::Value::LocalTime(v) => visitor.visit_string(v.into_value().to_string()),
+            crate::Value::Datetime(v) => visitor.visit_string(v.into_value().to_string()),
             crate::Value::Array(v) => {
                 visitor.visit_seq(crate::easy::de::ArraySeqAccess::with_array(v))
             }
diff --git a/src/encode.rs b/src/encode.rs
index 698f4e64..271d9257 100644
--- a/src/encode.rs
+++ b/src/encode.rs
@@ -52,10 +52,7 @@ impl Display for Value {
             Value::Integer(ref repr) => write!(f, "{}", repr),
             Value::Float(ref repr) => write!(f, "{}", repr),
             Value::Boolean(ref repr) => write!(f, "{}", repr),
-            Value::OffsetDateTime(ref repr) => write!(f, "{}", repr),
-            Value::LocalDateTime(ref repr) => write!(f, "{}", repr),
-            Value::LocalDate(ref repr) => write!(f, "{}", repr),
-            Value::LocalTime(ref repr) => write!(f, "{}", repr),
+            Value::Datetime(ref repr) => write!(f, "{}", repr),
             Value::Array(ref array) => write!(f, "{}", array),
             Value::InlineTable(ref table) => write!(f, "{}", table),
         }
diff --git a/src/item.rs b/src/item.rs
index 3298c063..251d7bc3 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -166,43 +166,13 @@ impl Item {
     }
 
     /// Casts `self` to date-time.
-    pub fn as_offset_datetime(&self) -> Option<&OffsetDateTime> {
-        self.as_value().and_then(Value::as_offset_datetime)
+    pub fn as_datetime(&self) -> Option<&Datetime> {
+        self.as_value().and_then(Value::as_datetime)
     }
 
     /// Returns true iff `self` is a date-time.
-    pub fn is_offset_datetime(&self) -> bool {
-        self.as_offset_datetime().is_some()
-    }
-
-    /// Casts `self` to date-time.
-    pub fn as_local_datetime(&self) -> Option<&LocalDateTime> {
-        self.as_value().and_then(Value::as_local_datetime)
-    }
-
-    /// Returns true iff `self` is a date-time.
-    pub fn is_local_datetime(&self) -> bool {
-        self.as_local_datetime().is_some()
-    }
-
-    /// Casts `self` to date-time.
-    pub fn as_local_date(&self) -> Option<&LocalDate> {
-        self.as_value().and_then(Value::as_local_date)
-    }
-
-    /// Returns true iff `self` is a date-time.
-    pub fn is_local_date(&self) -> bool {
-        self.as_local_date().is_some()
-    }
-
-    /// Casts `self` to date-time.
-    pub fn as_local_time(&self) -> Option<&LocalTime> {
-        self.as_value().and_then(Value::as_local_time)
-    }
-
-    /// Returns true iff `self` is a date-time.
-    pub fn is_local_time(&self) -> bool {
-        self.as_local_time().is_some()
+    pub fn is_datetime(&self) -> bool {
+        self.as_datetime().is_some()
     }
 
     /// Casts `self` to array.
diff --git a/src/parser/datetime.rs b/src/parser/datetime.rs
index 590bd517..f800557b 100644
--- a/src/parser/datetime.rs
+++ b/src/parser/datetime.rs
@@ -1,10 +1,7 @@
 use crate::datetime::*;
 use crate::parser::errors::CustomError;
-use crate::repr::{Formatted, Repr};
-use crate::Value;
-use chrono::TimeZone;
-use combine::parser::char::{char, digit};
-use combine::parser::range::{recognize, take};
+use combine::parser::char::char;
+use combine::parser::range::{take, take_while1};
 use combine::stream::RangeStream;
 use combine::*;
 
@@ -16,7 +13,7 @@ use combine::*;
 // local-date = full-date
 // local-time = partial-time
 // full-time = partial-time time-offset
-parse!(date_time() -> Value, {
+parse!(date_time() -> Datetime, {
     choice!(
         (
             full_date(),
@@ -26,66 +23,43 @@ parse!(date_time() -> Value, {
                 optional(time_offset()),
             ))
         )
-            .map(|(d, opt)| {
+            .map(|(date, opt)| {
                 match opt {
                     // Offset Date-Time
-                    Some((_, t, Some(o))) => {
-                        let dt = chrono::NaiveDateTime::new(d, t);
-                        let dt = OffsetDateTime { inner: o.from_local_datetime(&dt).unwrap() };
-                        let repr = Repr::new_unchecked(dt.to_string());
-                        Value::OffsetDateTime(
-                            Formatted::new(dt, repr)
-                        )
-                    }
-                    // Local Date-Time
-                    Some((_, t, None)) => {
-                        let dt = LocalDateTime { inner: chrono::NaiveDateTime::new(d, t)};
-                        let repr = Repr::new_unchecked(dt.to_string());
-                        Value::LocalDateTime(
-                            Formatted::new(dt, repr)
-                        )
+                    Some((_, time, offset)) => {
+                        Datetime { date: Some(date), time: Some(time), offset }
                     }
                     // Local Date
                     None => {
-                        let dt = LocalDate { inner: d};
-                        let repr = Repr::new_unchecked(dt.to_string());
-                        Value::LocalDate(
-                            Formatted::new(dt, repr)
-                        )
-                    }
+                        Datetime { date: Some(date), time: None, offset: None}
+                    },
                 }
-
             }),
         // Local Time
         partial_time()
             .message("While parsing a Time")
             .map(|t| {
-                let dt = LocalTime { inner: t};
-                let repr = Repr::new_unchecked(dt.to_string());
-                Value::LocalTime(
-                    Formatted::new(dt, repr)
-                )
+                t.into()
             })
     )
         .message("While parsing a Date-Time")
 });
 
 // full-date      = date-fullyear "-" date-month "-" date-mday
-parse!(full_date() -> chrono::NaiveDate, {
+parse!(full_date() -> Date, {
     (
         attempt((date_fullyear(), char('-'))),
         date_month(),
         char('-'),
         date_mday(),
-    ).and_then(|((year, _), month, _, day)| {
-        chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(CustomError::DateOutOfRange { year, month, day })
+    ).map(|((year, _), month, _, day)| {
+        Date { year, month, day }
     })
 });
 
 // partial-time   = time-hour ":" time-minute ":" time-second [time-secfrac]
-// time-secfrac   = "." 1*DIGIT
-parse!(partial_time() -> chrono::NaiveTime, {
-    recognize((
+parse!(partial_time() -> Time, {
+    (
         attempt((
             time_hour(),
             char(':'),
@@ -93,47 +67,59 @@ parse!(partial_time() -> chrono::NaiveTime, {
         time_minute(),
         char(':'),
         time_second(),
-        optional(attempt(char('.')).and(skip_many1(digit()))),
-    )).and_then(|s: &str| s.parse::<chrono::NaiveTime>())
+        optional(attempt(time_secfrac())),
+    ).map(|((hour, _), minute, _, second, nanosecond)| {
+        Time { hour, minute, second, nanosecond: nanosecond.unwrap_or_default() }
+    })
 });
 
 // time-offset    = "Z" / time-numoffset
 // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
-parse!(time_offset() -> chrono::FixedOffset, {
-    attempt(satisfy(|c| c == 'Z' || c == 'z')).map(|_| chrono::FixedOffset::east(0))
+parse!(time_offset() -> Offset, {
+    attempt(satisfy(|c| c == 'Z' || c == 'z')).map(|_| Offset::Z)
         .or(
             (
                 attempt(choice([char('+'), char('-')])),
                 time_hour(),
                 char(':'),
                 time_minute(),
-            ).and_then(|(sign, hour, _, minute)| {
-                const SEC: i32 = 1;
-                const MIN: i32 = 60 * SEC;
-                const HOUR: i32 = 60 * MIN;
-                let secs = (hour as i32) * HOUR + (minute as i32) * MIN;
-                match sign {
-                    '+' => chrono::FixedOffset::east_opt(secs),
-                    '-' => chrono::FixedOffset::west_opt(secs),
+            ).map(|(sign, hours, _, minutes)| {
+                let hours = hours as i8;
+                let hours = match sign {
+                    '+' => hours,
+                    '-' => -hours,
                     _ => unreachable!("Parser prevents this"),
-                }.ok_or(CustomError::OffsetOutOfRange { sign, hour, minute})
+                };
+                Offset::Custom { hours, minutes }
             })
         ).message("While parsing a Time Offset")
 });
 
 // date-fullyear  = 4DIGIT
-parse!(date_fullyear() -> i32, {
-    signed_digits(4)
+parse!(date_fullyear() -> u16, {
+    signed_digits(4).map(|d| d as u16)
 });
 
 // date-month     = 2DIGIT  ; 01-12
-parse!(date_month() -> u32, {
-    unsigned_digits(2)
+parse!(date_month() -> u8, {
+    unsigned_digits(2).map(|d| d as u8).and_then(|v| {
+        if (1..=12).contains(&v) {
+            Ok(v)
+        } else {
+            Err(CustomError::OutOfRange)
+        }
+    })
 });
 
 // date-mday      = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on month/year
-parse!(date_mday() -> u32, {
-    unsigned_digits(2)
+parse!(date_mday() -> u8, {
+    unsigned_digits(2).map(|d| d as u8).and_then(|v| {
+        if (1..=31).contains(&v) {
+            Ok(v)
+        } else {
+            Err(CustomError::OutOfRange)
+        }
+    })
 });
 
 // time-delim     = "T" / %x20 ; T, t, or space
@@ -142,18 +128,51 @@ fn is_time_delim(c: char) -> bool {
 }
 
 // time-hour      = 2DIGIT  ; 00-23
-parse!(time_hour() -> u32, {
-    unsigned_digits(2)
+parse!(time_hour() -> u8, {
+    unsigned_digits(2).map(|d| d as u8).and_then(|v| {
+        if (0..=23).contains(&v) {
+            Ok(v)
+        } else {
+            Err(CustomError::OutOfRange)
+        }
+    })
 });
 
 // time-minute    = 2DIGIT  ; 00-59
-parse!(time_minute() -> u32, {
-    unsigned_digits(2)
+parse!(time_minute() -> u8, {
+    unsigned_digits(2).map(|d| d as u8).and_then(|v| {
+        if (0..=59).contains(&v) {
+            Ok(v)
+        } else {
+            Err(CustomError::OutOfRange)
+        }
+    })
 });
 
 // time-second    = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second rules
-parse!(time_second() -> u32, {
-    unsigned_digits(2)
+parse!(time_second() -> u8, {
+    unsigned_digits(2).map(|d| d as u8).and_then(|v| {
+        if (0..=60).contains(&v) {
+            Ok(v)
+        } else {
+            Err(CustomError::OutOfRange)
+        }
+    })
+});
+
+// time-secfrac   = "." 1*DIGIT
+parse!(time_secfrac() -> u32, {
+    char('.').and(take_while1(|c: char| c.is_digit(10))).and_then::<_, _, CustomError>(|(_, repr): (char, &str)| {
+        let v = repr.parse::<u32>().map_err(|_| CustomError::OutOfRange)?;
+        let consumed = repr.len();
+
+        // scale the number accordingly.
+        static SCALE: [u32; 10] =
+            [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
+        let scale = SCALE.get(consumed).ok_or(CustomError::OutOfRange)?;
+        let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?;
+        Ok(v)
+    })
 });
 
 parse!(signed_digits(count: usize) -> i32, {
diff --git a/src/parser/document.rs b/src/parser/document.rs
index 15905a8b..5d6ae44c 100644
--- a/src/parser/document.rs
+++ b/src/parser/document.rs
@@ -49,7 +49,6 @@ parser! {
          <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
          From<std::num::ParseIntError> +
          From<std::num::ParseFloatError> +
-         From<chrono::ParseError> +
          From<crate::parser::errors::CustomError>
     ] {
         (
diff --git a/src/parser/errors.rs b/src/parser/errors.rs
index 9304c301..2312bcc8 100644
--- a/src/parser/errors.rs
+++ b/src/parser/errors.rs
@@ -24,6 +24,7 @@ impl TomlError {
         )
     }
 
+    #[cfg(feature = "easy")]
     pub(crate) fn custom(message: String) -> Self {
         Self { message }
     }
@@ -107,8 +108,7 @@ pub(crate) enum CustomError {
     DuplicateKey { key: String, table: String },
     InvalidHexEscape(u32),
     UnparsedLine,
-    DateOutOfRange { year: i32, month: u32, day: u32 },
-    OffsetOutOfRange { sign: char, hour: u32, minute: u32 },
+    OutOfRange,
 }
 
 impl StdError for CustomError {
@@ -127,20 +127,7 @@ impl Display for CustomError {
                 writeln!(f, "Invalid hex escape code: {:x} ", h)
             }
             CustomError::UnparsedLine => writeln!(f, "Could not parse the line"),
-            CustomError::DateOutOfRange {
-                ref year,
-                ref month,
-                ref day,
-            } => {
-                writeln!(f, "Date out-of-range: `{}-{}-{}`", year, month, day)
-            }
-            CustomError::OffsetOutOfRange {
-                ref sign,
-                ref hour,
-                ref minute,
-            } => {
-                writeln!(f, "Offset out-of-range: `{}{}:{}`", sign, hour, minute)
-            }
+            CustomError::OutOfRange => writeln!(f, "Value is out of range"),
         }
     }
 }
diff --git a/src/parser/macros.rs b/src/parser/macros.rs
index cb607b5e..6a76dc58 100644
--- a/src/parser/macros.rs
+++ b/src/parser/macros.rs
@@ -12,7 +12,6 @@ macro_rules! parse (
                  <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
                  From<std::num::ParseIntError> +
                  From<std::num::ParseFloatError> +
-                 From<chrono::ParseError> +
                  From<crate::parser::errors::CustomError>
                 ]
             {
@@ -36,7 +35,6 @@ macro_rules! toml_parser (
                  <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
                  From<std::num::ParseIntError> +
                  From<std::num::ParseFloatError> +
-                 From<chrono::ParseError> +
                  From<crate::parser::errors::CustomError>
                 ]
             {
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index a90009b1..66f2bdae 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -4,7 +4,7 @@
 #[macro_use]
 mod macros;
 mod array;
-mod datetime;
+pub(crate) mod datetime;
 mod document;
 mod errors;
 mod inline_table;
@@ -277,7 +277,7 @@ trimmed in raw strings.
             "1979-05-27T00:32:00.999999-07:00",
         ];
         for input in &inputs {
-            parsed_date_time_eq!(input, is_offset_datetime);
+            parsed_date_time_eq!(input, is_datetime);
         }
     }
 
@@ -285,7 +285,7 @@ trimmed in raw strings.
     fn local_date_time() {
         let inputs = ["1979-05-27T07:32:00", "1979-05-27T00:32:00.999999"];
         for input in &inputs {
-            parsed_date_time_eq!(input, is_local_datetime);
+            parsed_date_time_eq!(input, is_datetime);
         }
     }
 
@@ -293,7 +293,7 @@ trimmed in raw strings.
     fn local_date() {
         let inputs = ["1979-05-27", "2017-07-20"];
         for input in &inputs {
-            parsed_date_time_eq!(input, is_local_date);
+            parsed_date_time_eq!(input, is_datetime);
         }
     }
 
@@ -301,7 +301,7 @@ trimmed in raw strings.
     fn local_time() {
         let inputs = ["07:32:00", "00:32:00.999999"];
         for input in &inputs {
-            parsed_date_time_eq!(input, is_local_time);
+            parsed_date_time_eq!(input, is_datetime);
         }
     }
 
diff --git a/src/parser/table.rs b/src/parser/table.rs
index 68a7a52f..f799cc5e 100644
--- a/src/parser/table.rs
+++ b/src/parser/table.rs
@@ -60,7 +60,6 @@ parser! {
          <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
          From<std::num::ParseIntError> +
          From<std::num::ParseFloatError> +
-         From<chrono::ParseError> +
          From<crate::parser::errors::CustomError>
     ]    {
         array_table(parser)
diff --git a/src/parser/value.rs b/src/parser/value.rs
index 6f3a676e..67085cc2 100644
--- a/src/parser/value.rs
+++ b/src/parser/value.rs
@@ -26,7 +26,8 @@ parse!(value() -> v::Value, {
             .map(v::Value::Array),
         inline_table()
             .map(v::Value::InlineTable),
-        date_time(),
+        date_time()
+            .map(v::Value::from),
         float()
             .map(v::Value::from),
         integer()
@@ -48,16 +49,7 @@ fn apply_raw(mut val: Value, raw: &str) -> Value {
         Value::Boolean(ref mut f) => {
             f.repr = Repr::new_unchecked(raw);
         }
-        Value::OffsetDateTime(ref mut f) => {
-            f.repr = Repr::new_unchecked(raw);
-        }
-        Value::LocalDateTime(ref mut f) => {
-            f.repr = Repr::new_unchecked(raw);
-        }
-        Value::LocalDate(ref mut f) => {
-            f.repr = Repr::new_unchecked(raw);
-        }
-        Value::LocalTime(ref mut f) => {
+        Value::Datetime(ref mut f) => {
             f.repr = Repr::new_unchecked(raw);
         }
         Value::Array(_) | Value::InlineTable(_) => {}
diff --git a/src/value.rs b/src/value.rs
index c7db0c57..5bd40104 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -22,13 +22,7 @@ pub enum Value {
     /// A boolean value.
     Boolean(Formatted<bool>),
     /// An RFC 3339 formatted date-time with offset.
-    OffsetDateTime(Formatted<OffsetDateTime>),
-    /// An RFC 3339 formatted date-time without offset.
-    LocalDateTime(Formatted<LocalDateTime>),
-    /// Date portion of an RFC 3339 formatted date-time.
-    LocalDate(Formatted<LocalDate>),
-    /// Time portion of an RFC 3339 formatted date-time.
-    LocalTime(Formatted<LocalTime>),
+    Datetime(Formatted<Datetime>),
     /// An inline array of values.
     Array(Array),
     /// An inline table of key/value pairs.
@@ -44,10 +38,7 @@ impl Value {
             Value::Integer(..) => "integer",
             Value::Float(..) => "float",
             Value::Boolean(..) => "boolean",
-            Value::OffsetDateTime(..) => "offset datetime",
-            Value::LocalDateTime(..) => "local datetime",
-            Value::LocalDate(..) => "local date",
-            Value::LocalTime(..) => "local time",
+            Value::Datetime(..) => "datetime",
             Value::Array(..) => "array",
             Value::InlineTable(..) => "inline table",
         }
@@ -106,55 +97,16 @@ impl Value {
     }
 
     /// Casts `self` to date-time.
-    pub fn as_offset_datetime(&self) -> Option<&OffsetDateTime> {
+    pub fn as_datetime(&self) -> Option<&Datetime> {
         match *self {
-            Value::OffsetDateTime(ref value) => Some(value.value()),
+            Value::Datetime(ref value) => Some(value.value()),
             _ => None,
         }
     }
 
     /// Returns true iff `self` is a date-time.
-    pub fn is_offset_datetime(&self) -> bool {
-        self.as_offset_datetime().is_some()
-    }
-
-    /// Casts `self` to date-time.
-    pub fn as_local_datetime(&self) -> Option<&LocalDateTime> {
-        match *self {
-            Value::LocalDateTime(ref value) => Some(value.value()),
-            _ => None,
-        }
-    }
-
-    /// Returns true iff `self` is a date-time.
-    pub fn is_local_datetime(&self) -> bool {
-        self.as_local_datetime().is_some()
-    }
-
-    /// Casts `self` to date-time.
-    pub fn as_local_date(&self) -> Option<&LocalDate> {
-        match *self {
-            Value::LocalDate(ref value) => Some(value.value()),
-            _ => None,
-        }
-    }
-
-    /// Returns true iff `self` is a date-time.
-    pub fn is_local_date(&self) -> bool {
-        self.as_local_date().is_some()
-    }
-
-    /// Casts `self` to date-time.
-    pub fn as_local_time(&self) -> Option<&LocalTime> {
-        match *self {
-            Value::LocalTime(ref value) => Some(value.value()),
-            _ => None,
-        }
-    }
-
-    /// Returns true iff `self` is a date-time.
-    pub fn is_local_time(&self) -> bool {
-        self.as_local_time().is_some()
+    pub fn is_datetime(&self) -> bool {
+        self.as_datetime().is_some()
     }
 
     /// Casts `self` to array.
@@ -213,10 +165,7 @@ impl Value {
             Value::Integer(f) => f.decor_mut(),
             Value::Float(f) => f.decor_mut(),
             Value::Boolean(f) => f.decor_mut(),
-            Value::OffsetDateTime(f) => f.decor_mut(),
-            Value::LocalDateTime(f) => f.decor_mut(),
-            Value::LocalDate(f) => f.decor_mut(),
-            Value::LocalTime(f) => f.decor_mut(),
+            Value::Datetime(f) => f.decor_mut(),
             Value::Array(a) => a.decor_mut(),
             Value::InlineTable(t) => t.decor_mut(),
         }
@@ -234,10 +183,7 @@ impl Value {
             Value::Integer(ref f) => f.decor(),
             Value::Float(ref f) => f.decor(),
             Value::Boolean(ref f) => f.decor(),
-            Value::OffsetDateTime(ref f) => f.decor(),
-            Value::LocalDateTime(ref f) => f.decor(),
-            Value::LocalDate(ref f) => f.decor(),
-            Value::LocalTime(ref f) => f.decor(),
+            Value::Datetime(ref f) => f.decor(),
             Value::Array(ref a) => a.decor(),
             Value::InlineTable(ref t) => t.decor(),
         }
@@ -506,31 +452,24 @@ impl From<bool> for Value {
     }
 }
 
-impl From<OffsetDateTime> for Value {
-    fn from(d: OffsetDateTime) -> Self {
-        let s = d.to_string();
-        Value::OffsetDateTime(Formatted::new(d, Repr::new_unchecked(s)))
-    }
-}
-
-impl From<LocalDateTime> for Value {
-    fn from(d: LocalDateTime) -> Self {
+impl From<Datetime> for Value {
+    fn from(d: Datetime) -> Self {
         let s = d.to_string();
-        Value::LocalDateTime(Formatted::new(d, Repr::new_unchecked(s)))
+        Value::Datetime(Formatted::new(d, Repr::new_unchecked(s)))
     }
 }
 
-impl From<LocalDate> for Value {
-    fn from(d: LocalDate) -> Self {
-        let s = d.to_string();
-        Value::LocalDate(Formatted::new(d, Repr::new_unchecked(s)))
+impl From<Date> for Value {
+    fn from(d: Date) -> Self {
+        let d: Datetime = d.into();
+        d.into()
     }
 }
 
-impl From<LocalTime> for Value {
-    fn from(d: LocalTime) -> Self {
-        let s = d.to_string();
-        Value::LocalTime(Formatted::new(d, Repr::new_unchecked(s)))
+impl From<Time> for Value {
+    fn from(d: Time) -> Self {
+        let d: Datetime = d.into();
+        d.into()
     }
 }
 


Test patch:
diff --git a/tests/decoder.rs b/tests/decoder.rs
index cb8de965..401a61a0 100644
--- a/tests/decoder.rs
+++ b/tests/decoder.rs
@@ -47,18 +47,18 @@ fn value_to_decoded(
         toml_edit::Value::Float(v) => Ok(toml_test_harness::Decoded::Value(
             toml_test_harness::DecodedValue::from(*v.value()),
         )),
-        toml_edit::Value::OffsetDateTime(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::Datetime(v.value().to_string()),
-        )),
-        toml_edit::Value::LocalDateTime(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::DatetimeLocal(v.value().to_string()),
-        )),
-        toml_edit::Value::LocalDate(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::DateLocal(v.value().to_string()),
-        )),
-        toml_edit::Value::LocalTime(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::TimeLocal(v.value().to_string()),
-        )),
+        toml_edit::Value::Datetime(v) => {
+            let v = v.value();
+            let value = v.to_string();
+            let value = match (v.date.is_some(), v.time.is_some(), v.offset.is_some()) {
+                (true, true, true) => toml_test_harness::DecodedValue::Datetime(value),
+                (true, true, false) => toml_test_harness::DecodedValue::DatetimeLocal(value),
+                (true, false, false) => toml_test_harness::DecodedValue::DateLocal(value),
+                (false, true, false) => toml_test_harness::DecodedValue::TimeLocal(value),
+                _ => unreachable!("Unsupported case"),
+            };
+            Ok(toml_test_harness::Decoded::Value(value))
+        }
         toml_edit::Value::Boolean(v) => Ok(toml_test_harness::Decoded::Value(
             toml_test_harness::DecodedValue::from(*v.value()),
         )),
diff --git a/tests/encoder_compliance.rs b/tests/encoder_compliance.rs
index e875a7a1..a8bce968 100644
--- a/tests/encoder_compliance.rs
+++ b/tests/encoder_compliance.rs
@@ -87,19 +87,19 @@ fn from_decoded_value(
             .map_err(toml_test_harness::Error::new)?
             .into(),
         toml_test_harness::DecodedValue::Datetime(value) => value
-            .parse::<toml_edit::OffsetDateTime>()
+            .parse::<toml_edit::Datetime>()
             .map_err(toml_test_harness::Error::new)?
             .into(),
         toml_test_harness::DecodedValue::DatetimeLocal(value) => value
-            .parse::<toml_edit::LocalDateTime>()
+            .parse::<toml_edit::Datetime>()
             .map_err(toml_test_harness::Error::new)?
             .into(),
         toml_test_harness::DecodedValue::DateLocal(value) => value
-            .parse::<toml_edit::LocalDate>()
+            .parse::<toml_edit::Datetime>()
             .map_err(toml_test_harness::Error::new)?
             .into(),
         toml_test_harness::DecodedValue::TimeLocal(value) => value
-            .parse::<toml_edit::LocalTime>()
+            .parse::<toml_edit::Datetime>()
             .map_err(toml_test_harness::Error::new)?
             .into(),
     };
diff --git a/tests/test_parse.rs b/tests/test_parse.rs
index ba0bd106..7d03b0e3 100644
--- a/tests/test_parse.rs
+++ b/tests/test_parse.rs
@@ -54,12 +54,12 @@ fn test_key_from_str() {
 
 #[test]
 fn test_value_from_str() {
-    assert!(parse_value!("1979-05-27T00:32:00.999999-07:00").is_offset_datetime());
-    assert!(parse_value!("1979-05-27T00:32:00.999999Z").is_offset_datetime());
-    assert!(parse_value!("1979-05-27T00:32:00.999999").is_local_datetime());
-    assert!(parse_value!("1979-05-27T00:32:00").is_local_datetime());
-    assert!(parse_value!("1979-05-27").is_local_date());
-    assert!(parse_value!("00:32:00").is_local_time());
+    assert!(parse_value!("1979-05-27T00:32:00.999999-07:00").is_datetime());
+    assert!(parse_value!("1979-05-27T00:32:00.999999Z").is_datetime());
+    assert!(parse_value!("1979-05-27T00:32:00.999999").is_datetime());
+    assert!(parse_value!("1979-05-27T00:32:00").is_datetime());
+    assert!(parse_value!("1979-05-27").is_datetime());
+    assert!(parse_value!("00:32:00").is_datetime());
     assert!(parse_value!("-239").is_integer());
     assert!(parse_value!("1e200").is_float());
     assert!(parse_value!("9_224_617.445_991_228_313").is_float());
diff --git a/tests/test_valid.rs b/tests/test_valid.rs
index 67f1edc7..f07b5fd0 100644
--- a/tests/test_valid.rs
+++ b/tests/test_valid.rs
@@ -17,16 +17,7 @@ fn pair_to_json((key, value): (&str, Item)) -> (String, Json) {
             Value::Integer(ref i) => typed_json("integer", Json::String(format!("{}", i.value()))),
             Value::Float(ref f) => typed_json("float", Json::String(format!("{}", f.value()))),
             Value::Boolean(ref b) => typed_json("bool", Json::String(b.repr().as_raw().into())),
-            Value::OffsetDateTime(ref d) => {
-                typed_json("datetime", Json::String(d.repr().as_raw().into()))
-            }
-            Value::LocalDateTime(ref d) => {
-                typed_json("datetime", Json::String(d.repr().as_raw().into()))
-            }
-            Value::LocalDate(ref d) => {
-                typed_json("datetime", Json::String(d.repr().as_raw().into()))
-            }
-            Value::LocalTime(ref d) => {
+            Value::Datetime(ref d) => {
                 typed_json("datetime", Json::String(d.repr().as_raw().into()))
             }
             Value::Array(ref a) => {