diff --git a/.travis.yml b/.travis.yml index e276f81..fb78ec8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ script: - cargo clean - cargo build - cargo test + - cargo test --features chrono # it's enough to run this once: - | diff --git a/Cargo.toml b/Cargo.toml index ff1ef16..796490f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,14 +16,16 @@ codecov = { repository = "sile/hls_m3u8" } travis-ci = { repository = "sile/hls_m3u8" } [dependencies] -chrono = "0.4" +chrono = { version = "0.4", optional = true } +backtrace = { version = "0.3", features = ["std"], optional = true } + derive_builder = "0.9" -derive_more = "0.99" hex = "0.4" +thiserror = "1.0" + +derive_more = "0.99" shorthand = "0.1" strum = { version = "0.17", features = ["derive"] } -thiserror = "1.0" -backtrace = { version = "0.3", features = ["std"], optional = true } [dev-dependencies] pretty_assertions = "0.6" diff --git a/src/error.rs b/src/error.rs index bba33d1..85fc169 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,6 +59,7 @@ enum ErrorKind { UnexpectedTag { tag: String }, #[error("{source}")] + #[cfg(feature = "chrono")] Chrono { source: chrono::ParseError }, #[error("builder error: {message}")] @@ -169,6 +170,7 @@ impl Error { } // third party crates: + #[cfg(feature = "chrono")] pub(crate) fn chrono(source: chrono::format::ParseError) -> Self { Self::new(ErrorKind::Chrono { source }) } diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index 061489d..ce6f71a 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -3,6 +3,7 @@ use std::fmt; use std::str::FromStr; use std::time::Duration; +#[cfg(feature = "chrono")] use chrono::{DateTime, FixedOffset, SecondsFormat}; use derive_builder::Builder; use shorthand::ShortHand; @@ -12,13 +13,8 @@ use crate::types::{ProtocolVersion, Value}; use crate::utils::{quote, tag, unquote}; use crate::{Error, RequiredVersion}; -/// # [4.3.2.7. EXT-X-DATERANGE] -/// -/// The [`ExtXDateRange`] tag associates a date range (i.e., a range of -/// time defined by a starting and ending date) with a set of attribute/ -/// value pairs. -/// -/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7 +/// The [`ExtXDateRange`] tag associates a date range (i.e., a range of time +/// defined by a starting and ending date) with a set of attribute/value pairs. #[derive(ShortHand, Builder, Debug, Clone, PartialEq, PartialOrd)] #[builder(setter(into))] #[shorthand(enable(must_use, into))] @@ -43,6 +39,7 @@ pub struct ExtXDateRange { /// # Note /// /// This attribute is required. + #[cfg(feature = "chrono")] start_date: DateTime, /// The date at which the [`ExtXDateRange`] ends. It must be equal to or /// later than the value of the [`start-date`] attribute. @@ -52,8 +49,12 @@ pub struct ExtXDateRange { /// This attribute is optional. /// /// [`start-date`]: #method.start_date + #[cfg(feature = "chrono")] #[builder(setter(strip_option), default)] end_date: Option>, + #[cfg(not(feature = "chrono"))] + #[builder(setter(strip_option), default)] + end_date: Option, /// The duration of the [`ExtXDateRange`]. A single instant in time (e.g., /// crossing a finish line) should be represented with a duration of 0. /// @@ -145,27 +146,48 @@ impl ExtXDateRange { /// Makes a new [`ExtXDateRange`] tag. /// /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// ``` + #[cfg_attr( + feature = "chrono", + doc = r#" +``` +# use hls_m3u8::tags::ExtXDateRange; +use chrono::offset::TimeZone; +use chrono::{DateTime, FixedOffset}; + +const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds + +let date_range = ExtXDateRange::new( + "id", + FixedOffset::east(8 * HOURS_IN_SECS) + .ymd(2010, 2, 19) + .and_hms_milli(14, 54, 23, 31), +); +``` +"# + )] + #[cfg_attr( + not(feature = "chrono"), + doc = r#" +``` +# use hls_m3u8::tags::ExtXDateRange; + +let date_range = ExtXDateRange::new("id", "2010-02-19T14:54:23.031+08:00"); +``` + "# + )] #[must_use] - pub fn new>(id: T, start_date: DateTime) -> Self { + pub fn new, #[cfg(not(feature = "chrono"))] I: Into>( + id: T, + #[cfg(feature = "chrono")] start_date: DateTime, + #[cfg(not(feature = "chrono"))] start_date: I, + ) -> Self { Self { id: id.into(), class: None, + #[cfg(feature = "chrono")] start_date, + #[cfg(not(feature = "chrono"))] + start_date: start_date.into(), end_date: None, duration: None, planned_duration: None, @@ -210,8 +232,26 @@ impl FromStr for ExtXDateRange { match key { "ID" => id = Some(unquote(value)), "CLASS" => class = Some(unquote(value)), - "START-DATE" => start_date = Some(unquote(value)), - "END-DATE" => end_date = Some(unquote(value).parse().map_err(Error::chrono)?), + "START-DATE" => { + #[cfg(feature = "chrono")] + { + start_date = Some(unquote(value).parse().map_err(Error::chrono)?) + } + #[cfg(not(feature = "chrono"))] + { + start_date = Some(unquote(value)) + } + } + "END-DATE" => { + #[cfg(feature = "chrono")] + { + end_date = Some(unquote(value).parse().map_err(Error::chrono)?) + } + #[cfg(not(feature = "chrono"))] + { + end_date = Some(unquote(value)) + } + } "DURATION" => { duration = Some(Duration::from_secs_f64( value.parse().map_err(|e| Error::parse_float(value, e))?, @@ -244,10 +284,7 @@ impl FromStr for ExtXDateRange { } let id = id.ok_or_else(|| Error::missing_value("ID"))?; - let start_date = start_date - .ok_or_else(|| Error::missing_value("START-DATE"))? - .parse() - .map_err(Error::chrono)?; + let start_date = start_date.ok_or_else(|| Error::missing_value("START-DATE"))?; if end_on_next && class.is_none() { return Err(Error::invalid_input()); @@ -277,20 +314,36 @@ impl fmt::Display for ExtXDateRange { write!(f, ",CLASS={}", quote(value))?; } - write!( - f, - ",START-DATE={}", - quote(&self.start_date.to_rfc3339_opts(SecondsFormat::AutoSi, true)) - )?; - - if let Some(value) = &self.end_date { + #[cfg(feature = "chrono")] + { write!( f, - ",END-DATE={}", - quote(&value.to_rfc3339_opts(SecondsFormat::AutoSi, true)) + ",START-DATE={}", + quote(&self.start_date.to_rfc3339_opts(SecondsFormat::AutoSi, true)) )?; } + #[cfg(not(feature = "chrono"))] + { + write!(f, ",START-DATE={}", quote(&self.start_date))?; + } + + if let Some(value) = &self.end_date { + #[cfg(feature = "chrono")] + { + write!( + f, + ",END-DATE={}", + quote(&value.to_rfc3339_opts(SecondsFormat::AutoSi, true)) + )?; + } + + #[cfg(not(feature = "chrono"))] + { + write!(f, ",END-DATE={}", quote(&value))?; + } + } + if let Some(value) = &self.duration { write!(f, ",DURATION={}", value.as_secs_f64())?; } @@ -327,9 +380,11 @@ impl fmt::Display for ExtXDateRange { mod test { use super::*; use crate::types::Float; + #[cfg(feature = "chrono")] use chrono::offset::TimeZone; use pretty_assertions::assert_eq; + #[cfg(feature = "chrono")] const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds macro_rules! generate_tests { @@ -369,7 +424,16 @@ mod test { { ExtXDateRange::builder() .id("splice-6FFFFFF0") - .start_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 15, 0)) + .start_date({ + #[cfg(feature = "chrono")] + { + FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 15, 0) + } + #[cfg(not(feature = "chrono"))] + { + "2014-03-05T11:15:00Z" + } + }) .planned_duration(Duration::from_secs_f64(59.993)) .scte35_out(concat!( "0xFC002F0000000000FF00001", @@ -393,8 +457,26 @@ mod test { ExtXDateRange::builder() .id("test_id") .class("test_class") - .start_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 15, 0)) - .end_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 16, 0)) + .start_date({ + #[cfg(feature = "chrono")] + { + FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 15, 0) + } + #[cfg(not(feature = "chrono"))] + { + "2014-03-05T11:15:00Z" + } + }) + .end_date({ + #[cfg(feature = "chrono")] + { + FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 16, 0) + } + #[cfg(not(feature = "chrono"))] + { + "2014-03-05T11:16:00Z" + } + }) .duration(Duration::from_secs_f64(60.1)) .planned_duration(Duration::from_secs_f64(59.993)) .insert_client_attribute("X-CUSTOM", Float::new(45.3)) @@ -424,12 +506,18 @@ mod test { #[test] fn test_required_version() { assert_eq!( - ExtXDateRange::new( - "id", - FixedOffset::east(8 * HOURS_IN_SECS) - .ymd(2010, 2, 19) - .and_hms_milli(14, 54, 23, 31) - ) + ExtXDateRange::new("id", { + #[cfg(feature = "chrono")] + { + FixedOffset::east(8 * HOURS_IN_SECS) + .ymd(2010, 2, 19) + .and_hms_milli(14, 54, 23, 31) + } + #[cfg(not(feature = "chrono"))] + { + "2010-02-19T14:54:23.031+08:00" + } + }) .required_version(), ProtocolVersion::V1 ); diff --git a/src/tags/media_segment/program_date_time.rs b/src/tags/media_segment/program_date_time.rs index 36caca7..6352172 100644 --- a/src/tags/media_segment/program_date_time.rs +++ b/src/tags/media_segment/program_date_time.rs @@ -1,7 +1,9 @@ use std::fmt; use std::str::FromStr; +#[cfg(feature = "chrono")] use chrono::{DateTime, FixedOffset, SecondsFormat}; +#[cfg(feature = "chrono")] use derive_more::{Deref, DerefMut}; use crate::types::ProtocolVersion; @@ -16,8 +18,18 @@ use crate::{Error, RequiredVersion}; /// [`MediaSegment`]: crate::MediaSegment /// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: /// https://tools.ietf.org/html/rfc8216#section-4.3.2.6 -#[derive(Deref, DerefMut, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ExtXProgramDateTime(DateTime); +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "chrono", derive(Deref, DerefMut, Copy))] +pub struct ExtXProgramDateTime { + /// The date-time of the first sample of the associated media segment. + #[cfg(feature = "chrono")] + #[cfg_attr(feature = "chrono", deref_mut, deref)] + pub date_time: DateTime, + /// The date-time of the first sample of the associated media segment. + #[cfg(not(feature = "chrono"))] + pub date_time: String, + __non_exhaustive: (), +} impl ExtXProgramDateTime { pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:"; @@ -39,67 +51,28 @@ impl ExtXProgramDateTime { /// ); /// ``` #[must_use] - pub const fn new(date_time: DateTime) -> Self { Self(date_time) } + #[cfg(feature = "chrono")] + pub const fn new(date_time: DateTime) -> Self { + Self { + date_time, + __non_exhaustive: (), + } + } - /// Returns the date-time of the first sample of the associated media - /// segment. + /// Makes a new [`ExtXProgramDateTime`] tag. /// /// # Example /// /// ``` /// # use hls_m3u8::tags::ExtXProgramDateTime; - /// use chrono::{FixedOffset, TimeZone}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let program_date_time = ExtXProgramDateTime::new( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// - /// assert_eq!( - /// program_date_time.date_time(), - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31) - /// ); + /// let program_date_time = ExtXProgramDateTime::new("2010-02-19T14:54:23.031+08:00"); /// ``` - #[must_use] - pub const fn date_time(&self) -> DateTime { self.0 } - - /// Sets the date-time of the first sample of the associated media segment. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXProgramDateTime; - /// use chrono::{FixedOffset, TimeZone}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut program_date_time = ExtXProgramDateTime::new( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// - /// program_date_time.set_date_time( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10), - /// ); - /// - /// assert_eq!( - /// program_date_time.date_time(), - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10) - /// ); - /// ``` - pub fn set_date_time(&mut self, value: DateTime) -> &mut Self { - self.0 = value; - self + #[cfg(not(feature = "chrono"))] + pub fn new>(date_time: T) -> Self { + Self { + date_time: date_time.into(), + __non_exhaustive: (), + } } } @@ -110,7 +83,16 @@ impl RequiredVersion for ExtXProgramDateTime { impl fmt::Display for ExtXProgramDateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let date_time = self.0.to_rfc3339_opts(SecondsFormat::Millis, true); + let date_time = { + #[cfg(feature = "chrono")] + { + self.date_time.to_rfc3339_opts(SecondsFormat::Millis, true) + } + #[cfg(not(feature = "chrono"))] + { + &self.date_time + } + }; write!(f, "{}{}", Self::PREFIX, date_time) } } @@ -121,7 +103,17 @@ impl FromStr for ExtXProgramDateTime { fn from_str(input: &str) -> Result { let input = tag(input, Self::PREFIX)?; - let date_time = DateTime::parse_from_rfc3339(input).map_err(Error::chrono)?; + let date_time = { + #[cfg(feature = "chrono")] + { + DateTime::parse_from_rfc3339(input).map_err(Error::chrono)? + } + #[cfg(not(feature = "chrono"))] + { + input + } + }; + Ok(Self::new(date_time)) } } @@ -129,20 +121,30 @@ impl FromStr for ExtXProgramDateTime { #[cfg(test)] mod test { use super::*; + #[cfg(feature = "chrono")] use chrono::{Datelike, TimeZone}; + #[cfg(feature = "chrono")] use core::ops::DerefMut; use pretty_assertions::assert_eq; + #[cfg(feature = "chrono")] const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds #[test] fn test_display() { assert_eq!( - ExtXProgramDateTime::new( - FixedOffset::east(8 * HOURS_IN_SECS) - .ymd(2010, 2, 19) - .and_hms_milli(14, 54, 23, 31) - ) + ExtXProgramDateTime::new({ + #[cfg(feature = "chrono")] + { + FixedOffset::east(8 * HOURS_IN_SECS) + .ymd(2010, 2, 19) + .and_hms_milli(14, 54, 23, 31) + } + #[cfg(not(feature = "chrono"))] + { + "2010-02-19T14:54:23.031+08:00" + } + }) .to_string(), "#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00".to_string() ); @@ -151,11 +153,18 @@ mod test { #[test] fn test_parser() { assert_eq!( - ExtXProgramDateTime::new( - FixedOffset::east(8 * HOURS_IN_SECS) - .ymd(2010, 2, 19) - .and_hms_milli(14, 54, 23, 31) - ), + ExtXProgramDateTime::new({ + #[cfg(feature = "chrono")] + { + FixedOffset::east(8 * HOURS_IN_SECS) + .ymd(2010, 2, 19) + .and_hms_milli(14, 54, 23, 31) + } + #[cfg(not(feature = "chrono"))] + { + "2010-02-19T14:54:23.031+08:00" + } + }), "#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00" .parse::() .unwrap() @@ -165,17 +174,25 @@ mod test { #[test] fn test_required_version() { assert_eq!( - ExtXProgramDateTime::new( - FixedOffset::east(8 * HOURS_IN_SECS) - .ymd(2010, 2, 19) - .and_hms_milli(14, 54, 23, 31), - ) + ExtXProgramDateTime::new({ + #[cfg(feature = "chrono")] + { + FixedOffset::east(8 * HOURS_IN_SECS) + .ymd(2010, 2, 19) + .and_hms_milli(14, 54, 23, 31) + } + #[cfg(not(feature = "chrono"))] + { + "2010-02-19T14:54:23.031+08:00" + } + }) .required_version(), ProtocolVersion::V1 ); } #[test] + #[cfg(feature = "chrono")] fn test_deref() { assert_eq!( ExtXProgramDateTime::new( @@ -189,6 +206,7 @@ mod test { } #[test] + #[cfg(feature = "chrono")] fn test_deref_mut() { assert_eq!( ExtXProgramDateTime::new(