1
use crate::parser::property::{
2
    prop_comment, prop_date_time_start, prop_iana, prop_last_modified, prop_recurrence_date_times,
3
    prop_recurrence_rule, prop_time_zone_id, prop_time_zone_name, prop_time_zone_offset_from,
4
    prop_time_zone_offset_to, prop_time_zone_url, prop_x,
5
};
6
use crate::parser::types::CalendarComponent;
7
use crate::parser::types::ComponentProperty;
8
use crate::parser::Error;
9
use nom::branch::alt;
10
use nom::bytes::streaming::tag;
11
use nom::combinator::cut;
12
use nom::error::ParseError;
13
use nom::multi::many0;
14
use nom::sequence::tuple;
15
use nom::IResult;
16
use nom::Parser;
17

            
18
#[derive(Debug, PartialEq)]
19
enum PropertyOrComponent<'a> {
20
    Property(ComponentProperty<'a>),
21
    Component(CalendarComponent<'a>),
22
}
23

            
24
296
pub fn component_timezone<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], CalendarComponent<'a>, E>
25
296
where
26
296
    E: ParseError<&'a [u8]>
27
296
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
28
296
        + From<Error<'a>>,
29
296
{
30
296
    let (input, (_, properties, _)) = tuple((
31
296
        tag("BEGIN:VTIMEZONE\r\n"),
32
296
        cut(many0(alt((
33
296
            alt((
34
296
                prop_time_zone_id
35
296
                    .map(ComponentProperty::TimeZoneId)
36
296
                    .map(PropertyOrComponent::Property),
37
296
                prop_last_modified
38
296
                    .map(ComponentProperty::LastModified)
39
296
                    .map(PropertyOrComponent::Property),
40
296
                prop_time_zone_url
41
296
                    .map(ComponentProperty::TimeZoneUrl)
42
296
                    .map(PropertyOrComponent::Property),
43
296
                component_standard.map(PropertyOrComponent::Component),
44
296
                component_daylight.map(PropertyOrComponent::Component),
45
296
            )),
46
296
            prop_x
47
296
                .map(ComponentProperty::XProperty)
48
296
                .map(PropertyOrComponent::Property),
49
296
            prop_iana
50
296
                .map(ComponentProperty::IanaProperty)
51
296
                .map(PropertyOrComponent::Property),
52
296
        )))),
53
296
        tag("END:VTIMEZONE\r\n"),
54
296
    ))(input)?;
55

            
56
33
    let (properties, components): (Vec<PropertyOrComponent>, Vec<PropertyOrComponent>) = properties
57
33
        .into_iter()
58
131
        .partition(|p| matches!(p, PropertyOrComponent::Property(_)));
59
33

            
60
33
    Ok((
61
33
        input,
62
33
        CalendarComponent::TimeZone {
63
33
            properties: properties
64
33
                .into_iter()
65
65
                .map(|p| match p {
66
65
                    PropertyOrComponent::Property(p) => p,
67
                    _ => unreachable!(),
68
65
                })
69
33
                .collect(),
70
33
            components: components
71
33
                .into_iter()
72
66
                .map(|c| match c {
73
66
                    PropertyOrComponent::Component(c) => c,
74
                    _ => unreachable!(),
75
66
                })
76
33
                .collect(),
77
33
        },
78
33
    ))
79
296
}
80

            
81
105
pub fn component_standard<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], CalendarComponent<'a>, E>
82
105
where
83
105
    E: ParseError<&'a [u8]>
84
105
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
85
105
        + From<Error<'a>>,
86
105
{
87
105
    let (input, (_, properties, _)) = tuple((
88
105
        tag("BEGIN:STANDARD\r\n"),
89
105
        cut(tz_props),
90
105
        tag("END:STANDARD\r\n"),
91
105
    ))(input)?;
92

            
93
33
    Ok((input, CalendarComponent::Standard { properties }))
94
105
}
95

            
96
72
pub fn component_daylight<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], CalendarComponent<'a>, E>
97
72
where
98
72
    E: ParseError<&'a [u8]>
99
72
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
100
72
        + From<Error<'a>>,
