• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lennart-k / rustical / #175

24 Jan 2026 09:48PM UTC coverage: 51.134%. Remained the same
#175

push

lennart-k
Add truncation for automatically derived timezones

2 of 3 new or added lines in 3 files covered. (66.67%)

2547 of 4981 relevant lines covered (51.13%)

2.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

23.73
/crates/ical/src/address_object.rs
1
use crate::{CalendarObject, Error};
2
use caldata::{
3
    VcardParser,
4
    component::{
5
        CalendarInnerDataBuilder, ComponentMut, IcalAlarmBuilder, IcalCalendarObjectBuilder,
6
        IcalEventBuilder, VcardContact,
7
    },
8
    generator::Emitter,
9
    parser::{ContentLine, ParserOptions},
10
    property::{
11
        Calscale, IcalCALSCALEProperty, IcalDTENDProperty, IcalDTSTAMPProperty,
12
        IcalDTSTARTProperty, IcalPRODIDProperty, IcalRRULEProperty, IcalSUMMARYProperty,
13
        IcalUIDProperty, IcalVERSIONProperty, IcalVersion, VcardANNIVERSARYProperty,
14
        VcardBDAYProperty, VcardFNProperty,
15
    },
16
    types::{CalDate, PartialDate, Timezone},
17
};
18
use chrono::{NaiveDate, Utc};
19
use sha2::{Digest, Sha256};
20
use std::collections::BTreeMap;
21
use std::str::FromStr;
22

23
#[derive(Debug, Clone)]
24
pub struct AddressObject {
25
    vcf: String,
26
    vcard: VcardContact,
27
}
28

29
impl From<VcardContact> for AddressObject {
30
    fn from(vcard: VcardContact) -> Self {
1✔
31
        let vcf = vcard.generate();
1✔
32
        Self { vcf, vcard }
33
    }
34
}
35

36
impl AddressObject {
37
    pub fn from_vcf(vcf: String) -> Result<Self, Error> {
4✔
38
        let parser = VcardParser::from_slice(vcf.as_bytes());
8✔
39
        let vcard = parser.expect_one()?;
4✔
40
        Ok(Self { vcf, vcard })
2✔
41
    }
42

43
    #[must_use]
44
    pub fn get_etag(&self) -> String {
1✔
45
        let mut hasher = Sha256::new();
1✔
46
        hasher.update(self.get_vcf());
1✔
47
        format!("\"{:x}\"", hasher.finalize())
1✔
48
    }
49

50
    #[must_use]
51
    pub fn get_vcf(&self) -> &str {
1✔
52
        &self.vcf
1✔
53
    }
54

55
    fn get_significant_date_object(
×
56
        &self,
57
        date: &PartialDate,
58
        summary_prefix: &str,
59
        suffix: &str,
60
    ) -> Result<Option<CalendarObject>, Error> {
61
        let Some(uid) = self.vcard.get_uid() else {
×
62
            return Ok(None);
×
63
        };
64
        let uid = format!("{uid}{suffix}");
×
65
        let year = date.get_year();
×
66
        let year_suffix = year.map(|year| format!(" {year}")).unwrap_or_default();
×
67
        let Some(month) = date.get_month() else {
×
68
            return Ok(None);
×
69
        };
70
        let Some(day) = date.get_day() else {
×
71
            return Ok(None);
×
72
        };
73
        let Some(dtstart) = NaiveDate::from_ymd_opt(year.unwrap_or(1900), month, day) else {
×
74
            return Ok(None);
×
75
        };
76
        let start_date = CalDate(dtstart, Timezone::Local);
×
77
        let Some(end_date) = start_date.succ_opt() else {
×
78
            // start_date is MAX_DATE, this should never happen but FAPP also not raise an error
79
            return Ok(None);
×
80
        };
81
        let Some(VcardFNProperty(fullname, _)) = self.vcard.full_name.first() else {
×
82
            return Ok(None);
×
83
        };
84
        let summary = format!("{summary_prefix} {fullname}{year_suffix}");
×
85

86
        let event = IcalEventBuilder {
87
            properties: vec![
×
88
                IcalDTSTAMPProperty(Utc::now().into(), vec![].into()).into(),
89
                IcalDTSTARTProperty(start_date.into(), vec![].into()).into(),
90
                IcalDTENDProperty(end_date.into(), vec![].into()).into(),
91
                IcalUIDProperty(uid, vec![].into()).into(),
92
                IcalRRULEProperty(
93
                    rrule::RRule::from_str("FREQ=YEARLY").unwrap(),
94
                    vec![].into(),
95
                )
96
                .into(),
97
                IcalSUMMARYProperty(summary.clone(), vec![].into()).into(),
98
                ContentLine {
99
                    name: "TRANSP".to_owned(),
100
                    value: Some("TRANSPARENT".to_owned()),
101
                    ..Default::default()
102
                },
103
            ],
104
            alarms: vec![IcalAlarmBuilder {
×
105
                properties: vec![
106
                    ContentLine {
107
                        name: "TRIGGER".to_owned(),
108
                        value: Some("-PT0M".to_owned()),
109
                        params: vec![("VALUE".to_owned(), vec!["DURATION".to_owned()])].into(),
110
                    },
111
                    ContentLine {
112
                        name: "ACTION".to_owned(),
113
                        value: Some("DISPLAY".to_owned()),
114
                        ..Default::default()
115
                    },
116
                    ContentLine {
117
                        name: "DESCRIPTION".to_owned(),
118
                        value: Some(summary),
119
                        ..Default::default()
120
                    },
121
                ],
122
            }],
123
        };
124

125
        Ok(Some(
×
126
            IcalCalendarObjectBuilder {
×
127
                properties: vec![
×
128
                    IcalVERSIONProperty(IcalVersion::Version2_0, vec![].into()).into(),
×
129
                    IcalCALSCALEProperty(Calscale::Gregorian, vec![].into()).into(),
×
130
                    IcalPRODIDProperty(
×
131
                        "-//github.com/lennart-k/rustical birthday calendar//EN".to_owned(),
×
132
                        vec![].into(),
×
133
                    )
134
                    .into(),
×
135
                ],
136
                inner: Some(CalendarInnerDataBuilder::Event(vec![event])),
×
137
                vtimezones: BTreeMap::default(),
×
138
            }
NEW
139
            .build(&ParserOptions::default(), None)?
×
140
            .into(),
×
141
        ))
142
    }
143

144
    pub fn get_anniversary_object(&self) -> Result<Option<CalendarObject>, Error> {
×
145
        let Some(VcardANNIVERSARYProperty(anniversary, _)) = &self.vcard.anniversary else {
×
146
            return Ok(None);
×
147
        };
148
        let Some(date) = &anniversary.date else {
×
149
            return Ok(None);
×
150
        };
151

152
        self.get_significant_date_object(date, "💍", "-anniversary")
×
153
    }
154

155
    pub fn get_birthday_object(&self) -> Result<Option<CalendarObject>, Error> {
×
156
        let Some(VcardBDAYProperty(bday, _)) = &self.vcard.birthday else {
×
157
            return Ok(None);
×
158
        };
159
        let Some(date) = &bday.date else {
×
160
            return Ok(None);
×
161
        };
162

163
        self.get_significant_date_object(date, "🎂", "-birthday")
×
164
    }
165

166
    #[must_use]
167
    pub const fn get_vcard(&self) -> &VcardContact {
1✔
168
        &self.vcard
3✔
169
    }
170
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc