From db6961d19f540eb0ba9f4490f0dd0ab71e5c39f1 Mon Sep 17 00:00:00 2001 From: Luro02 <24826124+Luro02@users.noreply.github.com> Date: Sun, 15 Sep 2019 12:51:51 +0200 Subject: [PATCH] parse dates with chrono #9 --- Cargo.toml | 1 + src/error.rs | 13 ++++ src/tags/media_segment/date_range.rs | 67 ++++++++++++++++----- src/tags/media_segment/program_date_time.rs | 53 +++++++++++----- 4 files changed, 106 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ee2342..d37b563 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ getset = "0.0.8" failure = "0.1.5" derive_builder = "0.7.2" url = "2.1.0" +chrono = "0.4.9" [dev-dependencies] clap = "2" diff --git a/src/error.rs b/src/error.rs index c6eee27..7acb888 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,9 @@ pub type Result = std::result::Result; /// The ErrorKind. #[derive(Debug, Fail, Clone, PartialEq, Eq)] pub enum ErrorKind { + #[fail(display = "ChronoParseError: {}", _0)] + /// An error from the [Chrono](chrono) crate. + ChronoParseError(String), #[fail(display = "UrlParseError: {}", _0)] /// An error from the [Url](url) crate. UrlParseError(String), @@ -184,6 +187,10 @@ impl Error { pub(crate) fn url(value: T) -> Self { Self::from(ErrorKind::UrlParseError(value.to_string())) } + + pub(crate) fn chrono(value: T) -> Self { + Self::from(ErrorKind::ChronoParseError(value.to_string())) + } } impl From<::std::num::ParseIntError> for Error { @@ -209,3 +216,9 @@ impl From<::url::ParseError> for Error { Error::url(value) } } + +impl From<::chrono::ParseError> for Error { + fn from(value: ::chrono::ParseError) -> Self { + Error::chrono(value) + } +} diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index 138002c..d1c4fbd 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -3,6 +3,9 @@ use std::fmt; use std::str::FromStr; use std::time::Duration; +use chrono::{DateTime, FixedOffset}; +use getset::{Getters, MutGetters, Setters}; + use crate::attribute::AttributePairs; use crate::types::{DecimalFloatingPoint, ProtocolVersion}; use crate::utils::{quote, tag, unquote}; @@ -14,19 +17,52 @@ use crate::Error; /// /// TODO: Implement properly #[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters, MutGetters, Setters)] +#[get = "pub"] +#[set = "pub"] +#[get_mut = "pub"] pub struct ExtXDateRange { - pub id: String, - pub class: Option, - pub start_date: String, - pub end_date: Option, - pub duration: Option, - pub planned_duration: Option, - pub scte35_cmd: Option, - pub scte35_out: Option, - pub scte35_in: Option, - pub end_on_next: bool, - pub client_attributes: BTreeMap, + /// A string that uniquely identifies a Date Range in the Playlist. + /// This attribute is REQUIRED. + id: String, + /// A client-defined string that specifies some set of attributes and their associated value + /// semantics. All Date Ranges with the same CLASS attribute value MUST adhere to these + /// semantics. This attribute is OPTIONAL. + class: Option, + /// The date at which the Date Range begins. This attribute is REQUIRED. + start_date: DateTime, + /// The date at which the Date Range ends. It MUST be equal to or later than the value of the + /// START-DATE attribute. This attribute is OPTIONAL. + end_date: Option>, + /// The duration of the Date Range. It MUST NOT be negative. A single + /// instant in time (e.g., crossing a finish line) SHOULD be + /// represented with a duration of 0. This attribute is OPTIONAL. + duration: Option, + /// The expected duration of the Date Range. It MUST NOT be negative. This + /// attribute SHOULD be used to indicate the expected duration of a + /// Date Range whose actual duration is not yet known. + /// It is OPTIONAL. + planned_duration: Option, + /// + scte35_cmd: Option, + /// + scte35_out: Option, + /// + scte35_in: Option, + /// This attribute indicates that the end of the range containing it is equal to the + /// START-DATE of its Following Range. The Following Range is the + /// Date Range of the same CLASS, that has the earliest START-DATE + /// after the START-DATE of the range in question. This attribute is + /// OPTIONAL. + end_on_next: bool, + /// The "X-" prefix defines a namespace reserved for client-defined + /// attributes. The client-attribute MUST be a legal AttributeName. + /// Clients SHOULD use a reverse-DNS syntax when defining their own + /// attribute names to avoid collisions. The attribute value MUST be + /// a quoted-string, a hexadecimal-sequence, or a decimal-floating- + /// point. An example of a client-defined attribute is X-COM-EXAMPLE- + /// AD-ID="XYZ123". These attributes are OPTIONAL. + client_attributes: BTreeMap, } impl ExtXDateRange { @@ -102,7 +138,7 @@ impl FromStr for ExtXDateRange { "ID" => id = Some(unquote(value)), "CLASS" => class = Some(unquote(value)), "START-DATE" => start_date = Some(unquote(value)), - "END-DATE" => end_date = Some(unquote(value)), + "END-DATE" => end_date = Some(unquote(value).parse()?), "DURATION" => { let seconds: DecimalFloatingPoint = (value.parse())?; duration = Some(seconds.to_duration()); @@ -132,7 +168,10 @@ impl FromStr for ExtXDateRange { } let id = id.ok_or(Error::missing_value("EXT-X-ID"))?; - let start_date = start_date.ok_or(Error::missing_value("EXT-X-START-DATE"))?; + let start_date = start_date + .ok_or(Error::missing_value("EXT-X-START-DATE"))? + .parse()?; + if end_on_next { if class.is_none() { return Err(Error::invalid_input()); diff --git a/src/tags/media_segment/program_date_time.rs b/src/tags/media_segment/program_date_time.rs index 46a2874..643ec4b 100644 --- a/src/tags/media_segment/program_date_time.rs +++ b/src/tags/media_segment/program_date_time.rs @@ -1,6 +1,8 @@ use std::fmt; use std::str::FromStr; +use chrono::{DateTime, FixedOffset}; + use crate::types::ProtocolVersion; use crate::utils::tag; use crate::Error; @@ -8,19 +10,19 @@ use crate::Error; /// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME] /// /// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6 -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ExtXProgramDateTime(String); +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ExtXProgramDateTime(DateTime); impl ExtXProgramDateTime { pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:"; /// Makes a new `ExtXProgramDateTime` tag. - pub fn new(date_time: T) -> Self { - Self(date_time.to_string()) + pub fn new>>(date_time: T) -> Self { + Self(date_time.into()) } /// Returns the date-time of the first sample of the associated media segment. - pub const fn date_time(&self) -> &String { + pub const fn date_time(&self) -> &DateTime { &self.0 } @@ -32,7 +34,8 @@ impl ExtXProgramDateTime { impl fmt::Display for ExtXProgramDateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", Self::PREFIX, self.0) + let date_time = self.0.to_rfc3339(); + write!(f, "{}{}", Self::PREFIX, date_time) } } @@ -43,8 +46,8 @@ impl FromStr for ExtXProgramDateTime { let input = tag(input, Self::PREFIX)?; // TODO: parse with chrono - - Ok(Self::new(input)) + let date_time = DateTime::parse_from_rfc3339(input)?; + Ok(Self::new(date_time)) } } @@ -53,12 +56,34 @@ mod test { use super::*; #[test] - fn ext_x_program_date_time() { - let text = "#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00"; - assert!(text.parse::().is_ok()); + fn test_display() { + let date_time = "2010-02-19T14:54:23.031+08:00" + .parse::>() + .unwrap(); - let tag = text.parse::().unwrap(); - assert_eq!(tag.to_string(), text); - assert_eq!(tag.requires_version(), ProtocolVersion::V1); + let program_date_time = ExtXProgramDateTime::new(date_time); + + assert_eq!( + program_date_time.to_string(), + "#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00".to_string() + ); + } + + #[test] + fn test_parser() { + "#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00" + .parse::() + .unwrap(); + } + + #[test] + fn test_requires_version() { + let date_time = "2010-02-19T14:54:23.031+08:00" + .parse::>() + .unwrap(); + + let program_date_time = ExtXProgramDateTime::new(date_time); + + assert_eq!(program_date_time.requires_version(), ProtocolVersion::V1); } }