use crate::event::{Availability, Event, Status};
use icalendar::{Component, EventLike};
impl From<&Event> for icalendar::Event {
fn from(value: &Event) -> Self {
let mut event = icalendar::Event::new();
event.starts(icalendar::DatePerhapsTime::from(&value.start));
event.uid(value.uid.as_str());
if let Some(end) = &value.end {
event.ends(icalendar::DatePerhapsTime::from(end));
}
if value.status != Status::default() {
event.append_property(icalendar::Property::new(
"STATUS",
value.status.as_ics_str(),
));
}
if value.availability != Availability::default() {
event.append_property(icalendar::Property::new(
"TRANSP",
value.availability.as_ics_str(),
));
}
if let Some(visibility) = value.visibility {
event.append_property(icalendar::Property::new("CLASS", visibility.as_ics_str()));
}
if let Some(recurrence) = &value.recurrence {
recurrence.apply_to(&mut event);
}
if let Some(recurrence_id) = &value.recurrence_id {
event.recurrence_id(icalendar::DatePerhapsTime::from(
recurrence_id.as_event_time(),
));
}
if let Some(summary) = &value.summary {
event.summary(summary);
}
if let Some(description) = &value.description {
event.description(description);
}
if let Some(location) = &value.location {
event.location(location);
}
if let Some(last_modified) = value.last_modified {
event.last_modified(last_modified);
}
if value.sequence != 0 {
event.append_property(icalendar::Property::new(
"SEQUENCE",
value.sequence.to_string(),
));
}
if let Some(organizer) = &value.organizer {
event.append_property(icalendar::Property::from(organizer));
}
for attendee in &value.attendees {
event.append_multi_property(icalendar::Property::from(attendee));
}
if let Some(url) = &value.url {
event.append_property(icalendar::Property::new("URL", url));
}
for attachment in &value.attachments {
event.append_multi_property(icalendar::Property::from(attachment));
}
for x in &value.x_properties {
event.append_property(icalendar::Property::from(x));
}
event.done()
}
}
impl From<Event> for icalendar::Event {
fn from(value: Event) -> Self {
icalendar::Event::from(&value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::EventTime;
use crate::event::{EventUid, RecurrenceId, Visibility};
use crate::test_utils::test_event;
#[test]
fn converts_summary() {
let mut event = test_event();
event.summary = Some("Hello world".to_string());
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.get_summary(), Some("Hello world"));
}
#[test]
fn converts_location() {
let mut event = test_event();
event.location = Some("New York".to_string());
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.get_location(), Some("New York"));
}
#[test]
fn converts_uid() {
let mut event = test_event();
event.uid = EventUid::new("abc123@hooli.com".to_string());
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.get_uid(), Some("abc123@hooli.com"));
}
#[test]
fn converts_description() {
let mut event = test_event();
event.description = Some("Multi-line\nnotes".to_string());
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.get_description(), Some("Multi-line\nnotes"));
}
#[test]
fn converts_start() {
let mut event = test_event();
event.start = EventTime::Date(chrono::NaiveDate::from_ymd_opt(2026, 10, 10).unwrap());
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.get_start(),
Some(icalendar::DatePerhapsTime::Date(
chrono::NaiveDate::from_ymd_opt(2026, 10, 10).unwrap()
))
);
}
#[test]
fn converts_end() {
let mut event = test_event();
event.end = Some(EventTime::Date(
chrono::NaiveDate::from_ymd_opt(2026, 10, 11).unwrap(),
));
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.get_end(),
Some(icalendar::DatePerhapsTime::Date(
chrono::NaiveDate::from_ymd_opt(2026, 10, 11).unwrap()
))
);
}
#[test]
fn omits_end_when_none() {
let mut event = test_event();
event.end = None;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.get_end(), None);
}
#[test]
fn converts_status() {
let mut event = test_event();
event.status = Status::Cancelled;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("STATUS"), Some("CANCELLED"));
}
#[test]
fn omits_status_when_default() {
let mut event = test_event();
event.status = Status::Confirmed;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("STATUS"), None);
}
#[test]
fn converts_availability() {
let mut event = test_event();
event.availability = Availability::Free;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("TRANSP"), Some("TRANSPARENT"));
}
#[test]
fn omits_availability_when_default() {
let mut event = test_event();
event.availability = Availability::Busy;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("TRANSP"), None);
}
#[test]
fn converts_visibility() {
let mut event = test_event();
event.visibility = Some(Visibility::Private);
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("CLASS"), Some("PRIVATE"));
}
#[test]
fn omits_visibility_when_unspecified() {
let mut event = test_event();
event.visibility = None;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("CLASS"), None);
}
#[test]
fn writes_explicit_public_visibility() {
let mut event = test_event();
event.visibility = Some(Visibility::Public);
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("CLASS"), Some("PUBLIC"));
}
#[test]
fn converts_recurrence() {
let mut event = test_event();
event.recurrence = Some(crate::event::Recurrence::new("FREQ=WEEKLY;BYDAY=MO"));
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.property_value("RRULE"),
Some("FREQ=WEEKLY;BYDAY=MO")
);
}
#[test]
fn omits_recurrence_when_none() {
let mut event = test_event();
event.recurrence = None;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("RRULE"), None);
}
#[test]
fn converts_recurrence_id() {
let mut event = test_event();
event.recurrence_id = Some(RecurrenceId::from_event_time(EventTime::Date(
chrono::NaiveDate::from_ymd_opt(2026, 5, 15).unwrap(),
)));
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.get_recurrence_id(),
Some(icalendar::DatePerhapsTime::Date(
chrono::NaiveDate::from_ymd_opt(2026, 5, 15).unwrap()
))
);
}
#[test]
fn omits_recurrence_id_when_none() {
let mut event = test_event();
event.recurrence_id = None;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.get_recurrence_id(), None);
}
#[test]
fn converts_last_modified() {
let last_modified = chrono::NaiveDate::from_ymd_opt(2026, 5, 2)
.unwrap()
.and_hms_opt(17, 39, 14)
.unwrap()
.and_utc();
let mut event = test_event();
event.last_modified = Some(last_modified);
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.property_value("LAST-MODIFIED"),
Some("20260502T173914Z")
);
}
#[test]
fn omits_last_modified_when_none() {
let mut event = test_event();
event.last_modified = None;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("LAST-MODIFIED"), None);
}
#[test]
fn converts_sequence() {
let mut event = test_event();
event.sequence = 3;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("SEQUENCE"), Some("3"));
}
#[test]
fn converts_negative_sequence() {
let mut event = test_event();
event.sequence = -1;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("SEQUENCE"), Some("-1"));
}
#[test]
fn omits_sequence_when_default() {
let mut event = test_event();
event.sequence = 0;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("SEQUENCE"), None);
}
#[test]
fn converts_organizer() {
let mut event = test_event();
event.organizer = Some(crate::event::Organizer::new("alice@example.com"));
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.property_value("ORGANIZER"),
Some("mailto:alice@example.com")
);
}
#[test]
fn omits_organizer_when_none() {
let mut event = test_event();
event.organizer = None;
let ical_event: icalendar::Event = event.into();
assert!(ical_event.properties().get("ORGANIZER").is_none());
}
#[test]
fn converts_attendees() {
let mut event = test_event();
event.attendees = vec![
crate::event::Attendee::new("bob@example.com"),
crate::event::Attendee::new("carol@example.com"),
];
let ical_event: icalendar::Event = event.into();
let attendees = ical_event
.multi_properties()
.get("ATTENDEE")
.expect("ATTENDEE multi-property should be present");
assert_eq!(
attendees.iter().map(|p| p.value()).collect::<Vec<_>>(),
vec!["mailto:bob@example.com", "mailto:carol@example.com"]
);
}
#[test]
fn omits_attendees_when_empty() {
let mut event = test_event();
event.attendees = vec![];
let ical_event: icalendar::Event = event.into();
assert!(ical_event.multi_properties().get("ATTENDEE").is_none());
}
#[test]
fn converts_url() {
let mut event = test_event();
event.url = Some("https://meet.example.com/abc-defg-hij".to_string());
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.property_value("URL"),
Some("https://meet.example.com/abc-defg-hij")
);
}
#[test]
fn omits_url_when_none() {
let mut event = test_event();
event.url = None;
let ical_event: icalendar::Event = event.into();
assert_eq!(ical_event.property_value("URL"), None);
}
#[test]
fn converts_attachments() {
let mut event = test_event();
event.attachments = vec![
crate::event::Attachment::new("https://drive.google.com/file/d/abc"),
crate::event::Attachment::new("https://example.com/report.doc"),
];
let ical_event: icalendar::Event = event.into();
let attachments = ical_event
.multi_properties()
.get("ATTACH")
.expect("ATTACH multi-property should be present");
assert_eq!(
attachments.iter().map(|p| p.value()).collect::<Vec<_>>(),
vec![
"https://drive.google.com/file/d/abc",
"https://example.com/report.doc"
]
);
}
#[test]
fn omits_attachments_when_empty() {
let mut event = test_event();
event.attachments = vec![];
let ical_event: icalendar::Event = event.into();
assert!(ical_event.multi_properties().get("ATTACH").is_none());
}
#[test]
fn converts_x_properties() {
let mut event = test_event();
event.x_properties = vec![crate::event::XProperty::new(
"X-HOOLI-EVENT-ID",
"abc123@hooli.com",
)];
let ical_event: icalendar::Event = event.into();
assert_eq!(
ical_event.property_value("X-HOOLI-EVENT-ID"),
Some("abc123@hooli.com")
);
}
#[test]
fn omits_x_properties_when_empty() {
let mut event = test_event();
event.x_properties = vec![];
let ical_event: icalendar::Event = event.into();
assert!(ical_event.properties().keys().all(|k| !k.starts_with("X-")));
}
}