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

Initial commit for the introduction of the iCalendar-Duration struct. #94

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
28 changes: 14 additions & 14 deletions examples/alarm.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use chrono::*;
use chrono::{Utc, Duration as ChronoDuration, };
hoodie marked this conversation as resolved.
Show resolved Hide resolved
use icalendar::*;

fn main() {
let mut calendar = Calendar::new();

let now = Utc::now();
let soon = Utc::now() + Duration::minutes(12);
let tomorrow = Utc::now() + Duration::days(1);
let soon = Utc::now() + ChronoDuration::minutes(12);
let tomorrow = Utc::now() + ChronoDuration::days(1);

let todo_test_audio = Todo::new()
.summary("TODO with audio alarm -15min")
Expand All @@ -17,22 +17,22 @@ fn main() {
.percent_complete(98)
.alarm(
Alarm::audio(-Duration::minutes(10))
.duration_and_repeat(chrono::Duration::minutes(1), 4),
.duration_and_repeat(Duration::minutes(1), 4),
)
.done();

let event_test_display = Event::new()
.summary("test event")
.description("here I have something really important to do")
.starts(Utc::now() + Duration::minutes(5))
.starts(Utc::now() + ChronoDuration::minutes(5))
.class(Class::Confidential)
.ends(Utc::now() + Duration::hours(1))
.ends(Utc::now() + ChronoDuration::hours(1))
.alarm(
Alarm::display(
"you should test your implementation",
Utc::now() + Duration::minutes(1),
"you should test your implementation",
Utc::now() + ChronoDuration::minutes(1),
)
.duration_and_repeat(chrono::Duration::minutes(1), 4),
.duration_and_repeat(Duration::minutes(1), 4),
)
.done();

Expand All @@ -44,10 +44,10 @@ fn main() {
.status(TodoStatus::NeedsAction)
.alarm(
Alarm::display(
"you should test your implementation",
(-Duration::minutes(10), Related::End),
"you should test your implementation",
(-Duration::minutes(10), Related::End),
)
.duration_and_repeat(chrono::Duration::minutes(1), 4),
.duration_and_repeat(Duration::minutes(1), 4),
)
.done();

Expand All @@ -58,8 +58,8 @@ fn main() {
.due(tomorrow)
.status(TodoStatus::NeedsAction)
.alarm(
Alarm::audio(now + Duration::minutes(1))
.duration_and_repeat(chrono::Duration::minutes(1), 4),
Alarm::audio(now + ChronoDuration::minutes(1))
.duration_and_repeat(Duration::minutes(1), 4),
)
.done();

Expand Down
4 changes: 2 additions & 2 deletions examples/alarm_minimal.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use chrono::*;
use chrono::{Utc, Duration as ChronoDuration};
use icalendar::*;

fn main() {
// alarm will occur one minute from now
let event_with_absolute_audio_alarm = Event::new()
.alarm(
Alarm::audio(Utc::now() + Duration::minutes(1))
Alarm::audio(Utc::now() + ChronoDuration::minutes(1))
.duration_and_repeat(Duration::minutes(1), 4),
)
.done();
Expand Down
4 changes: 2 additions & 2 deletions examples/full_circle.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg(feature = "parser")]
use std::str::FromStr;

use chrono::*;
use chrono::{Utc, Duration as ChronoDuration};
use icalendar::parser;
use icalendar::*;

Expand All @@ -11,7 +11,7 @@ fn main() {
.description("here I have something really important to do")
.starts(Utc::now())
.class(Class::Confidential)
.ends(Utc::now() + Duration::days(1))
.ends(Utc::now() + ChronoDuration::days(1))
.append_property(
Property::new("TEST", "FOOBAR")
.add_parameter("IMPORTANCE", "very")
Expand Down
4 changes: 2 additions & 2 deletions examples/readme.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use chrono::*;
use chrono::{Utc, NaiveDate, Duration as ChronoDuration};
#[cfg(feature = "chrono-tz")]
use chrono_tz::Europe::Berlin;
use icalendar::*;
Expand All @@ -14,7 +14,7 @@ fn main() {
.description("here I have something really important to do")
.starts(Utc::now())
.class(Class::Confidential)
.ends(Utc::now() + Duration::days(1))
.ends(Utc::now() + ChronoDuration::days(1))
.append_property(
Property::new("TEST", "FOOBAR")
.add_parameter("IMPORTANCE", "very")
Expand Down
4 changes: 2 additions & 2 deletions examples/todos.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use chrono::*;
use chrono::{Local, Utc, Duration as ChronoDuration};
use icalendar::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let groceries = Todo::new()
.summary("buy groceries")
.description("* soy-milk\n* oak-meal\n* vegan chocolate\n* kale\n* bacon\nabcdefghijklmnopqrstuvwxyz")
.starts(Local::now().naive_local())
.ends(Local::now().naive_local() + Duration::hours(24))
.ends(Local::now().naive_local() + ChronoDuration::hours(24))
.priority(12)
.percent_complete(28)
.status(TodoStatus::InProcess)
Expand Down
207 changes: 199 additions & 8 deletions src/components/alarm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use chrono::Duration;
use std::{collections::HashMap, fmt::Debug, str::FromStr};

pub use self::properties::{Related, Trigger};
pub use self::properties::{Related, Trigger, Duration};

use self::properties::*;
use super::*;
Expand Down Expand Up @@ -282,7 +281,11 @@ fn test_email() {

pub mod properties {

use crate::components::{alarm::properties::Parameter, date_time::parse_duration};
use std::{fmt::Display, ops::Neg};

use crate::components::alarm::properties::Parameter;

use self::date_time::parse_duration;

use super::*;

Expand Down Expand Up @@ -402,6 +405,178 @@ pub mod properties {
)
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Duration {
hoodie marked this conversation as resolved.
Show resolved Hide resolved
positive: bool,
weeks: u64,
days: u64,
hours: u64,
minutes: u64,
seconds: u64
}

impl Default for Duration {
fn default() -> Self {
Self {
positive: true,
weeks:0,
days:0,
hours:0,
minutes:0,
seconds:0
}
}
}

#[allow(dead_code)]
impl Duration {
pub fn new(positive: bool, weeks: u64, days: u64, hours: u64, minutes: u64, seconds: u64) -> Self {
Self{positive, weeks, days, hours, minutes, seconds}
}

pub fn weeks(weeks: u64) -> Self {
Self {
weeks,
..Self::default()
}
}

pub fn days(days: u64) -> Self {
Self {
days,
..Self::default()
}
}

pub fn hours(hours:u64) -> Self {
Self {
hours,
..Self::default()
}
}

pub fn minutes(minutes: u64) -> Self {
Self {
minutes,
..Self::default()
}
}

pub fn seconds(seconds: u64) -> Self {
Self {
seconds,
..Self::default()
}
}
}

impl Neg for Duration {
type Output = Duration;

fn neg(self) -> Self::Output {
Self {
positive: !self.positive,
..self
}
}
}

impl FromStr for Duration {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(())
}
let mut positiv:bool = true;

let mut it = s.chars();
let first = it.next();
if let Some(first) = first {
if first == '-' {
positiv = false;
}
}

let parsed = if positiv {
iso8601::duration(s)
} else {
iso8601::duration(it.as_str())
};

if let Ok(parsed) = parsed {
let parsed = Duration::try_from(parsed)?;

if !positiv {
Ok(-parsed)
} else {
Ok(parsed)
}

} else {
Err(())
}
}

}

impl TryFrom<iso8601::Duration> for Duration {
type Error = ();

fn try_from(value: iso8601::Duration) -> Result<Self, Self::Error> {
// What happens if the format is P1W1D?
// I think this didnt work before either
match value {
iso8601::Duration::YMDHMS { year, month, day, hour, minute, second, millisecond: _ } => {
if (year | month ) > 0 {
// Its not allowed to have year or month specifiers for ics files
// https://icalendar.org/iCalendar-RFC-5545/3-3-6-duration.html
return Err(())
hoodie marked this conversation as resolved.
Show resolved Hide resolved
}
Ok(Self::new(true, 0, day as u64, hour as u64, minute as u64, second as u64))
},
iso8601::Duration::Weeks(weeks) => Ok(Self::weeks(weeks as u64)),
}
}
}

impl From<Duration> for Property {
fn from(value: Duration) -> Self {
Property::new_pre_alloc("DURATION".into(), value.to_string())
}
}


impl Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.positive {
f.write_str("-")?;
}
f.write_str("P")?;

if self.weeks > 0 {
f.write_fmt(format_args!("{}W", self.weeks))?;
}
if self.days > 0 {
f.write_fmt(format_args!("{}D", self.days))?;
}
if (self.hours | self.minutes | self.seconds) > 0 {
f.write_str("T")?;
if self.hours > 0 {
f.write_fmt(format_args!("{}H", self.hours))?;
}
if self.minutes > 0 {
f.write_fmt(format_args!("{}M", self.minutes))?;
}
if self.seconds > 0 {
f.write_fmt(format_args!("{}S", self.seconds))?;
}
}

Ok(())
}
}

/// Describes when an alarm is supposed to occure.
///
/// [RFC 5545, Section 3.8.6.3](https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.6.3),
Expand All @@ -418,7 +593,6 @@ pub mod properties {
impl Trigger {
/// ```
/// # use icalendar::*;
/// # use chrono::*;
/// assert_eq!(
/// Trigger::after_start(Duration::hours(1)),
/// Trigger::Duration(Duration::hours(1), Some(Related::Start))
Expand All @@ -431,7 +605,6 @@ pub mod properties {

/// ```
/// # use icalendar::*;
/// # use chrono::*;
/// assert_eq!(
/// Trigger::after_end(Duration::hours(1)),
/// Trigger::Duration(Duration::hours(1), Some(Related::End))
Expand All @@ -444,7 +617,6 @@ pub mod properties {

/// ```
/// # use icalendar::*;
/// # use chrono::*;
/// assert_eq!(
/// Trigger::before_start(Duration::hours(1)),
/// Trigger::Duration(-Duration::hours(1), Some(Related::Start))
Expand All @@ -457,7 +629,6 @@ pub mod properties {

/// ```
/// # use icalendar::*;
/// # use chrono::*;
/// assert_eq!(
/// Trigger::before_end(Duration::hours(1)),
/// Trigger::Duration(-Duration::hours(1), Some(Related::End))
Expand Down Expand Up @@ -527,7 +698,6 @@ pub mod properties {
let param_related = prop.get_param_as("RELATED", |s| Related::from_str(s).ok());

// TODO: improve error handling here
// TODO: yes I found icalendar-duration, let's find a way to integrate this if possible
let parsed_duration = prop.get_value_as(parse_duration);

if let Some(duration) = parsed_duration {
Expand Down Expand Up @@ -637,4 +807,25 @@ pub mod properties {
Some(Trigger::Duration(dur, Some(Related::Start)))
);
}

#[test]
fn test_duration_parse_full_positiv() {
let dur_str = "P1DT1H1M1S";

pretty_assertions::assert_eq!(
Duration::from_str(dur_str).unwrap().to_string(),
dur_str
);
}

#[test]
fn test_duration_parse_full_negativ() {
let dur_str = "-P1DT1H1M1S";

pretty_assertions::assert_eq!(
Duration::from_str(dur_str).unwrap().to_string(),
dur_str
);
}

}
Loading