Improves TimezoneOffset API

pull/345/head
Tpt 2 years ago committed by Thomas Tanon
parent fe2b7c2e76
commit 78c4e750ae
  1. 100
      lib/oxsdatatypes/src/date_time.rs
  2. 2
      lib/oxsdatatypes/src/lib.rs
  3. 4
      lib/oxsdatatypes/src/parser.rs

@ -1142,21 +1142,26 @@ impl fmt::Display for GDay {
} }
} }
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] /// A timezone offset with respect to UTC.
///
/// It is encoded as a number of minutes between -PT14H and PT14H.
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash)]
pub struct TimezoneOffset { pub struct TimezoneOffset {
offset: i16, // in minute with respect to UTC offset: i16, // in minute with respect to UTC
} }
impl TimezoneOffset { impl TimezoneOffset {
#[inline]
pub const fn utc() -> Self {
Self { offset: 0 }
}
/// From offset in minute with respect to UTC /// From offset in minute with respect to UTC
#[inline] #[inline]
pub(super) const fn new(offset: i16) -> Self { pub fn new(offset_in_minutes: i16) -> Result<Self, DateTimeError> {
Self { offset } let value = Self {
offset: offset_in_minutes,
};
if Self::MIN <= value && value <= Self::MAX {
Ok(value)
} else {
Err(DATE_TIME_OVERFLOW)
}
} }
#[inline] #[inline]
@ -1170,12 +1175,35 @@ impl TimezoneOffset {
pub fn to_be_bytes(self) -> [u8; 2] { pub fn to_be_bytes(self) -> [u8; 2] {
self.offset.to_be_bytes() self.offset.to_be_bytes()
} }
pub const MIN: Self = Self { offset: -14 * 60 };
pub const UTC: Self = Self { offset: 0 };
pub const MAX: Self = Self { offset: 14 * 60 };
} }
impl From<i16> for TimezoneOffset { impl TryFrom<DayTimeDuration> for TimezoneOffset {
type Error = DateTimeError;
#[inline]
fn try_from(value: DayTimeDuration) -> Result<Self, DateTimeError> {
let result = Self::new((value.minutes() + value.hours() * 60) as i16)?;
if DayTimeDuration::from(result) == value {
Ok(result)
} else {
// The value is not an integral number of minutes or overflow problems
Err(DATE_TIME_OVERFLOW)
}
}
}
impl TryFrom<Duration> for TimezoneOffset {
type Error = DateTimeError;
#[inline] #[inline]
fn from(offset: i16) -> Self { fn try_from(value: Duration) -> Result<Self, DateTimeError> {
Self { offset } DayTimeDuration::try_from(value)
.map_err(|_| DATE_TIME_OVERFLOW)?
.try_into()
} }
} }
@ -1287,9 +1315,7 @@ impl Timestamp {
Ok(Self { Ok(Self {
timezone_offset: props.timezone_offset, timezone_offset: props.timezone_offset,
value: time_on_timeline(props).ok_or(DateTimeError { value: time_on_timeline(props).ok_or(DATE_TIME_OVERFLOW)?,
kind: DateTimeErrorKind::Overflow,
})?,
}) })
} }
@ -1305,12 +1331,10 @@ impl Timestamp {
hour: Some(0), hour: Some(0),
minute: Some(0), minute: Some(0),
second: Some(Decimal::default()), second: Some(Decimal::default()),
timezone_offset: Some(TimezoneOffset::utc()), timezone_offset: Some(TimezoneOffset::UTC),
}, },
) )
.ok_or(DateTimeError { .ok_or(DATE_TIME_OVERFLOW)?,
kind: DateTimeErrorKind::Overflow,
})?,
) )
} }
@ -1332,11 +1356,7 @@ impl Timestamp {
#[inline] #[inline]
fn year_month_day(&self) -> (i64, u8, u8) { fn year_month_day(&self) -> (i64, u8, u8) {
let mut days = (self.value.as_i128() let mut days = (self.value.as_i128()
+ i128::from( + i128::from(self.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset) * 60)
self.timezone_offset
.unwrap_or_else(TimezoneOffset::utc)
.offset,
) * 60)
.div_euclid(86400) .div_euclid(86400)
+ 366; + 366;
@ -1406,11 +1426,7 @@ impl Timestamp {
#[inline] #[inline]
fn hour(&self) -> u8 { fn hour(&self) -> u8 {
(((self.value.as_i128() (((self.value.as_i128()
+ i128::from( + i128::from(self.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset) * 60)
self.timezone_offset
.unwrap_or_else(TimezoneOffset::utc)
.offset,
) * 60)
.rem_euclid(86400)) .rem_euclid(86400))
/ 3600) as u8 / 3600) as u8
} }
@ -1419,11 +1435,7 @@ impl Timestamp {
#[inline] #[inline]
fn minute(&self) -> u8 { fn minute(&self) -> u8 {
(((self.value.as_i128() (((self.value.as_i128()
+ i128::from( + i128::from(self.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset) * 60)
self.timezone_offset
.unwrap_or_else(TimezoneOffset::utc)
.offset,
) * 60)
.rem_euclid(3600)) .rem_euclid(3600))
/ 60) as u8 / 60) as u8
} }
@ -1486,11 +1498,8 @@ impl Timestamp {
fn since_unix_epoch() -> Result<Duration, DateTimeError> { fn since_unix_epoch() -> Result<Duration, DateTimeError> {
Ok(Duration::new( Ok(Duration::new(
0, 0,
Decimal::try_from(crate::Double::from(js_sys::Date::now() / 1000.)).map_err(|_| { Decimal::try_from(crate::Double::from(js_sys::Date::now() / 1000.))
DateTimeError { .map_err(|_| DATE_TIME_OVERFLOW)?,
kind: DateTimeErrorKind::Overflow,
}
})?,
)) ))
} }
@ -1501,9 +1510,7 @@ fn since_unix_epoch() -> Result<Duration, DateTimeError> {
SystemTime::now() SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)? .duration_since(SystemTime::UNIX_EPOCH)?
.try_into() .try_into()
.map_err(|_| DateTimeError { .map_err(|_| DATE_TIME_OVERFLOW)
kind: DateTimeErrorKind::Overflow,
})
} }
/// The [normalizeMonth](https://www.w3.org/TR/xmlschema11-2/#f-dt-normMo) function /// The [normalizeMonth](https://www.w3.org/TR/xmlschema11-2/#f-dt-normMo) function
@ -1627,12 +1634,7 @@ fn time_on_timeline(props: &DateTimeSevenPropertyModel) -> Option<Decimal> {
.map_or_else(|| days_in_month(Some(yr + 1), mo) - 1, |d| d - 1); .map_or_else(|| days_in_month(Some(yr + 1), mo) - 1, |d| d - 1);
let hr = props.hour.unwrap_or(0); let hr = props.hour.unwrap_or(0);
let mi = i128::from(props.minute.unwrap_or(0)) let mi = i128::from(props.minute.unwrap_or(0))
- i128::from( - i128::from(props.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset);
props
.timezone_offset
.unwrap_or_else(TimezoneOffset::utc)
.offset,
);
let se = props.second.unwrap_or_default(); let se = props.second.unwrap_or_default();
Decimal::try_from( Decimal::try_from(
@ -1695,6 +1697,10 @@ impl From<SystemTimeError> for DateTimeError {
} }
} }
const DATE_TIME_OVERFLOW: DateTimeError = DateTimeError {
kind: DateTimeErrorKind::Overflow,
};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

@ -15,7 +15,7 @@ mod parser;
pub use self::boolean::Boolean; pub use self::boolean::Boolean;
pub use self::date_time::{ pub use self::date_time::{
Date, DateTime, DateTimeError, GDay, GMonth, GMonthDay, GYear, GYearMonth, Time, Date, DateTime, DateTimeError, GDay, GMonth, GMonthDay, GYear, GYearMonth, Time, TimezoneOffset,
}; };
pub use self::decimal::{Decimal, DecimalOverflowError, DecimalParseError}; pub use self::decimal::{Decimal, DecimalOverflowError, DecimalParseError};
pub use self::double::Double; pub use self::double::Double;

@ -490,8 +490,8 @@ fn end_of_day_frag(input: &str) -> XsdResult<'_, (u8, u8, Decimal)> {
// [63] timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00') // [63] timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00')
fn timezone_frag(input: &str) -> XsdResult<'_, TimezoneOffset> { fn timezone_frag(input: &str) -> XsdResult<'_, TimezoneOffset> {
alt(( alt((
map(char('Z'), |_| TimezoneOffset::utc()), map(char('Z'), |_| TimezoneOffset::UTC),
map( map_res(
tuple(( tuple((
alt((map(char('+'), |_| 1), map(char('-'), |_| -1))), alt((map(char('+'), |_| 1), map(char('-'), |_| -1))),
alt(( alt((

Loading…
Cancel
Save