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

lennart-k / ical-rs / #73

12 Jan 2026 01:50PM UTC coverage: 77.771% (-0.03%) from 77.802%
#73

Pull #2

lennart-k
IcalCalendarObjectBuilder: Make attributes public
Pull Request #2: Major overhaul

908 of 1184 new or added lines in 30 files covered. (76.69%)

32 existing lines in 10 files now uncovered.

1221 of 1570 relevant lines covered (77.77%)

2.77 hits per line

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

80.95
/src/types/date.rs
1
use crate::types::{CalDateTimeError, Timezone, Value};
2
use crate::{property::ContentLine, types::CalDateTime};
3
use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveTime};
4
use chrono_tz::Tz;
5
use std::{collections::HashMap, ops::Add};
6

7
pub const LOCAL_DATE: &str = "%Y%m%d";
8

9
#[derive(Debug, Clone, PartialEq, Eq)]
10
pub struct CalDate(pub NaiveDate, pub Timezone);
11

12
impl PartialOrd for CalDate {
13
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1✔
14
        Some(self.cmp(other))
1✔
15
    }
16
}
17

18
impl Ord for CalDate {
19
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1✔
20
        self.as_datetime().cmp(&other.as_datetime())
1✔
21
    }
22
}
23

24
impl Add<Duration> for CalDate {
25
    type Output = CalDateTime;
26

27
    fn add(self, duration: Duration) -> Self::Output {
1✔
28
        (self
1✔
29
            .0
30
            .and_time(NaiveTime::default())
1✔
31
            .and_local_timezone(self.1)
1✔
32
            .earliest()
1✔
33
            .expect("Local timezone has constant offset")
1✔
34
            + duration)
35
            .into()
36
    }
37
}
38

39
impl CalDate {
40
    pub fn parse_prop(
2✔
41
        prop: &ContentLine,
42
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
43
    ) -> Result<Self, CalDateTimeError> {
44
        let prop_value = prop
4✔
45
            .value
46
            .as_ref()
47
            .ok_or_else(|| CalDateTimeError::InvalidDatetimeFormat("empty property".into()))?;
2✔
48

49
        let timezone = if let Some(tzid) = prop.params.get_tzid() {
4✔
NEW
50
            if let Some(timezone) = timezones.and_then(|timezones| timezones.get(tzid)) {
×
NEW
51
                timezone.to_owned()
×
52
            } else {
53
                // TZID refers to timezone that does not exist
NEW
54
                return Err(CalDateTimeError::InvalidTZID(tzid.to_string()));
×
55
            }
56
        } else {
57
            // No explicit timezone specified.
58
            // This is valid and will be localtime or UTC depending on the value
59
            // We will stick to this default as documented in https://github.com/lennart-k/rustical/issues/102
60
            None
2✔
61
        };
62

63
        Self::parse(prop_value, timezone)
2✔
64
    }
65

66
    #[must_use]
67
    pub fn naive_date(&self) -> &NaiveDate {
1✔
68
        &self.0
69
    }
70

71
    #[must_use]
72
    pub fn format(&self) -> String {
2✔
73
        self.0.format(LOCAL_DATE).to_string()
2✔
74
    }
75

76
    #[must_use]
77
    pub fn as_datetime(&self) -> DateTime<Timezone> {
1✔
78
        self.0
79
            .and_time(NaiveTime::default())
1✔
80
            .and_local_timezone(self.1.to_owned())
1✔
81
            .earliest()
82
            .expect("Midnight always exists")
83
    }
84

85
    pub fn parse(value: &str, timezone: Option<Tz>) -> Result<Self, CalDateTimeError> {
2✔
86
        let timezone = timezone.map_or(Timezone::Local, Timezone::Olson);
2✔
87
        if let Ok(date) = NaiveDate::parse_from_str(value, LOCAL_DATE) {
4✔
88
            return Ok(Self(date, timezone));
2✔
89
        }
NEW
90
        Err(CalDateTimeError::InvalidDatetimeFormat(value.to_string()))
×
91
    }
92

93
    #[must_use]
NEW
94
    pub fn timezone(&self) -> &Timezone {
×
NEW
95
        &self.1
×
96
    }
97

98
    #[must_use]
NEW
99
    pub fn succ_opt(&self) -> Option<Self> {
×
NEW
100
        Some(Self(self.0.succ_opt()?, self.1.clone()))
×
101
    }
102
}
103

