diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 1be5637c..ef657fb0 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -26,6 +26,7 @@ quick-xml = "0.13" ordered-float = "1" num-traits = "0.2" rust_decimal = "0.10" +chrono = "0.4" [build-dependencies] peg = "0.5" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 1a0e9f31..bcb2229b 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -36,6 +36,7 @@ extern crate byteorder; extern crate error_chain; #[macro_use] extern crate lazy_static; +extern crate chrono; extern crate num_traits; extern crate ordered_float; extern crate quick_xml; diff --git a/lib/src/model/literal.rs b/lib/src/model/literal.rs index 206502de..9b7afee2 100644 --- a/lib/src/model/literal.rs +++ b/lib/src/model/literal.rs @@ -1,3 +1,6 @@ +use chrono::DateTime; +use chrono::FixedOffset; +use chrono::NaiveDateTime; use model::named_node::NamedNode; use model::vocab::rdf; use model::vocab::xsd; @@ -47,6 +50,8 @@ enum LiteralContent { Double(OrderedFloat), Integer(i128), Decimal(Decimal), + DateTime(DateTime), + NaiveDateTime(NaiveDateTime), TypedLiteral { value: String, datatype: NamedNode }, } @@ -88,6 +93,19 @@ impl Literal { Ok(value) => LiteralContent::Decimal(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } + } else if datatype == *xsd::DATE_TIME { + match DateTime::parse_from_rfc3339(&value) { + Ok(value) => LiteralContent::DateTime(value), + Err(_) => match NaiveDateTime::parse_from_str(&value, "%Y-%m-%dT%H:%M:%S") { + Ok(value) => LiteralContent::NaiveDateTime(value), + Err(_) => LiteralContent::TypedLiteral { value, datatype }, + }, + } + } else if datatype == *xsd::DATE_TIME_STAMP { + match DateTime::parse_from_rfc3339(&value) { + Ok(value) => LiteralContent::DateTime(value), + Err(_) => LiteralContent::TypedLiteral { value, datatype }, + } } else { LiteralContent::TypedLiteral { value, datatype } }) @@ -116,6 +134,8 @@ impl Literal { LiteralContent::Double(value) => Cow::Owned(value.to_string()), LiteralContent::Integer(value) => Cow::Owned(value.to_string()), LiteralContent::Decimal(value) => Cow::Owned(value.to_string()), + LiteralContent::DateTime(value) => Cow::Owned(value.to_string()), + LiteralContent::NaiveDateTime(value) => Cow::Owned(value.to_string()), } } @@ -141,6 +161,7 @@ impl Literal { LiteralContent::Double(_) => &xsd::DOUBLE, LiteralContent::Integer(_) => &xsd::INTEGER, LiteralContent::Decimal(_) => &xsd::DECIMAL, + LiteralContent::DateTime(_) | LiteralContent::NaiveDateTime(_) => &xsd::DATE_TIME, LiteralContent::TypedLiteral { ref datatype, .. } => datatype, } } @@ -204,6 +225,22 @@ impl Literal { } } + /// Checks if the literal has the datatype [xsd:dateTime](http://www.w3.org/2001/XMLSchema#dateTime) or one of its sub datatype and is valid + pub fn is_date_time(&self) -> bool { + match self.0 { + LiteralContent::DateTime(_) | LiteralContent::NaiveDateTime(_) => true, + _ => false, + } + } + + /// Checks if the literal has the datatype [xsd:dateTimeStamp](http://www.w3.org/2001/XMLSchema#dateTimeStamp) or [xsd:dateTime](http://www.w3.org/2001/XMLSchema#dateTime) with a fixed timezone and is valid + pub fn is_date_time_stamp(&self) -> bool { + match self.0 { + LiteralContent::DateTime(_) => true, + _ => false, + } + } + /// Returns the [effective boolean value](https://www.w3.org/TR/sparql11-query/#ebv) of the literal if it exists pub fn to_bool(&self) -> Option { match self.0 { @@ -282,6 +319,24 @@ impl Literal { _ => None, } } + + /// Returns the value of this literal as NaiveDateTime if possible + pub(crate) fn to_date_time(&self) -> Option { + match self.0 { + LiteralContent::DateTime(value) => Some(value.naive_utc()), + LiteralContent::NaiveDateTime(value) => Some(value), + _ => None, + } + } + + /// Returns the value of this literal as DateTime if possible + pub(crate) fn to_date_time_stamp(&self) -> Option> { + if let LiteralContent::DateTime(value) = self.0 { + Some(value) + } else { + None + } + } } impl fmt::Display for Literal { @@ -373,3 +428,15 @@ impl From for Literal { Literal(LiteralContent::Decimal(value)) } } + +impl From> for Literal { + fn from(value: DateTime) -> Self { + Literal(LiteralContent::DateTime(value)) + } +} + +impl From for Literal { + fn from(value: NaiveDateTime) -> Self { + Literal(LiteralContent::NaiveDateTime(value)) + } +} diff --git a/lib/src/store/numeric_encoder.rs b/lib/src/store/numeric_encoder.rs index c8cbd99a..9b56d6b9 100644 --- a/lib/src/store/numeric_encoder.rs +++ b/lib/src/store/numeric_encoder.rs @@ -1,4 +1,7 @@ use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; +use chrono::DateTime; +use chrono::FixedOffset; +use chrono::NaiveDateTime; use model::vocab::rdf; use model::vocab::xsd; use model::*; @@ -62,6 +65,8 @@ const TYPE_FLOAT_LITERAL: u8 = 9; const TYPE_DOUBLE_LITERAL: u8 = 10; const TYPE_INTEGER_LITERAL: u8 = 11; const TYPE_DECIMAL_LITERAL: u8 = 12; +const TYPE_DATE_TIME_LITERAL: u8 = 13; +const TYPE_NAIVE_DATE_TIME_LITERAL: u8 = 14; pub static ENCODED_DEFAULT_GRAPH: EncodedTerm = EncodedTerm::DefaultGraph {}; pub static ENCODED_EMPTY_SIMPLE_LITERAL: EncodedTerm = EncodedTerm::SimpleLiteral { @@ -109,6 +114,8 @@ pub enum EncodedTerm { DoubleLiteral(OrderedFloat), IntegerLiteral(i128), DecimalLiteral(Decimal), + DateTime(DateTime), + NaiveDateTime(NaiveDateTime), } impl EncodedTerm { @@ -136,7 +143,9 @@ impl EncodedTerm { | EncodedTerm::FloatLiteral(_) | EncodedTerm::DoubleLiteral(_) | EncodedTerm::IntegerLiteral(_) - | EncodedTerm::DecimalLiteral(_) => true, + | EncodedTerm::DecimalLiteral(_) + | EncodedTerm::DateTime(_) + | EncodedTerm::NaiveDateTime(_) => true, _ => false, } } @@ -155,6 +164,9 @@ impl EncodedTerm { EncodedTerm::DoubleLiteral(..) => Some(ENCODED_XSD_DOUBLE_NAMED_NODE), EncodedTerm::IntegerLiteral(..) => Some(ENCODED_XSD_INTEGER_NAMED_NODE), EncodedTerm::DecimalLiteral(..) => Some(ENCODED_XSD_DECIMAL_NAMED_NODE), + EncodedTerm::DateTime(..) | EncodedTerm::NaiveDateTime(..) => { + Some(ENCODED_XSD_DATE_TIME_NAMED_NODE) + } _ => None, } } @@ -174,6 +186,8 @@ impl EncodedTerm { EncodedTerm::DoubleLiteral(_) => TYPE_DOUBLE_LITERAL, EncodedTerm::IntegerLiteral(_) => TYPE_INTEGER_LITERAL, EncodedTerm::DecimalLiteral(_) => TYPE_DECIMAL_LITERAL, + EncodedTerm::DateTime(_) => TYPE_DATE_TIME_LITERAL, + EncodedTerm::NaiveDateTime(_) => TYPE_NAIVE_DATE_TIME_LITERAL, } } } @@ -208,6 +222,18 @@ impl From for EncodedTerm { } } +impl From> for EncodedTerm { + fn from(value: DateTime) -> Self { + EncodedTerm::DateTime(value) + } +} + +impl From for EncodedTerm { + fn from(value: NaiveDateTime) -> Self { + EncodedTerm::NaiveDateTime(value) + } +} + #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct EncodedQuad { pub subject: EncodedTerm, @@ -281,6 +307,20 @@ impl TermReader for R { self.read_exact(&mut buffer)?; Ok(EncodedTerm::DecimalLiteral(Decimal::deserialize(buffer))) } + TYPE_DATE_TIME_LITERAL => Ok(EncodedTerm::DateTime(DateTime::from_utc( + NaiveDateTime::from_timestamp_opt( + self.read_i64::()?, + self.read_u32::()?, + ).ok_or("Invalid date time serialization")?, + FixedOffset::east_opt(self.read_i32::()?) + .ok_or("Invalid timezone offset")?, + ))), + TYPE_NAIVE_DATE_TIME_LITERAL => Ok(EncodedTerm::NaiveDateTime( + NaiveDateTime::from_timestamp_opt( + self.read_i64::()?, + self.read_u32::()?, + ).ok_or("Invalid date time serialization")?, + )), _ => Err("the term buffer has an invalid type id".into()), } } @@ -361,6 +401,15 @@ impl TermWriter for R { EncodedTerm::DoubleLiteral(value) => self.write_f64::(*value)?, EncodedTerm::IntegerLiteral(value) => self.write_i128::(value)?, EncodedTerm::DecimalLiteral(value) => self.write_all(&value.serialize())?, + EncodedTerm::DateTime(value) => { + self.write_i64::(value.timestamp())?; + self.write_u32::(value.timestamp_subsec_nanos())?; + self.write_i32::(value.timezone().local_minus_utc())?; + } + EncodedTerm::NaiveDateTime(value) => { + self.write_i64::(value.timestamp())?; + self.write_u32::(value.timestamp_subsec_nanos())?; + } } Ok(()) } @@ -450,6 +499,16 @@ impl Encoder { .to_decimal() .ok_or_else(|| Error::from("decimal literal without decimal value"))? .into() + } else if literal.is_date_time_stamp() { + literal + .to_date_time_stamp() + .ok_or_else(|| Error::from("dateTimeStamp literal without dateTimeStamp value"))? + .into() + } else if literal.is_decimal() { + literal + .to_date_time() + .ok_or_else(|| Error::from("dateTime literal without dateTime value"))? + .into() } else { EncodedTerm::TypedLiteral { value_id: self.encode_str_value(&literal.value())?, @@ -530,6 +589,8 @@ impl Encoder { EncodedTerm::DoubleLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::IntegerLiteral(value) => Ok(Literal::from(value).into()), EncodedTerm::DecimalLiteral(value) => Ok(Literal::from(value).into()), + EncodedTerm::DateTime(value) => Ok(Literal::from(value).into()), + EncodedTerm::NaiveDateTime(value) => Ok(Literal::from(value).into()), } }