101
72
{
102
72
    let (input, (_, properties, _)) = tuple((
103
72
        tag("BEGIN:DAYLIGHT\r\n"),
104
72
        cut(tz_props),
105
72
        tag("END:DAYLIGHT\r\n"),
106
72
    ))(input)?;
107

            
108
33
    Ok((input, CalendarComponent::Daylight { properties }))
109
72
}
110

            
111
66
fn tz_props<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<ComponentProperty<'a>>, E>
112
66
where
113
66
    E: ParseError<&'a [u8]>
114
66
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
115
66
        + From<Error<'a>>,
116
66
{
117
66
    many0(alt((
118
66
        alt((
119
66
            prop_date_time_start.map(ComponentProperty::DateTimeStart),
120
66
            prop_time_zone_offset_to.map(ComponentProperty::TimeZoneOffsetTo),
121
66
            prop_time_zone_offset_from.map(ComponentProperty::TimeZoneOffsetFrom),
122
66
            prop_recurrence_rule.map(ComponentProperty::RecurrenceRule),
123
66
            prop_comment.map(ComponentProperty::Comment),
124
66
            prop_recurrence_date_times.map(ComponentProperty::RecurrenceDateTimes),
125
66
            prop_time_zone_name.map(ComponentProperty::TimeZoneName),
126
66
        )),
127
66
        prop_x.map(ComponentProperty::XProperty),
128
66
        prop_iana.map(ComponentProperty::IanaProperty),
129
66
    )))(input)
130
66
}
131

            
132
#[cfg(test)]
133
pub mod tests {
134
    use super::*;
135
    use crate::parser::types::{
136
        Date, DateOrDateTime, DateTime, DateTimeStartProperty, LastModifiedProperty, Time,
137
        TimeZoneIdProperty, TimeZoneNameProperty, TimeZoneOffsetProperty, UtcOffset,
138
    };
139
    use crate::parser::Error;
140
    use crate::test_utils::check_rem;
141

            
142
    #[test]
143
2
    fn test_component_timezone() {
144
2
        let input = b"BEGIN:VTIMEZONE\r\n\
145
2
TZID:America/New_York\r\n\
146
2
LAST-MODIFIED:20050809T050000Z\r\n\
147
2
BEGIN:STANDARD\r\n\
148
2
DTSTART:20071104T020000\r\n\
149
2
TZOFFSETFROM:-0400\r\n\
150
2
TZOFFSETTO:-0500\r\n\
151
2
TZNAME:EST\r\n\
152
2
END:STANDARD\r\n\
153
2
BEGIN:DAYLIGHT\r\n\
154
2
DTSTART:20070311T020000\r\n\
155
2
TZOFFSETFROM:-0500\r\n\
156
2
TZOFFSETTO:-0400\r\n\
157
2
TZNAME:EDT\r\n\
158
2
END:DAYLIGHT\r\n\
159
2
END:VTIMEZONE\r\n";
160
2

            
161
2
        let (rem, component) = component_timezone::<Error>(input).unwrap();
162
2
        check_rem(rem, 0);
163
2

            
164
2
        match component {
165
            CalendarComponent::TimeZone {
166
2
                properties,
167
2
                components,
168
2
            } => {
169
2
                assert_eq!(properties.len(), 2);
170
2
                assert_eq!(components.len(), 2);
171

            
172
2
                assert_eq!(
173
2
                    properties[0],
174
2
                    ComponentProperty::TimeZoneId(TimeZoneIdProperty {
175
2
                        other_params: vec![],
176
2
                        unique_registry_id: false,
177
2
                        value: b"America/New_York".to_vec(),
178
2
                    })
179
2
                );
180

            
181
2
                assert_eq!(
182
2
                    properties[1],
183
2
                    ComponentProperty::LastModified(LastModifiedProperty {
184
2
                        other_params: vec![],
185
2
                        value: DateTime {
186
2
                            date: Date {
187
2
                                year: 2005,
188
2
                                month: 8,
189
2
                                day: 9,
190
2
                            },
191
2
                            time: Time {
192
2
                                hour: 5,
193
2
                                minute: 0,
194
2
                                second: 0,
195
2
                                is_utc: true,
196
2
                            },
197
2
                        }
198
2
                    })
199
2
                );
200

            
201
2
                match &components[0] {
202
2
                    CalendarComponent::Standard { properties } => {
203
2
                        assert_eq!(properties.len(), 4);
204

            
205
2
                        assert_eq!(
206
2
                            properties[0],
207
2
                            ComponentProperty::DateTimeStart(DateTimeStartProperty {
208
2
                                params: vec![],
209
2
                                value: DateOrDateTime::DateTime(DateTime {
210
2
                                    date: Date {
211
2
                                        year: 2007,
212
2
                                        month: 11,
213
2
                                        day: 4,
214
2
                                    },
215
2
                                    time: Time {
216
2
                                        hour: 2,
217
2
                                        minute: 0,
218
2
                                        second: 0,
219
2
                                        is_utc: false,
220
2
                                    },
221
2
                                })
222
2
                            })
223
2
                        );
224

            
225
2
                        assert_eq!(
226
2
                            properties[1],
227
2
                            ComponentProperty::TimeZoneOffsetFrom(TimeZoneOffsetProperty {
228
2
                                other_params: vec![],
229
2
                                value: UtcOffset {
230
2
                                    sign: -1,
231
2
                                    hours: 4,
232
2
                                    minutes: 0,
233
2
                                    seconds: None,
234
2
                                }
235
2
                            })
236
2
                        );
237

            
238
2
                        assert_eq!(
239
2
                            properties[2],
240
2
                            ComponentProperty::TimeZoneOffsetTo(TimeZoneOffsetProperty {
241
2
                                other_params: vec![],
242
2
                                value: UtcOffset {
243
2
                                    sign: -1,
244
2
                                    hours: 5,
245
2
                                    minutes: 0,
246
2
                                    seconds: None,
247
2
                                }
248
2
                            })
249
2
                        );
250

            
251
2
                        assert_eq!(
252
2
                            properties[3],
253
2
                            ComponentProperty::TimeZoneName(TimeZoneNameProperty {
254
2
                                params: vec![],
255
2
                                value: b"EST".to_vec(),
256
2
                            })
257
2
                        );
258
                    }
259
                    _ => panic!("Unexpected component type"),
260
                }
261

            
262
2
                match &components[1] {
263
2
                    CalendarComponent::Daylight { properties } => {
264
2
                        assert_eq!(properties.len(), 4);
265

            
266
2
                        assert_eq!(
267
2
                            properties[0],
268
2
                            ComponentProperty::DateTimeStart(DateTimeStartProperty {
269
2
                                params: vec![],
270
2
                                value: DateOrDateTime::DateTime(DateTime {
271
2
                                    date: Date {
272
2
                                        year: 2007,
273
2
                                        month: 3,
274
2
                                        day: 11,
275
2
                                    },
276
2
                                    time: Time {
277
2
                                        hour: 2,
278
2
                                        minute: 0,
279
2
                                        second: 0,
280
2
                                        is_utc: false,
281
2
                                    },
282
2
                                })
283
2
                            })
284
2
                        );
285

            
286
2
                        assert_eq!(
287
2
                            properties[1],
288
2
                            ComponentProperty::TimeZoneOffsetFrom(TimeZoneOffsetProperty {
289
2
                                other_params: vec![],
290
2
                                value: UtcOffset {
291
2
                                    sign: -1,
292
2
                                    hours: 5,
293
2
                                    minutes: 0,
294
2
                                    seconds: None,
295
2
                                }
296
2
                            })
297
2
                        );
298

            
299
2
                        assert_eq!(
300
2
                            properties[2],
301
2
                            ComponentProperty::TimeZoneOffsetTo(TimeZoneOffsetProperty {
302
2
                                other_params: vec![],
303
2
                                value: UtcOffset {
304
2
                                    sign: -1,
305
2
                                    hours: 4,
306
2
                                    minutes: 0,
307
2
                                    seconds: None,
308
2
                                }
309
2
                            })
310
2
                        );
311

            
312
2
                        assert_eq!(
313
2
                            properties[3],
314
2
                            ComponentProperty::TimeZoneName(TimeZoneNameProperty {
315
2
                                params: vec![],
316
2
                                value: b"EDT".to_vec(),
317
2
                            })
318
2
                        );
319
                    }
320
                    _ => panic!("Unexpected component type"),
321
                }
322
            }
323
            _ => panic!("Unexpected component type"),
324
        }
325
2
    }
326
}