104
#[cfg(not(tarpaulin_include))]
105
impl Datelike for CalDate {
106
    fn year(&self) -> i32 {
107
        self.0.year()
108
    }
109
    fn month(&self) -> u32 {
110
        self.0.month()
111
    }
112

113
    fn month0(&self) -> u32 {
114
        self.0.month0()
115
    }
116
    fn day(&self) -> u32 {
117
        self.0.day()
118
    }
119
    fn day0(&self) -> u32 {
120
        self.0.day0()
121
    }
122
    fn ordinal(&self) -> u32 {
123
        self.0.ordinal()
124
    }
125
    fn ordinal0(&self) -> u32 {
126
        self.0.ordinal0()
127
    }
128
    fn weekday(&self) -> chrono::Weekday {
129
        self.0.weekday()
130
    }
131
    fn iso_week(&self) -> chrono::IsoWeek {
132
        self.0.iso_week()
133
    }
134
    fn with_year(&self, year: i32) -> Option<Self> {
135
        Some(Self(self.0.with_year(year)?, self.1.to_owned()))
136
    }
137
    fn with_month(&self, month: u32) -> Option<Self> {
138
        Some(Self(self.0.with_month(month)?, self.1.to_owned()))
139
    }
140
    fn with_month0(&self, month0: u32) -> Option<Self> {
141
        Some(Self(self.0.with_month0(month0)?, self.1.to_owned()))
142
    }
143
    fn with_day(&self, day: u32) -> Option<Self> {
144
        Some(Self(self.0.with_day(day)?, self.1.to_owned()))
145
    }
146
    fn with_day0(&self, day0: u32) -> Option<Self> {
147
        Some(Self(self.0.with_day0(day0)?, self.1.to_owned()))
148
    }
149
    fn with_ordinal(&self, ordinal: u32) -> Option<Self> {
150
        Some(Self(self.0.with_ordinal(ordinal)?, self.1.to_owned()))
151
    }
152
    fn with_ordinal0(&self, ordinal0: u32) -> Option<Self> {
153
        Some(Self(self.0.with_ordinal0(ordinal0)?, self.1.to_owned()))
154
    }
155
}
156

157
impl Value for CalDate {
158
    fn value_type(&self) -> Option<&'static str> {
2✔
159
        Some("DATE")
160
    }
161
    fn value(&self) -> String {
2✔
162
        self.format()
2✔
163
    }
164

165
    fn utc_or_local(self) -> Self {
2✔
166
        let tz = if self.1.is_local() {
2✔
167
            Timezone::Local
2✔
168
        } else {
169
            Timezone::utc()
1✔
170
        };
171
        Self(self.0, tz)
2✔
172
    }
173
}
174

175
#[cfg(test)]
176
mod tests {
177
    use crate::types::{CalDate, Value};
178
    use chrono::Duration;
179

180
    #[test]
181
    fn test_date() {
182
        let a = CalDate::parse("20121212", None).unwrap();
183
        let b = CalDate::parse("20121213", None).unwrap();
184
        let c = CalDate::parse("20121213", Some(chrono_tz::Europe::Berlin)).unwrap();
185
        let d = CalDate::parse("20121213", Some(chrono_tz::Europe::Kyiv)).unwrap();
186
        // Floating time and fixed time => different results
187
        assert_ne!(b.clone().utc_or_local(), c.clone().utc_or_local());
188
        // fixed timezones resolve to UTC
189
        assert_eq!(c.clone().utc_or_local(), d.utc_or_local());
190
        assert_eq!((a.clone() + Duration::days(1)).0, b.as_datetime());
191
        assert!(b > a);
192
        assert!(b >= a);
193
    }
194
}
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