Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

From str nom7 #2

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ keywords = ["iso8601", "duration", "parser"]
travis-ci = { repository = "PoiScript/iso8601-duration" }

[dependencies]
nom = "5.0.1"
nom = "^7"
81 changes: 72 additions & 9 deletions src/duration.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::fmt;
use std::str::FromStr;
use std::time::Duration as StdDuration;

Expand All @@ -6,12 +7,14 @@ use nom::{
bytes::complete::tag,
character::complete::digit1,
combinator::{all_consuming, map_res, opt},
error::{ErrorKind, ParseError},
error::{Error, ErrorKind, ParseError},
number::complete::float,
sequence::{preceded, separated_pair, terminated, tuple},
Err, IResult,
};

const YEAR_IN_S: f64 = 31556952.0; // gregorian - includes leap-seconds

#[derive(Debug, PartialEq)]
pub struct Duration {
pub year: f32,
Expand All @@ -34,18 +37,24 @@ impl Duration {
}
}

// In this scheme we try to balance the flexibility of fractional units
// with the need to avoid rounding errors caused by floating point drift.
// The smaller we keep the floats, the better.
pub fn to_std(&self) -> StdDuration {
StdDuration::from_secs_f32(
self.year * 60. * 60. * 24. * 30. * 12.
+ self.month * 60. * 60. * 24. * 30.
+ self.day * 60. * 60. * 24.
+ self.hour * 60. * 60.
+ self.minute * 60.
+ self.second,
let millis = (self.second.fract() * 1000.0).round() as u64;
StdDuration::from_millis(
((self.year as f64 * YEAR_IN_S).round() as u64
+ (self.month * 30.42 * 60.0 * 60.0 * 24.0).round() as u64
+ (self.day * 24.0 * 60.0 * 60.0).round() as u64
+ (self.hour * 60.0 * 60.0).round() as u64
+ (self.minute * 60.0).round() as u64
+ self.second.trunc() as u64)
* 1000
+ millis,
)
}

pub fn parse(input: &str) -> Result<Duration, Err<(&str, ErrorKind)>> {
pub fn parse(input: &str) -> Result<Duration, DurationParseError> {
let (_, duration) = all_consuming(preceded(
tag("P"),
alt((parse_week_format, parse_basic_format)),
Expand All @@ -55,6 +64,34 @@ impl Duration {
}
}

impl FromStr for Duration {
type Err = DurationParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Duration::parse(s).map_err(DurationParseError::from)
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DurationParseError(String);

impl DurationParseError {
pub fn new<S: Into<String>>(s: S) -> DurationParseError {
DurationParseError(s.into())
}
}

impl From<Err<Error<&str>>> for DurationParseError {
fn from(err: Err<Error<&str>>) -> Self {
DurationParseError(err.to_string())
}
}

impl fmt::Display for DurationParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

fn decimal_comma_number(input: &str) -> IResult<&str, f32> {
map_res(separated_pair(digit1, tag(","), digit1), |(a, b)| {
f32::from_str(&format!("{}.{}", a, b))
Expand Down Expand Up @@ -137,3 +174,29 @@ fn parse_week_format(input: &str) -> IResult<&str, Duration> {
fn _parse_extended_format(_input: &str) -> IResult<&str, Duration> {
unimplemented!()
}

#[cfg(test)]
mod test {
use super::*;
use std::time::Duration as StdDuration;
#[test]
fn parsing_units() {
let d1: Duration = "P10Y10M10DT10H10M10S".parse().unwrap();
assert_eq!(d1.to_std(), StdDuration::from_secs(342753010));
let d2: Duration = "P10Y10M10DT10H10M10.5S".parse().unwrap();
assert_eq!(d2.to_std(), StdDuration::from_millis(342753010500));
let d3: Duration = "P10.5Y10M10DT10H10M10S".parse().unwrap();
assert_eq!(d3.to_std(), StdDuration::from_secs(358531486));
let d4: Duration = "P10Y10.5M10DT10H10M10S".parse().unwrap();
assert_eq!(d4.to_std(), StdDuration::from_secs(344067154));
let d5: Duration = "P10Y10M10.5DT10H10M10S".parse().unwrap();
assert_eq!(d5.to_std(), StdDuration::from_secs(342796210));
let d6: Duration = "P10Y10M10DT10.5H10M10S".parse().unwrap();
assert_eq!(d6.to_std(), StdDuration::from_secs(342754810));
let d7: Duration = "PT5.5H5.5M".parse().unwrap();
assert_eq!(
d7.to_std(),
StdDuration::from_secs((5.5 * 60. * 60.) as u64 + (5.5 * 60.) as u64)
);
}
}
28 changes: 19 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
//! # Usage
//!
//! ```rust
//! use iso8601_duration::Duration;
//! use nom::{error::ErrorKind, Err};
//!
//! use iso8601_duration::{ Duration, DurationParseError };
//! use std::time::Duration as StdDuration;
//! assert_eq!(
//! Duration::parse("P23DT23H"),
//! Ok(Duration::new(0., 0., 23., 23., 0., 0.))
Expand All @@ -32,25 +31,36 @@
//! Duration::parse("P12W"),
//! Ok(Duration::new(0., 0., 84., 0., 0., 0.))
//! );
//!
//! assert_eq!(
//! Duration::parse("PT30M5S").unwrap().to_std(),
//! StdDuration::new(30 * 60 + 5, 0)
//! );
//! assert_eq!(
//! Duration::parse("PT5H5M5S").unwrap().to_std(),
//! StdDuration::new(5 * 3600 + 5 * 60 + 5, 0)
//! );
//! assert_eq!(
//! Duration::parse("PT5H5M5.555S").unwrap().to_std(),
//! StdDuration::new(5 * 3600 + 5 * 60 + 5, 555_000_000)
//! );
//! assert_eq!(
//! Duration::parse("PT"),
//! Err(Err::Error(("", ErrorKind::Verify)))
//! Err(DurationParseError::new("Parsing Error: Error { input: \"\", code: Verify }"))
//! );
//! assert_eq!(
//! Duration::parse("P12WT12H30M5S"),
//! Err(Err::Error(("T12H30M5S", ErrorKind::Eof)))
//! Err(DurationParseError::new("Parsing Error: Error { input: \"T12H30M5S\", code: Eof }"))
//! );
//! assert_eq!(
//! Duration::parse("P0.5S0.5M"),
//! Err(Err::Error(("0.5S0.5M", ErrorKind::Verify)))
//! Err(DurationParseError::new("Parsing Error: Error { input: \"0.5S0.5M\", code: Verify }"))
//! );
//! assert_eq!(
//! Duration::parse("P0.5A"),
//! Err(Err::Error(("0.5A", ErrorKind::Verify)))
//! Err(DurationParseError::new("Parsing Error: Error { input: \"0.5A\", code: Verify }"))
//! );
//! ```

mod duration;

pub use crate::duration::Duration;
pub use crate::duration::{Duration, DurationParseError};