1#[cfg(feature = "recurrence")]
2use crate::recurrence::RecurrenceError;
3use chrono::{DateTime, NaiveDate, Utc};
4use uuid::Uuid;
5
6use std::{collections::BTreeMap, fmt, mem};
7
8use crate::{Attendee, properties::*};
9use date_time::{format_utc_date_time, naive_date_to_property, parse_utc_date_time};
10
11pub mod alarm;
12pub(crate) mod date_time;
13mod event;
14mod other;
15mod todo;
16mod venue;
17
18use alarm::*;
19use date_time::{CalendarDateTime, DatePerhapsTime};
20pub use event::*;
21pub use other::*;
22pub use todo::*;
23pub use venue::*;
24
25#[derive(Debug, Default, PartialEq, Eq, Clone)]
26pub(crate) struct InnerComponent {
27 pub properties: BTreeMap<String, Property>,
28 pub multi_properties: BTreeMap<String, Vec<Property>>,
29 pub components: Vec<Other>,
30}
31
32impl From<Other> for InnerComponent {
33 fn from(val: Other) -> Self {
34 val.inner
35 }
36}
37
38impl InnerComponent {
45 pub fn done(&mut self) -> Self {
48 InnerComponent {
49 properties: mem::take(&mut self.properties),
50 multi_properties: mem::take(&mut self.multi_properties),
51 components: mem::take(&mut self.components),
52 }
53 }
54
55 pub(crate) fn insert_multi(&mut self, property: impl Into<Property>) -> &mut Self {
56 let property = property.into();
57 let key = property.key().to_owned();
58
59 self.multi_properties
60 .entry(key)
61 .and_modify(|v| v.push(property.to_owned()))
62 .or_insert(vec![property.to_owned()]);
63 self
64 }
65
66 #[cfg(test)]
67 pub fn property_value(&self, key: &str) -> Option<&str> {
68 Some(self.properties.get(key)?.value())
69 }
70}
71
72pub trait Component {
74 fn component_kind(&self) -> String;
80
81 fn properties(&self) -> &BTreeMap<String, Property>;
83
84 fn components(&self) -> &[Other];
86
87 fn multi_properties(&self) -> &BTreeMap<String, Vec<Property>>;
89
90 fn property_value(&self, key: &str) -> Option<&str> {
92 Some(self.properties().get(key)?.value())
93 }
94
95 fn fmt_write<W: fmt::Write>(&self, out: &mut W) -> Result<(), fmt::Error> {
97 write_crlf!(out, "BEGIN:{}", self.component_kind())?;
98
99 if !self.properties().contains_key("DTSTAMP") {
100 let now = Utc::now();
101 write_crlf!(out, "DTSTAMP:{}", format_utc_date_time(now))?;
102 }
103
104 for property in self.properties().values() {
105 property.fmt_write(out)?;
106 }
107
108 if !self.properties().contains_key("UID") {
109 write_crlf!(out, "UID:{}", Uuid::new_v4())?;
110 }
111
112 for property in self.multi_properties().values().flatten() {
113 property.fmt_write(out)?;
114 }
115
116 for component in self.components() {
117 component.fmt_write(out)?;
118 }
119
120 write_crlf!(out, "END:{}", self.component_kind())?;
121 Ok(())
122 }
123
124 fn to_string(&self) -> String {
130 self.try_into_string().unwrap()
131 }
132
133 fn try_into_string(&self) -> Result<String, fmt::Error> {
135 let mut out_string = String::new();
136 self.fmt_write(&mut out_string)?;
137 Ok(out_string)
138 }
139
140 fn append_property(&mut self, property: impl Into<Property>) -> &mut Self;
142
143 fn append_component(&mut self, child: impl Into<Other>) -> &mut Self;
145
146 fn append_multi_property(&mut self, property: impl Into<Property>) -> &mut Self;
148
149 fn add_property(&mut self, key: impl Into<String>, val: impl Into<String>) -> &mut Self {
151 self.append_property(Property::new(key, val))
152 }
153
154 fn remove_property(&mut self, key: &str) -> &mut Self;
156
157 fn remove_multi_property(&mut self, key: &str) -> &mut Self;
159
160 #[deprecated]
161 fn add_property_pre_alloc(&mut self, key: String, val: String) -> &mut Self {
163 self.append_property(Property::new(key, val))
164 }
165
166 fn add_multi_property(&mut self, key: &str, val: &str) -> &mut Self {
168 self.append_multi_property(Property::new(key, val))
169 }
170
171 fn timestamp(&mut self, dt: DateTime<Utc>) -> &mut Self {
175 self.add_property("DTSTAMP", format_utc_date_time(dt))
176 }
177
178 fn remove_timestamp(&mut self) -> &mut Self {
180 self.remove_property("DTSTAMP")
181 }
182
183 fn get_timestamp(&self) -> Option<DateTime<Utc>> {
185 parse_utc_date_time(self.property_value("DTSTAMP")?)
186 }
187
188 fn get_start(&self) -> Option<DatePerhapsTime> {
190 DatePerhapsTime::from_property(self.properties().get("DTSTART")?)
191 }
192
193 fn get_recurrence_id(&self) -> Option<DatePerhapsTime> {
195 DatePerhapsTime::from_property(self.properties().get("RECURRENCE-ID")?)
196 }
197
198 fn get_end(&self) -> Option<DatePerhapsTime> {
200 DatePerhapsTime::from_property(self.properties().get("DTEND")?)
201 }
202
203 fn priority(&mut self, priority: u32) -> &mut Self {
207 let priority = std::cmp::min(priority, 10);
208 self.add_property("PRIORITY", priority.to_string())
209 }
210
211 fn remove_priority(&mut self) -> &mut Self {
213 self.remove_property("PRIORITY")
214 }
215
216 fn get_priority(&self) -> Option<u32> {
227 let priority = self.property_value("PRIORITY")?.parse().ok()?;
228 if priority <= 10 { Some(priority) } else { None }
229 }
230
231 fn print(&self) -> Result<(), fmt::Error> {
233 let mut out = String::new();
234 self.fmt_write(&mut out)?;
235 print_crlf!("{}", out);
236 Ok(())
237 }
238
239 fn summary(&mut self, desc: &str) -> &mut Self {
241 self.add_property("SUMMARY", desc)
242 }
243
244 fn remove_summary(&mut self) -> &mut Self {
246 self.remove_property("SUMMARY")
247 }
248
249 fn get_summary(&self) -> Option<&str> {
251 self.property_value("SUMMARY")
252 }
253
254 fn description(&mut self, desc: &str) -> &mut Self {
256 self.add_property("DESCRIPTION", desc)
257 }
258
259 fn remove_description(&mut self) -> &mut Self {
261 self.remove_property("DESCRIPTION")
262 }
263
264 fn get_description(&self) -> Option<&str> {
266 self.property_value("DESCRIPTION")
267 }
268
269 fn attendee(&mut self, attendee: Attendee) -> &mut Self {
271 self.append_multi_property(attendee)
272 }
273
274 fn get_attendees(&self) -> Vec<Attendee> {
276 self.multi_properties()
277 .get("ATTENDEE")
278 .map(|v| {
279 v.iter()
280 .filter_map(|p| Attendee::try_from(p).ok())
281 .collect::<Vec<_>>()
282 })
283 .unwrap_or_default()
284 }
285
286 fn uid(&mut self, uid: &str) -> &mut Self {
288 self.add_property("UID", uid)
289 }
290
291 fn get_uid(&self) -> Option<&str> {
293 self.property_value("UID")
294 }
295
296 fn sequence(&mut self, sequence: u32) -> &mut Self {
298 self.add_property("SEQUENCE", sequence.to_string())
299 }
300
301 fn remove_sequence(&mut self) -> &mut Self {
303 self.remove_property("SEQUENCE")
304 }
305
306 fn get_sequence(&self) -> Option<u32> {
308 self.property_value("SEQUENCE").and_then(|s| s.parse().ok())
309 }
310
311 fn class(&mut self, class: Class) -> &mut Self {
313 self.append_property(class)
314 }
315
316 fn remove_class(&mut self) -> &mut Self {
318 self.remove_property("CLASS")
319 }
320
321 fn get_class(&self) -> Option<Class> {
323 Class::from_str(self.property_value("CLASS")?)
324 }
325
326 fn url(&mut self, url: &str) -> &mut Self {
328 self.add_property("URL", url)
329 }
330
331 fn remove_url(&mut self) -> &mut Self {
333 self.remove_property("URL")
334 }
335
336 fn get_url(&self) -> Option<&str> {
338 self.property_value("URL")
339 }
340
341 fn last_modified(&mut self, dt: DateTime<Utc>) -> &mut Self {
345 self.add_property("LAST-MODIFIED", format_utc_date_time(dt))
346 }
347
348 fn remove_last_modified(&mut self) -> &mut Self {
350 self.remove_property("LAST-MODIFIED")
351 }
352
353 fn get_last_modified(&self) -> Option<DateTime<Utc>> {
355 parse_utc_date_time(self.property_value("LAST-MODIFIED")?)
356 }
357
358 fn created(&mut self, dt: DateTime<Utc>) -> &mut Self {
362 self.add_property("CREATED", format_utc_date_time(dt))
363 }
364
365 fn remove_created(&mut self) -> &mut Self {
367 self.remove_property("CREATED")
368 }
369
370 fn get_created(&self) -> Option<DateTime<Utc>> {
372 parse_utc_date_time(self.property_value("CREATED")?)
373 }
374}
375
376pub trait EventLike: Component {
378 fn starts<T: Into<DatePerhapsTime>>(&mut self, dt: T) -> &mut Self {
382 let calendar_dt = dt.into();
383 self.append_property(calendar_dt.to_property("DTSTART"))
384 }
385
386 fn remove_starts(&mut self) -> &mut Self {
388 self.remove_property("DTSTART")
389 }
390
391 fn ends<T: Into<DatePerhapsTime>>(&mut self, dt: T) -> &mut Self {
395 let calendar_dt = dt.into();
396 self.append_property(calendar_dt.to_property("DTEND"))
397 }
398
399 fn remove_ends(&mut self) -> &mut Self {
401 self.remove_property("DTEND")
402 }
403
404 fn recurrence_id<T: Into<DatePerhapsTime>>(&mut self, dt: T) -> &mut Self {
409 let calendar_dt = dt.into();
410 self.append_property(calendar_dt.to_property("RECURRENCE-ID"))
411 }
412
413 fn remove_recurrence_id(&mut self) -> &mut Self {
415 self.remove_property("RECURRENCE-ID")
416 }
417
418 fn all_day(&mut self, date: NaiveDate) -> &mut Self {
422 self.append_property(naive_date_to_property(date, "DTSTART"))
423 .append_property(naive_date_to_property(date, "DTEND"))
424 }
425
426 fn venue(&mut self, location: &str, venue_uid: &str) -> &mut Self {
429 self.append_property(
430 Property::new("LOCATION", location)
431 .append_parameter(Parameter::new("VVENUE", venue_uid))
432 .done(),
433 );
434 self
435 }
436
437 fn location(&mut self, location: &str) -> &mut Self {
440 self.add_property("LOCATION", location)
441 }
442
443 fn remove_location(&mut self) -> &mut Self {
445 self.remove_property("LOCATION")
446 }
447
448 fn get_location(&self) -> Option<&str> {
450 self.property_value("LOCATION")
451 }
452
453 #[cfg(feature = "recurrence")]
461 fn recurrence(&mut self, rrule: crate::UnvalidatedRRule) -> Result<&mut Self, RecurrenceError> {
462 let dt_start_prop = self
464 .properties()
465 .get("DTSTART")
466 .ok_or(RecurrenceError::MissingDtStart)?;
467
468 let dt_start = crate::recurrence::dt_start_to_rrule_datetime(dt_start_prop)?;
469
470 let rruleset = rrule.build(dt_start)?;
471
472 let rrule_str = rruleset
473 .get_rrule()
474 .iter()
475 .map(ToString::to_string)
476 .collect::<Vec<_>>()
477 .join("\n");
478
479 self.add_property("RRULE", rrule_str);
480 for dt in rruleset.get_rdate() {
481 self.rdate(CalendarDateTime::from(dt));
482 }
483 for dt in rruleset.get_exdate() {
484 self.rdate(CalendarDateTime::from(dt));
485 }
486
487 Ok(self)
488 }
489
490 #[cfg(feature = "recurrence")]
511 fn get_recurrence(&self) -> Result<rrule::RRuleSet, RecurrenceError> {
512 use std::fmt::Write;
513
514 let mut b = String::new();
515
516 if let Some(dt_start_prop) = self.properties().get("DTSTART") {
517 if let Some(tzid) = dt_start_prop.params().get("TZID") {
520 writeln!(b, "DTSTART;TZID={}:{}", tzid.value(), dt_start_prop.value()).unwrap();
521 } else {
522 writeln!(b, "DTSTART:{}", dt_start_prop.value()).unwrap();
523 }
524
525 let has_rrule = self.property_value("RRULE").is_some();
529 if !has_rrule {
530 if let Some(tzid) = dt_start_prop.params().get("TZID") {
531 writeln!(b, "RDATE;TZID={}:{}", tzid.value(), dt_start_prop.value()).unwrap();
532 } else {
533 writeln!(b, "RDATE:{}", dt_start_prop.value()).unwrap();
534 }
535 }
536 };
537
538 if let Some(rrule_str) = self.property_value("RRULE") {
539 writeln!(b, "RRULE:{rrule_str}").unwrap();
540 }
541
542 if let Some(rdates) = self.multi_properties().get("RDATE") {
543 for rdate in rdates.iter().filter_map(|p| p.to_line().ok()) {
544 writeln!(b, "{rdate}").unwrap();
545 }
546 }
547
548 if let Some(exdates) = self.multi_properties().get("EXDATE") {
549 for exdate in exdates.iter().filter_map(|p| p.to_line().ok()) {
550 writeln!(b, "{exdate}").unwrap();
551 }
552 }
553
554 b.parse::<rrule::RRuleSet>().map_err(RecurrenceError::Rule)
555 }
556
557 fn rdate<T: Into<DatePerhapsTime>>(&mut self, rdate: T) -> &mut Self {
561 self.append_multi_property(rdate.into().to_property("RDATE"))
562 }
563
564 fn exdate<T: Into<DatePerhapsTime>>(&mut self, exdate: T) -> &mut Self {
568 self.append_multi_property(exdate.into().to_property("EXDATE"))
569 }
570
571 fn alarm<A: Into<Alarm>>(&mut self, alarm: A) -> &mut Self {
574 let alarm: Alarm = alarm.into();
575 self.append_component(alarm)
576 }
577}
578
579macro_rules! event_impl {
580 ($t:ty) => {
581 impl EventLike for $t {}
582 };
583}
584
585macro_rules! component_impl {
586 ($t:ty, $kind:expr) => {
587 impl Component for $t {
588 fn component_kind(&self) -> String {
592 $kind
593 }
594
595 fn properties(&self) -> &BTreeMap<String, Property> {
597 &self.inner.properties
598 }
599
600 fn components(&self) -> &[Other] {
602 &self.inner.components
603 }
604
605 fn multi_properties(&self) -> &BTreeMap<String, Vec<Property>> {
607 &self.inner.multi_properties
608 }
609
610 fn append_property(&mut self, property: impl Into<Property>) -> &mut Self {
612 let property = property.into();
613 self.inner
614 .properties
615 .insert(property.key().to_owned(), property);
616 self
617 }
618
619 fn append_component(&mut self, child: impl Into<Other>) -> &mut Self {
621 self.inner.components.push(child.into());
622 self
623 }
624
625 fn append_multi_property(&mut self, property: impl Into<Property>) -> &mut Self {
627 self.inner.insert_multi(property);
628 self
629 }
630
631 fn remove_property(&mut self, key: &str) -> &mut Self {
633 self.inner.properties.remove(key);
634 self
635 }
636
637 fn remove_multi_property(&mut self, key: &str) -> &mut Self {
639 self.inner.multi_properties.remove(key);
640 self
641 }
642 }
643
644 impl From<InnerComponent> for $t {
645 fn from(inner: InnerComponent) -> $t {
646 Self { inner }
647 }
648 }
649 impl From<$t> for Other {
650 fn from(val: $t) -> Self {
651 (val.component_kind(), val.inner).into()
652 }
653 }
654
655 impl TryInto<String> for $t {
656 type Error = std::fmt::Error;
657
658 fn try_into(self) -> Result<String, Self::Error> {
659 self.try_into_string()
660 }
661 }
662 };
663}
664
665component_impl! { Event, String::from("VEVENT") }
666event_impl! { Event }
667
668component_impl! { Todo , String::from("VTODO")}
669event_impl! { Todo}
670
671component_impl! { Venue , String::from("VVENUE")}
672component_impl! { Alarm, String::from("VALARM") }
673
674#[cfg(test)]
675mod tests {
676 use chrono::TimeZone;
677
678 use super::*;
679
680 #[test]
681 #[cfg(feature = "parser")]
682 fn get_url() {
683 let url = "http://hoodie.de/";
684 let event = Event::new().url(url).done();
685
686 let serialized = event.to_string();
687 let reparsed =
688 Other::from(crate::parser::Component::<'_>::try_from(serialized.as_str()).unwrap());
689
690 assert_eq!(event.get_url(), Some(url));
691 assert_eq!(reparsed.get_url(), Some(url));
692 }
693
694 #[test]
695 fn get_properties_unset() {
696 let event = Event::new();
697 assert_eq!(event.get_priority(), None);
698 assert_eq!(event.get_summary(), None);
699 assert_eq!(event.get_description(), None);
700 assert_eq!(event.get_location(), None);
701 assert_eq!(event.get_uid(), None);
702 assert_eq!(event.get_class(), None);
703 assert_eq!(event.get_timestamp(), None);
704 assert_eq!(event.get_last_modified(), None);
705 assert_eq!(event.get_created(), None);
706 assert_eq!(event.get_url(), None);
707 }
708
709 #[test]
710 fn get_properties_set() {
711 let event = Event::new()
712 .priority(5)
713 .summary("summary")
714 .description("description")
715 .location("location")
716 .uid("uid")
717 .class(Class::Private)
718 .url("http://some.test/url")
719 .done();
720 assert_eq!(event.get_priority(), Some(5));
721 assert_eq!(event.get_summary(), Some("summary"));
722 assert_eq!(event.get_description(), Some("description"));
723 assert_eq!(event.get_location(), Some("location"));
724 assert_eq!(event.get_uid(), Some("uid"));
725 assert_eq!(event.get_class(), Some(Class::Private));
726 assert_eq!(event.get_url(), Some("http://some.test/url"));
727 }
728
729 #[test]
730 fn get_properties_remove() {
731 let mut event = Event::new()
732 .priority(5)
733 .summary("summary")
734 .description("description")
735 .location("location")
736 .uid("uid")
737 .class(Class::Private)
738 .url("http://some.test/url")
739 .done();
740 assert_eq!(event.get_priority(), Some(5));
741 assert_eq!(event.get_summary(), Some("summary"));
742 assert_eq!(event.get_description(), Some("description"));
743 assert_eq!(event.get_location(), Some("location"));
744 assert_eq!(event.get_uid(), Some("uid"));
745 assert_eq!(event.get_class(), Some(Class::Private));
746 assert_eq!(event.get_url(), Some("http://some.test/url"));
747
748 event
749 .remove_priority()
750 .remove_summary()
751 .remove_description()
752 .remove_location()
753 .remove_class()
754 .remove_url();
755 assert_eq!(event.get_priority(), None);
756 assert_eq!(event.get_summary(), None);
757 assert_eq!(event.get_description(), None);
758 assert_eq!(event.get_location(), None);
759 assert_eq!(event.get_class(), None);
760 assert_eq!(event.get_url(), None);
761 }
762
763 #[test]
764 fn get_date_times_naive() {
765 let naive_date_time = NaiveDate::from_ymd_opt(2001, 3, 13)
766 .unwrap()
767 .and_hms_opt(14, 15, 16)
768 .unwrap();
769 let event = Event::new()
770 .starts(naive_date_time)
771 .ends(naive_date_time)
772 .done();
773 assert_eq!(event.get_start(), Some(naive_date_time.into()));
774 assert_eq!(event.get_end(), Some(naive_date_time.into()));
775 }
776
777 #[test]
778 fn get_date_times_utc() {
779 let utc_date_time = Utc.with_ymd_and_hms(2001, 3, 13, 14, 15, 16).unwrap();
780 let event = Event::new()
781 .timestamp(utc_date_time)
782 .last_modified(utc_date_time)
783 .created(utc_date_time)
784 .starts(utc_date_time)
785 .ends(utc_date_time)
786 .done();
787 assert_eq!(event.get_timestamp(), Some(utc_date_time));
788 assert_eq!(event.get_last_modified(), Some(utc_date_time));
789 assert_eq!(event.get_created(), Some(utc_date_time));
790 assert_eq!(event.get_start(), Some(utc_date_time.into()));
791 assert_eq!(event.get_end(), Some(utc_date_time.into()));
792 }
793
794 #[test]
795 fn get_date_times_tzid() {
796 let date_time = NaiveDate::from_ymd_opt(2001, 3, 13)
797 .unwrap()
798 .and_hms_opt(14, 15, 16)
799 .unwrap();
800 let date_time_tzid = CalendarDateTime::WithTimezone {
801 date_time,
802 tzid: "Pacific/Auckland".to_string(),
803 };
804 let event = Event::new()
805 .starts(date_time_tzid.clone())
806 .ends(date_time_tzid.clone())
807 .done();
808 assert_eq!(event.get_start(), Some(date_time_tzid.clone().into()));
809 assert_eq!(event.get_end(), Some(date_time_tzid.into()));
810 }
811
812 #[test]
813 fn get_dates_naive() {
814 let naive_date = NaiveDate::from_ymd_opt(2001, 3, 13).unwrap();
815 let event = Event::new().starts(naive_date).ends(naive_date).done();
816 assert_eq!(event.get_start(), Some(naive_date.into()));
817 assert_eq!(event.get_end(), Some(naive_date.into()));
818 }
819
820 #[test]
821 fn exdate_accepts_naive_date() {
822 let naive_date = NaiveDate::from_ymd_opt(2001, 3, 13).unwrap();
823 let event = Event::new().exdate(naive_date).done();
824
825 let exdates = event.multi_properties().get("EXDATE").unwrap();
826 assert_eq!(exdates.len(), 1);
827 let line = exdates.first().unwrap().to_line().unwrap();
829 assert!(
830 line.contains("VALUE=DATE"),
831 "EXDATE should carry VALUE=DATE parameter, got: {line}"
832 );
833 assert_eq!(exdates.first().unwrap().value(), "20010313");
834 }
835
836 #[test]
837 fn rdate_accepts_naive_date() {
838 let naive_date = NaiveDate::from_ymd_opt(2001, 3, 13).unwrap();
839 let event = Event::new().rdate(naive_date).done();
840
841 let rdates = event.multi_properties().get("RDATE").unwrap();
842 assert_eq!(rdates.len(), 1);
843 let line = rdates.first().unwrap().to_line().unwrap();
845 assert!(
846 line.contains("VALUE=DATE"),
847 "RDATE should carry VALUE=DATE parameter, got: {line}"
848 );
849 assert_eq!(rdates.first().unwrap().value(), "20010313");
850 }
851
852 #[test]
853 #[cfg(feature = "recurrence")]
854 fn get_recurrence() {
855 use crate::{Frequency, NWeekday, RRule, Weekday};
856
857 let naive_date = NaiveDate::from_ymd_opt(2001, 3, 13).unwrap();
858
859 let rrule = RRule::default()
860 .count(4)
861 .freq(Frequency::Weekly)
862 .by_weekday(vec![
863 NWeekday::Every(Weekday::Tue),
864 NWeekday::Every(Weekday::Wed),
865 ]);
866
867 let event = Event::new()
868 .starts(naive_date)
869 .ends(naive_date)
870 .recurrence(rrule)
871 .unwrap()
872 .done();
873
874 let output = event
875 .get_recurrence()
876 .expect("event should have a recurrence rule");
877
878 let output_rrules = output.get_rrule();
879
880 assert_eq!(output_rrules.len(), 1);
881 assert_eq!(output_rrules.first().unwrap().get_freq(), Frequency::Weekly);
882 assert_eq!(output_rrules.first().unwrap().get_interval(), 1);
883 assert_eq!(
884 output_rrules.first().unwrap().get_by_weekday(),
885 [NWeekday::Every(Weekday::Tue), NWeekday::Every(Weekday::Wed)]
886 );
887 }
888
889 #[test]
890 #[cfg(feature = "recurrence")]
891 fn no_empty_rdate_or_exdate_added() {
892 use crate::{Frequency, RRule};
893
894 let naive_date = NaiveDate::from_ymd_opt(2026, 3, 30).unwrap();
896 let event = Event::new()
897 .starts(naive_date)
898 .recurrence(RRule::default().count(3).freq(Frequency::Daily))
899 .unwrap()
900 .done();
901
902 let serialized = event.to_string();
903
904 assert!(!serialized.contains("RDATE:"));
906 assert!(!serialized.contains("EXDATE:"));
907 }
908}