Fix birthdays without year in birthday calendar

Fixes #79
This commit is contained in:
Lennart
2025-06-23 16:03:59 +02:00
parent c646986c56
commit 6e0129130e
2 changed files with 76 additions and 58 deletions

View File

@@ -62,14 +62,14 @@ impl AddressObject {
&self.vcf &self.vcf
} }
pub fn get_anniversary(&self) -> Option<CalDateTime> { pub fn get_anniversary(&self) -> Option<(CalDateTime, bool)> {
let prop = self.vcard.get_property("ANNIVERSARY")?; let prop = self.vcard.get_property("ANNIVERSARY")?.value.as_deref()?;
CalDateTime::parse_prop(prop, &HashMap::default()).ok() CalDateTime::parse_vcard(prop).ok()
} }
pub fn get_birthday(&self) -> Option<CalDateTime> { pub fn get_birthday(&self) -> Option<(CalDateTime, bool)> {
let prop = self.vcard.get_property("BDAY")?; let prop = self.vcard.get_property("BDAY")?.value.as_deref()?;
CalDateTime::parse_prop(prop, &HashMap::default()).ok() CalDateTime::parse_vcard(prop).ok()
} }
pub fn get_full_name(&self) -> Option<&str> { pub fn get_full_name(&self) -> Option<&str> {
@@ -78,14 +78,15 @@ impl AddressObject {
} }
pub fn get_anniversary_object(&self) -> Result<Option<CalendarObject>, Error> { pub fn get_anniversary_object(&self) -> Result<Option<CalendarObject>, Error> {
Ok(if let Some(anniversary) = self.get_anniversary() { Ok(
if let Some((anniversary, contains_year)) = self.get_anniversary() {
let fullname = if let Some(name) = self.get_full_name() { let fullname = if let Some(name) = self.get_full_name() {
name name
} else { } else {
return Ok(None); return Ok(None);
}; };
let anniversary = anniversary.date(); let anniversary = anniversary.date();
let year = anniversary.year(); let year = contains_year.then_some(anniversary.year());
let anniversary_start = anniversary.format(LOCAL_DATE); let anniversary_start = anniversary.format(LOCAL_DATE);
let anniversary_end = anniversary let anniversary_end = anniversary
.succ_opt() .succ_opt()
@@ -93,6 +94,7 @@ impl AddressObject {
.format(LOCAL_DATE); .format(LOCAL_DATE);
let uid = format!("{}-anniversary", self.get_id()); let uid = format!("{}-anniversary", self.get_id());
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
Some(CalendarObject::from_ics( Some(CalendarObject::from_ics(
uid.clone(), uid.clone(),
format!( format!(
@@ -105,12 +107,12 @@ DTSTART;VALUE=DATE:{anniversary_start}
DTEND;VALUE=DATE:{anniversary_end} DTEND;VALUE=DATE:{anniversary_end}
UID:{uid} UID:{uid}
RRULE:FREQ=YEARLY RRULE:FREQ=YEARLY
SUMMARY:💍 {fullname} ({year}) SUMMARY:💍 {fullname}{year_suffix}
TRANSP:TRANSPARENT TRANSP:TRANSPARENT
BEGIN:VALARM BEGIN:VALARM
TRIGGER;VALUE=DURATION:-PT0M TRIGGER;VALUE=DURATION:-PT0M
ACTION:DISPLAY ACTION:DISPLAY
DESCRIPTION:💍 {fullname} ({year}) DESCRIPTION:💍 {fullname}{year_suffix}
END:VALARM END:VALARM
END:VEVENT END:VEVENT
END:VCALENDAR"#, END:VCALENDAR"#,
@@ -118,22 +120,25 @@ END:VCALENDAR"#,
)?) )?)
} else { } else {
None None
}) },
)
} }
pub fn get_birthday_object(&self) -> Result<Option<CalendarObject>, Error> { pub fn get_birthday_object(&self) -> Result<Option<CalendarObject>, Error> {
Ok(if let Some(birthday) = self.get_birthday() { Ok(
if let Some((birthday, contains_year)) = self.get_birthday() {
let fullname = if let Some(name) = self.get_full_name() { let fullname = if let Some(name) = self.get_full_name() {
name name
} else { } else {
return Ok(None); return Ok(None);
}; };
let birthday = birthday.date(); let birthday = birthday.date();
let year = birthday.year(); let year = contains_year.then_some(birthday.year());
let birthday_start = birthday.format(LOCAL_DATE); let birthday_start = birthday.format(LOCAL_DATE);
let birthday_end = birthday.succ_opt().unwrap_or(birthday).format(LOCAL_DATE); let birthday_end = birthday.succ_opt().unwrap_or(birthday).format(LOCAL_DATE);
let uid = format!("{}-birthday", self.get_id()); let uid = format!("{}-birthday", self.get_id());
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
Some(CalendarObject::from_ics( Some(CalendarObject::from_ics(
uid.clone(), uid.clone(),
format!( format!(
@@ -146,12 +151,12 @@ DTSTART;VALUE=DATE:{birthday_start}
DTEND;VALUE=DATE:{birthday_end} DTEND;VALUE=DATE:{birthday_end}
UID:{uid} UID:{uid}
RRULE:FREQ=YEARLY RRULE:FREQ=YEARLY
SUMMARY:🎂 {fullname} ({year}) SUMMARY:🎂 {fullname}{year_suffix}
TRANSP:TRANSPARENT TRANSP:TRANSPARENT
BEGIN:VALARM BEGIN:VALARM
TRIGGER;VALUE=DURATION:-PT0M TRIGGER;VALUE=DURATION:-PT0M
ACTION:DISPLAY ACTION:DISPLAY
DESCRIPTION:🎂 {fullname} ({year}) DESCRIPTION:🎂 {fullname}{year_suffix}
END:VALARM END:VALARM
END:VEVENT END:VEVENT
END:VCALENDAR"#, END:VCALENDAR"#,
@@ -159,7 +164,8 @@ END:VCALENDAR"#,
)?) )?)
} else { } else {
None None
}) },
)
} }
/// Get significant dates associated with this address object /// Get significant dates associated with this address object

View File

@@ -254,6 +254,16 @@ impl CalDateTime {
if let Ok(date) = NaiveDate::parse_from_str(value, "%Y%m%d") { if let Ok(date) = NaiveDate::parse_from_str(value, "%Y%m%d") {
return Ok(CalDateTime::Date(date, timezone)); return Ok(CalDateTime::Date(date, timezone));
} }
Err(CalDateTimeError::InvalidDatetimeFormat(value.to_string()))
}
// Also returns whether the date contains a year
pub fn parse_vcard(value: &str) -> Result<(Self, bool), CalDateTimeError> {
if let Ok(datetime) = Self::parse(value, None) {
return Ok((datetime, true));
}
if let Some(captures) = RE_VCARD_DATE_MM_DD.captures(value) { if let Some(captures) = RE_VCARD_DATE_MM_DD.captures(value) {
// Because 1972 is a leap year // Because 1972 is a leap year
let year = 1972; let year = 1972;
@@ -261,13 +271,15 @@ impl CalDateTime {
let month = captures.name("m").unwrap().as_str().parse().ok().unwrap(); let month = captures.name("m").unwrap().as_str().parse().ok().unwrap();
let day = captures.name("d").unwrap().as_str().parse().ok().unwrap(); let day = captures.name("d").unwrap().as_str().parse().ok().unwrap();
return Ok(CalDateTime::Date( return Ok((
CalDateTime::Date(
NaiveDate::from_ymd_opt(year, month, day) NaiveDate::from_ymd_opt(year, month, day)
.ok_or(CalDateTimeError::ParseError(value.to_string()))?, .ok_or(CalDateTimeError::ParseError(value.to_string()))?,
timezone, CalTimezone::Local,
),
false,
)); ));
} }
Err(CalDateTimeError::InvalidDatetimeFormat(value.to_string())) Err(CalDateTimeError::InvalidDatetimeFormat(value.to_string()))
} }