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::IResult;
15
use nom::Parser;
16

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

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

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

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

            
110
33
    Ok((input, CalendarComponent::Daylight { properties }))
111
72
}
112

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

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

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

            
164
2
        let (rem, component) = component_timezone::<Error>(input).unwrap();
165
2
        check_rem(rem, 0);
166
2

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

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

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

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

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

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

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

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

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

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

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

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

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