@ -1,7 +1,6 @@
use super ::parser ::duration_lexical_rep ;
use super ::parser ::parse_value ;
use super ::decimal ::DecimalOverflowError ;
use super ::parser ::* ;
use super ::* ;
use crate ::model ::xsd ::decimal ::DecimalOverflowError ;
use std ::cmp ::Ordering ;
use std ::convert ::TryFrom ;
use std ::fmt ;
@ -11,20 +10,18 @@ use std::time::Duration as StdDuration;
/// [XML Schema `duration` datatype](https://www.w3.org/TR/xmlschema11-2/#duration) implementation.
///
/// It stores the duration using the two components model suggested by the specification:
/// - a number of months encoded using a `i64`
/// - a number of seconds encoded using a `Decimal`
/// It stores the duration using a pair of a `YearMonthDuration` and a `DayTimeDuration`.
#[ derive(Eq, PartialEq, Debug, Clone, Copy, Hash, Default) ]
pub struct Duration {
months : i64 ,
seconds : Decimal ,
year_month : YearMonthDuration ,
day_time : DayTimeDuration ,
}
impl Duration {
pub fn new ( months : impl Into < i64 > , seconds : impl Into < Decimal > ) -> Self {
Self {
months : months . into ( ) ,
seconds : seconds . into ( ) ,
year_month : YearMonthDuration ::new ( months ) ,
day_time : DayTimeDuration ::new ( seconds ) ,
}
}
@ -34,56 +31,53 @@ impl Duration {
let mut seconds = [ 8 ; 16 ] ;
seconds . copy_from_slice ( & bytes [ 8 .. 24 ] ) ;
Self {
months : i64 ::from_be_bytes ( months ) ,
seconds : Decimal ::from_be_bytes ( seconds ) ,
year_month : YearMonthDuration ::from_be_bytes ( months ) ,
day_time : DayTimeDuration ::from_be_bytes ( seconds ) ,
}
}
/// [fn:years-from-duration](https://www.w3.org/TR/xpath-functions/#func-years-from-duration)
pub fn years ( & self ) -> i64 {
self . months / 12
self . year_month . years ( )
}
/// [fn:months-from-duration](https://www.w3.org/TR/xpath-functions/#func-months-from-duration)
pub fn months ( & self ) -> i64 {
self . months % 12
self . year_month . months ( )
}
/// [fn:days-from-duration](https://www.w3.org/TR/xpath-functions/#func-days-from-duration)
#[ allow(clippy::cast_possible_truncation) ]
pub fn days ( & self ) -> i64 {
( self . seconds . as_i128 ( ) / 86400 ) as i64
self . day_time . days ( )
}
/// [fn:hours-from-duration](https://www.w3.org/TR/xpath-functions/#func-hours-from-duration)
#[ allow(clippy::cast_possible_truncation) ]
pub fn hours ( & self ) -> i64 {
( ( self . seconds . as_i128 ( ) % 86400 ) / 3600 ) as i64
self . day_time . hours ( )
}
/// [fn:minutes-from-duration](https://www.w3.org/TR/xpath-functions/#func-minutes-from-duration)
#[ allow(clippy::cast_possible_truncation) ]
pub fn minutes ( & self ) -> i64 {
( ( self . seconds . as_i128 ( ) % 3600 ) / 60 ) as i64
self . day_time . minutes ( )
}
/// [fn:seconds-from-duration](https://www.w3.org/TR/xpath-functions/#func-seconds-from-duration)
pub fn seconds ( & self ) -> Decimal {
self . seconds . checked_rem ( 60 ) . unwrap ( )
self . day_time . seconds ( )
}
pub ( super ) const fn all_months ( & self ) -> i64 {
self . months
self . year_month . all_ months( )
}
pub ( super ) const fn all_seconds ( & self ) -> Decimal {
self . seconds
self . day_time . all_ seconds( )
}
pub fn to_be_bytes ( & self ) -> [ u8 ; 24 ] {
let mut bytes = [ 0 ; 24 ] ;
bytes [ 0 .. 8 ] . copy_from_slice ( & self . months . to_be_bytes ( ) ) ;
bytes [ 8 .. 24 ] . copy_from_slice ( & self . seconds . to_be_bytes ( ) ) ;
bytes [ 0 .. 8 ] . copy_from_slice ( & self . year_ month. to_be_bytes ( ) ) ;
bytes [ 8 .. 24 ] . copy_from_slice ( & self . day_time . to_be_bytes ( ) ) ;
bytes
}
@ -91,8 +85,8 @@ impl Duration {
pub fn checked_add ( & self , rhs : impl Into < Self > ) -> Option < Self > {
let rhs = rhs . into ( ) ;
Some ( Self {
months : self . months . checked_add ( rhs . months ) ? ,
seconds : self . seconds . checked_add ( rhs . seconds ) ? ,
year_ month : self . year_ month. checked_add ( rhs . year_ month) ? ,
day_time : self . day_time . checked_add ( rhs . day_time ) ? ,
} )
}
@ -100,8 +94,8 @@ impl Duration {
pub fn checked_sub ( & self , rhs : impl Into < Self > ) -> Option < Self > {
let rhs = rhs . into ( ) ;
Some ( Self {
months : self . months . checked_sub ( rhs . months ) ? ,
seconds : self . seconds . checked_sub ( rhs . seconds ) ? ,
year_ month : self . year_ month. checked_sub ( rhs . year_ month) ? ,
day_time : self . day_time . checked_sub ( rhs . day_time ) ? ,
} )
}
}
@ -110,13 +104,7 @@ impl TryFrom<StdDuration> for Duration {
type Error = DecimalOverflowError ;
fn try_from ( value : StdDuration ) -> Result < Self , DecimalOverflowError > {
Ok ( Self {
months : 0 ,
seconds : Decimal ::new (
i128 ::try_from ( value . as_nanos ( ) ) . map_err ( | _ | DecimalOverflowError ) ? ,
9 ,
) ? ,
} )
Ok ( DayTimeDuration ::try_from ( value ) ? . into ( ) )
}
}
@ -131,8 +119,8 @@ impl FromStr for Duration {
impl fmt ::Display for Duration {
#[ allow(clippy::many_single_char_names) ]
fn fmt ( & self , f : & mut fmt ::Formatter < ' _ > ) -> fmt ::Result {
let mut ym = self . months ;
let mut ss = self . seconds ;
let mut ym = self . year_month . months ;
let mut ss = self . day_time . seconds ;
if ym < 0 | | ss < 0. into ( ) {
write! ( f , "-" ) ? ;
@ -222,9 +210,343 @@ impl PartialOrd for Duration {
impl Neg for Duration {
type Output = Self ;
fn neg ( self ) -> Self {
Self {
year_month : self . year_month . neg ( ) ,
day_time : self . day_time . neg ( ) ,
}
}
}
/// [XML Schema `yearMonthDuration` datatype](https://www.w3.org/TR/xmlschema11-2/#yearMonthDuration) implementation.
///
/// It stores the duration as a number of months encoded using a `i64`
#[ derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash, Default) ]
pub struct YearMonthDuration {
months : i64 ,
}
impl YearMonthDuration {
pub fn new ( months : impl Into < i64 > ) -> Self {
Self {
months : months . into ( ) ,
}
}
pub fn from_be_bytes ( bytes : [ u8 ; 8 ] ) -> Self {
Self {
months : i64 ::from_be_bytes ( bytes ) ,
}
}
/// [fn:years-from-duration](https://www.w3.org/TR/xpath-functions/#func-years-from-duration)
pub fn years ( & self ) -> i64 {
self . months / 12
}
/// [fn:months-from-duration](https://www.w3.org/TR/xpath-functions/#func-months-from-duration)
pub fn months ( & self ) -> i64 {
self . months % 12
}
/// [fn:days-from-duration](https://www.w3.org/TR/xpath-functions/#func-days-from-duration)
pub fn days ( & self ) -> i64 {
0
}
/// [fn:hours-from-duration](https://www.w3.org/TR/xpath-functions/#func-hours-from-duration)
pub fn hours ( & self ) -> i64 {
0
}
/// [fn:minutes-from-duration](https://www.w3.org/TR/xpath-functions/#func-minutes-from-duration)
pub fn minutes ( & self ) -> i64 {
0
}
/// [fn:seconds-from-duration](https://www.w3.org/TR/xpath-functions/#func-seconds-from-duration)
pub fn seconds ( & self ) -> Decimal {
Decimal ::default ( )
}
pub ( super ) const fn all_months ( & self ) -> i64 {
self . months
}
pub fn to_be_bytes ( & self ) -> [ u8 ; 8 ] {
self . months . to_be_bytes ( )
}
/// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions/#func-add-yearMonthDurations)
pub fn checked_add ( & self , rhs : impl Into < Self > ) -> Option < Self > {
let rhs = rhs . into ( ) ;
Some ( Self {
months : self . months . checked_add ( rhs . months ) ? ,
} )
}
/// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions/#func-subtract-yearMonthDurations)
pub fn checked_sub ( & self , rhs : impl Into < Self > ) -> Option < Self > {
let rhs = rhs . into ( ) ;
Some ( Self {
months : self . months . checked_sub ( rhs . months ) ? ,
} )
}
}
impl From < YearMonthDuration > for Duration {
fn from ( value : YearMonthDuration ) -> Self {
Self {
year_month : value ,
day_time : DayTimeDuration ::default ( ) ,
}
}
}
impl TryFrom < Duration > for YearMonthDuration {
type Error = DecimalOverflowError ;
fn try_from ( value : Duration ) -> Result < Self , DecimalOverflowError > {
if value . day_time = = DayTimeDuration ::default ( ) {
Ok ( value . year_month )
} else {
Err ( DecimalOverflowError { } )
}
}
}
impl FromStr for YearMonthDuration {
type Err = XsdParseError ;
fn from_str ( input : & str ) -> Result < Self , XsdParseError > {
parse_value ( year_month_duration_lexical_rep , input )
}
}
impl fmt ::Display for YearMonthDuration {
fn fmt ( & self , f : & mut fmt ::Formatter < ' _ > ) -> fmt ::Result {
if self . months = = 0 {
write! ( f , "P0M" )
} else {
Duration ::from ( * self ) . fmt ( f )
}
}
}
impl PartialEq < Duration > for YearMonthDuration {
fn eq ( & self , other : & Duration ) -> bool {
Duration ::from ( * self ) . eq ( other )
}
}
impl PartialEq < YearMonthDuration > for Duration {
fn eq ( & self , other : & YearMonthDuration ) -> bool {
self . eq ( & Duration ::from ( * other ) )
}
}
impl PartialOrd < Duration > for YearMonthDuration {
fn partial_cmp ( & self , other : & Duration ) -> Option < Ordering > {
Duration ::from ( * self ) . partial_cmp ( other )
}
}
impl PartialOrd < YearMonthDuration > for Duration {
fn partial_cmp ( & self , other : & YearMonthDuration ) -> Option < Ordering > {
self . partial_cmp ( & Duration ::from ( * other ) )
}
}
impl Neg for YearMonthDuration {
type Output = Self ;
fn neg ( self ) -> Self {
Self {
months : self . months . neg ( ) ,
}
}
}
/// [XML Schema `dayTimeDuration` datatype](https://www.w3.org/TR/xmlschema11-2/#dayTimeDuration) implementation.
///
/// It stores the duration as a number of seconds encoded using a `Decimal`
#[ derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash, Default) ]
pub struct DayTimeDuration {
seconds : Decimal ,
}
impl DayTimeDuration {
pub fn new ( seconds : impl Into < Decimal > ) -> Self {
Self {
seconds : seconds . into ( ) ,
}
}
pub fn from_be_bytes ( bytes : [ u8 ; 16 ] ) -> Self {
Self {
seconds : Decimal ::from_be_bytes ( bytes ) ,
}
}
/// [fn:years-from-duration](https://www.w3.org/TR/xpath-functions/#func-years-from-duration)
pub fn years ( & self ) -> i64 {
0
}
/// [fn:months-from-duration](https://www.w3.org/TR/xpath-functions/#func-months-from-duration)
pub fn months ( & self ) -> i64 {
0
}
/// [fn:days-from-duration](https://www.w3.org/TR/xpath-functions/#func-days-from-duration)
#[ allow(clippy::cast_possible_truncation) ]
pub fn days ( & self ) -> i64 {
( self . seconds . as_i128 ( ) / 86400 ) as i64
}
/// [fn:hours-from-duration](https://www.w3.org/TR/xpath-functions/#func-hours-from-duration)
#[ allow(clippy::cast_possible_truncation) ]
pub fn hours ( & self ) -> i64 {
( ( self . seconds . as_i128 ( ) % 86400 ) / 3600 ) as i64
}
/// [fn:minutes-from-duration](https://www.w3.org/TR/xpath-functions/#func-minutes-from-duration)
#[ allow(clippy::cast_possible_truncation) ]
pub fn minutes ( & self ) -> i64 {
( ( self . seconds . as_i128 ( ) % 3600 ) / 60 ) as i64
}
/// [fn:seconds-from-duration](https://www.w3.org/TR/xpath-functions/#func-seconds-from-duration)
pub fn seconds ( & self ) -> Decimal {
self . seconds . checked_rem ( 60 ) . unwrap ( )
}
pub ( super ) const fn all_seconds ( & self ) -> Decimal {
self . seconds
}
pub fn to_be_bytes ( & self ) -> [ u8 ; 16 ] {
self . seconds . to_be_bytes ( )
}
/// [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions/#func-add-dayTimeDurations)
pub fn checked_add ( & self , rhs : impl Into < Self > ) -> Option < Self > {
let rhs = rhs . into ( ) ;
Some ( Self {
seconds : self . seconds . checked_add ( rhs . seconds ) ? ,
} )
}
/// [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions/#func-subtract-dayTimeDurations)
pub fn checked_sub ( & self , rhs : impl Into < Self > ) -> Option < Self > {
let rhs = rhs . into ( ) ;
Some ( Self {
seconds : self . seconds . checked_sub ( rhs . seconds ) ? ,
} )
}
}
impl From < DayTimeDuration > for Duration {
fn from ( value : DayTimeDuration ) -> Self {
Self {
year_month : YearMonthDuration ::default ( ) ,
day_time : value ,
}
}
}
impl TryFrom < Duration > for DayTimeDuration {
type Error = DecimalOverflowError ;
fn try_from ( value : Duration ) -> Result < Self , DecimalOverflowError > {
if value . year_month = = YearMonthDuration ::default ( ) {
Ok ( value . day_time )
} else {
Err ( DecimalOverflowError { } )
}
}
}
impl TryFrom < StdDuration > for DayTimeDuration {
type Error = DecimalOverflowError ;
fn try_from ( value : StdDuration ) -> Result < Self , DecimalOverflowError > {
Ok ( Self {
seconds : Decimal ::new (
i128 ::try_from ( value . as_nanos ( ) ) . map_err ( | _ | DecimalOverflowError ) ? ,
9 ,
) ? ,
} )
}
}
impl FromStr for DayTimeDuration {
type Err = XsdParseError ;
fn from_str ( input : & str ) -> Result < Self , XsdParseError > {
parse_value ( day_time_duration_lexical_rep , input )
}
}
impl fmt ::Display for DayTimeDuration {
fn fmt ( & self , f : & mut fmt ::Formatter < ' _ > ) -> fmt ::Result {
Duration ::from ( * self ) . fmt ( f )
}
}
impl PartialEq < Duration > for DayTimeDuration {
fn eq ( & self , other : & Duration ) -> bool {
Duration ::from ( * self ) . eq ( other )
}
}
impl PartialEq < DayTimeDuration > for Duration {
fn eq ( & self , other : & DayTimeDuration ) -> bool {
self . eq ( & Duration ::from ( * other ) )
}
}
impl PartialEq < YearMonthDuration > for DayTimeDuration {
fn eq ( & self , other : & YearMonthDuration ) -> bool {
Duration ::from ( * self ) . eq ( & Duration ::from ( * other ) )
}
}
impl PartialEq < DayTimeDuration > for YearMonthDuration {
fn eq ( & self , other : & DayTimeDuration ) -> bool {
Duration ::from ( * self ) . eq ( & Duration ::from ( * other ) )
}
}
impl PartialOrd < Duration > for DayTimeDuration {
fn partial_cmp ( & self , other : & Duration ) -> Option < Ordering > {
Duration ::from ( * self ) . partial_cmp ( other )
}
}
impl PartialOrd < DayTimeDuration > for Duration {
fn partial_cmp ( & self , other : & DayTimeDuration ) -> Option < Ordering > {
self . partial_cmp ( & Duration ::from ( * other ) )
}
}
impl PartialOrd < YearMonthDuration > for DayTimeDuration {
fn partial_cmp ( & self , other : & YearMonthDuration ) -> Option < Ordering > {
Duration ::from ( * self ) . partial_cmp ( & Duration ::from ( * other ) )
}
}
impl PartialOrd < DayTimeDuration > for YearMonthDuration {
fn partial_cmp ( & self , other : & DayTimeDuration ) -> Option < Ordering > {
Duration ::from ( * self ) . partial_cmp ( & Duration ::from ( * other ) )
}
}
impl Neg for DayTimeDuration {
type Output = Self ;
fn neg ( self ) -> Self {
Self {
seconds : self . seconds . neg ( ) ,
}
}
@ -242,17 +564,62 @@ mod tests {
) ;
let max = Duration ::new ( i64 ::max_value ( ) , Decimal ::max_value ( ) ) ;
assert_eq! (
YearMonthDuration ::from_str ( "P1Y" ) . unwrap ( ) . to_string ( ) ,
"P1Y"
) ;
assert_eq! ( Duration ::from_str ( "P1Y" ) . unwrap ( ) . to_string ( ) , "P1Y" ) ;
assert_eq! (
YearMonthDuration ::from_str ( "P1M" ) . unwrap ( ) . to_string ( ) ,
"P1M"
) ;
assert_eq! ( Duration ::from_str ( "P1M" ) . unwrap ( ) . to_string ( ) , "P1M" ) ;
assert_eq! ( DayTimeDuration ::from_str ( "P1D" ) . unwrap ( ) . to_string ( ) , "P1D" ) ;
assert_eq! ( Duration ::from_str ( "P1D" ) . unwrap ( ) . to_string ( ) , "P1D" ) ;
assert_eq! (
DayTimeDuration ::from_str ( "PT1H" ) . unwrap ( ) . to_string ( ) ,
"PT1H"
) ;
assert_eq! ( Duration ::from_str ( "PT1H" ) . unwrap ( ) . to_string ( ) , "PT1H" ) ;
assert_eq! (
DayTimeDuration ::from_str ( "PT1M" ) . unwrap ( ) . to_string ( ) ,
"PT1M"
) ;
assert_eq! ( Duration ::from_str ( "PT1M" ) . unwrap ( ) . to_string ( ) , "PT1M" ) ;
assert_eq! (
DayTimeDuration ::from_str ( "PT1.1S" ) . unwrap ( ) . to_string ( ) ,
"PT1.1S"
) ;
assert_eq! ( Duration ::from_str ( "PT1.1S" ) . unwrap ( ) . to_string ( ) , "PT1.1S" ) ;
assert_eq! (
YearMonthDuration ::from_str ( "-P1Y" ) . unwrap ( ) . to_string ( ) ,
"-P1Y"
) ;
assert_eq! ( Duration ::from_str ( "-P1Y" ) . unwrap ( ) . to_string ( ) , "-P1Y" ) ;
assert_eq! (
YearMonthDuration ::from_str ( "-P1M" ) . unwrap ( ) . to_string ( ) ,
"-P1M"
) ;
assert_eq! ( Duration ::from_str ( "-P1M" ) . unwrap ( ) . to_string ( ) , "-P1M" ) ;
assert_eq! (
DayTimeDuration ::from_str ( "-P1D" ) . unwrap ( ) . to_string ( ) ,
"-P1D"
) ;
assert_eq! ( Duration ::from_str ( "-P1D" ) . unwrap ( ) . to_string ( ) , "-P1D" ) ;
assert_eq! (
DayTimeDuration ::from_str ( "-PT1H" ) . unwrap ( ) . to_string ( ) ,
"-PT1H"
) ;
assert_eq! ( Duration ::from_str ( "-PT1H" ) . unwrap ( ) . to_string ( ) , "-PT1H" ) ;
assert_eq! (
DayTimeDuration ::from_str ( "-PT1M" ) . unwrap ( ) . to_string ( ) ,
"-PT1M"
) ;
assert_eq! ( Duration ::from_str ( "-PT1M" ) . unwrap ( ) . to_string ( ) , "-PT1M" ) ;
assert_eq! (
DayTimeDuration ::from_str ( "-PT1.1S" ) . unwrap ( ) . to_string ( ) ,
"-PT1.1S"
) ;
assert_eq! (
Duration ::from_str ( "-PT1.1S" ) . unwrap ( ) . to_string ( ) ,
"-PT1.1S"
@ -263,10 +630,34 @@ mod tests {
#[ test ]
fn equals ( ) {
assert_eq! (
YearMonthDuration ::from_str ( "P1Y" ) . unwrap ( ) ,
YearMonthDuration ::from_str ( "P12M" ) . unwrap ( )
) ;
assert_eq! (
YearMonthDuration ::from_str ( "P1Y" ) . unwrap ( ) ,
Duration ::from_str ( "P12M" ) . unwrap ( )
) ;
assert_eq! (
Duration ::from_str ( "P1Y" ) . unwrap ( ) ,
YearMonthDuration ::from_str ( "P12M" ) . unwrap ( )
) ;
assert_eq! (
Duration ::from_str ( "P1Y" ) . unwrap ( ) ,
Duration ::from_str ( "P12M" ) . unwrap ( )
) ;
assert_eq! (
DayTimeDuration ::from_str ( "PT24H" ) . unwrap ( ) ,
DayTimeDuration ::from_str ( "P1D" ) . unwrap ( )
) ;
assert_eq! (
DayTimeDuration ::from_str ( "PT24H" ) . unwrap ( ) ,
Duration ::from_str ( "P1D" ) . unwrap ( )
) ;
assert_eq! (
Duration ::from_str ( "PT24H" ) . unwrap ( ) ,
DayTimeDuration ::from_str ( "P1D" ) . unwrap ( )
) ;
assert_eq! (
Duration ::from_str ( "PT24H" ) . unwrap ( ) ,
Duration ::from_str ( "P1D" ) . unwrap ( )