1
use crate::model::property::Duration;
2
use std::cmp::Ordering;
3
use std::ops::{Add, Sub};
4

            
5
#[derive(Debug, Clone, Eq, PartialEq, Default)]
6
pub enum CalendarUserType {
7
    #[default]
8
    Individual,
9
    Group,
10
    Resource,
11
    Room,
12
    Unknown,
13
    XName(String),
14
    IanaToken(String),
15
}
16

            
17
#[derive(Debug, Clone, Eq, PartialEq, Default)]
18
pub enum Encoding {
19
    #[default]
20
    EightBit,
21
    Base64,
22
}
23

            
24
#[derive(Debug, Clone, Eq, PartialEq)]
25
pub enum FreeBusyTimeType {
26
    Free,
27
    Busy,
28
    BusyUnavailable,
29
    BusyTentative,
30
    XName(String),
31
    IanaToken(String),
32
}
33

            
34
#[derive(Debug, Clone, PartialEq, Eq)]
35
pub struct LanguageTag {
36
    pub language: String,
37
    pub ext_lang: Option<String>,
38
    pub script: Option<String>,
39
    pub region: Option<String>,
40
    pub variants: Vec<String>,
41
    pub extensions: Vec<String>,
42
    pub private_use: Option<String>,
43
}
44

            
45
impl Default for LanguageTag {
46
54
    fn default() -> Self {
47
54
        Self {
48
54
            language: String::new(),
49
54
            ext_lang: None,
50
54
            script: None,
51
54
            region: None,
52
54
            variants: Vec::with_capacity(0),
53
54
            extensions: Vec::with_capacity(0),
54
54
            private_use: None,
55
54
        }
56
54
    }
57
}
58

            
59
impl LanguageTag {
60
36
    pub fn new(language: &str) -> Self {
61
36
        Self {
62
36
            language: language.to_string(),
63
36
            ..Default::default()
64
36
        }
65
36
    }
66

            
67
12
    pub fn with_ext_lang(mut self, ext_lang: &str) -> Self {
68
12
        self.ext_lang = Some(ext_lang.to_string());
69
12
        self
70
12
    }
71

            
72
12
    pub fn with_script(mut self, script: &str) -> Self {
73
12
        self.script = Some(script.to_string());
74
12
        self
75
12
    }
76

            
77
36
    pub fn with_region(mut self, region: &str) -> Self {
78
36
        self.region = Some(region.to_string());
79
36
        self
80
36
    }
81

            
82
24
    pub fn add_variant(mut self, variant: &str) -> Self {
83
24
        self.variants.push(variant.to_string());
84
24
        self
85
24
    }
86

            
87
24
    pub fn add_extension(mut self, extension: &str) -> Self {
88
24
        self.extensions.push(extension.to_string());
89
24
        self
90
24
    }
91

            
92
12
    pub fn with_private_use(mut self, private_use: &str) -> Self {
93
12
        self.private_use = Some(private_use.to_string());
94
12
        self
95
12
    }
96
}
97

            
98
#[derive(Debug, Clone, Eq, PartialEq)]
99
pub enum Range {
100
    ThisAndFuture,
101
}
102

            
103
#[derive(Debug, Clone, Eq, PartialEq, Default)]
104
pub enum TriggerRelationship {
105
    #[default]
106
    Start,
107
    End,
108
}
109

            
110
#[derive(Debug, Clone, Eq, PartialEq, Default)]
111
pub enum RelationshipType {
112
    #[default]
113
    Parent,
114
    Child,
115
    Sibling,
116
    XName(String),
117
    IanaToken(String),
118
}
119

            
120
#[derive(Debug, Clone, Eq, PartialEq, Default)]
121
pub enum Role {
122
    Chair,
123
    #[default]
124
    RequiredParticipant,
125
    OptionalParticipant,
126
    NonParticipant,
127
    XName(String),
128
    IanaToken(String),
129
}
130

            
131
#[derive(Debug, Clone, Eq, PartialEq)]
132
pub enum Value {
133
    Binary,
134
    Boolean,
135
    CalendarAddress,
136
    Date,
137
    DateTime,
138
    Duration,
139
    Float,
140
    Integer,
141
    Period,
142
    Recurrence,
143
    Text,
144
    Time,
145
    Uri,
146
    UtcOffset,
147
    XName(String),
148
    IanaToken(String),
149
}
150

            
151
#[derive(Debug, Clone, Eq, PartialEq, Default)]
152
pub enum ParticipationStatusUnknown {
153
    #[default]
154
    NeedsAction,
155
    Accepted,
156
    Declined,
157
    Tentative,
158
    Delegated,
159
    Completed,
160
    InProcess,
161
    XName(String),
162
    IanaToken(String),
163
}
164

            
165
#[derive(Debug, Clone, Eq, PartialEq)]
166
pub enum Status {
167
    Tentative,
168
    Confirmed,
169
    Cancelled,
170
    NeedsAction,
171
    Completed,
172
    InProcess,
173
    Draft,
174
    Final,
175
}
176

            
177
#[derive(Debug, Clone, Eq, PartialEq)]
178
pub enum TimeTransparency {
179
    Opaque,
180
    Transparent,
181
}
182

            
183
#[derive(Debug, Clone, Eq, PartialEq)]
184
pub enum RecurFreq {
185
    Secondly,
186
    Minutely,
187
    Hourly,
188
    Daily,
189
    Weekly,
190
    Monthly,
191
    Yearly,
192
}
193

            
194
#[derive(Debug, Clone, Eq, PartialEq)]
195
pub enum Weekday {
196
    Monday,
197
    Tuesday,
198
    Wednesday,
199
    Thursday,
200
    Friday,
201
    Saturday,
202
    Sunday,
203
}
204

            
205
#[derive(Debug, Clone, Eq, PartialEq)]
206
pub struct OffsetWeekday {
207
    pub offset_weeks: Option<i8>,
208
    pub weekday: Weekday,
209
}
210

            
211
impl OffsetWeekday {
212
60
    pub fn new(weekday: Weekday, offset_weeks: Option<i8>) -> Self {
213
60
        OffsetWeekday {
214
60
            weekday,
215
60
            offset_weeks,
216
60
        }
217
60
    }
218
}
219

            
220
#[derive(Debug, Clone, PartialEq, Eq)]
221
pub struct CalendarDateTime {
222
    date: time::Date,
223
    time: Option<time::Time>,
224
    utc: bool,
225
}
226

            
227
impl From<(time::Date, time::Time, bool)> for CalendarDateTime {
228
564
    fn from((date, time, utc): (time::Date, time::Time, bool)) -> Self {
229
564
        CalendarDateTime {
230
564
            date,
231
564
            time: Some(time),
232
564
            utc,
233
564
        }
234
564
    }
235
}
236

            
237
impl From<(time::Date, Option<time::Time>, bool)> for CalendarDateTime {
238
752
    fn from((date, time, utc): (time::Date, Option<time::Time>, bool)) -> Self {
239
752
        CalendarDateTime { date, time, utc }
240
752
    }
241
}
242

            
243
impl CalendarDateTime {
244
22
    pub fn add(&self, duration: &Duration) -> anyhow::Result<Self> {
245
22
        match self.time {
246
22
            Some(time) => {
247
22
                // TODO otherwise you have to account for daylight changes. Not yet supported
248
22
                assert!(self.utc);
249

            
250
22
                if let Some(weeks) = duration.weeks {
251
10
                    let new_date = if duration.sign > 0 {
252
8
                        self.date
253
8
                            .add(std::time::Duration::from_secs(weeks * 7 * 24 * 60 * 60))
254
                    } else {
255
2
                        self.date
256
2
                            .sub(std::time::Duration::from_secs(weeks * 7 * 24 * 60 * 60))
257
                    };
258
10
                    Ok(CalendarDateTime {
259
10
                        date: new_date,
260
10
                        time: self.time,
261
10
                        utc: self.utc,
262
10
                    })
263
                } else {
264
12
                    let mut new_date = self.date;
265
12
                    let mut new_time = time;
266
12

            
267
12
                    if duration.sign > 0 {
268
6
                        let mut add_days = 0;
269

            
270
6
                        if let Some(days) = duration.days {
271
2
                            add_days += days;
272
4
                        }
273

            
274
6
                        let mut add_seconds = duration.hours.unwrap_or(0) * 60 * 60
275
6
                            + duration.minutes.unwrap_or(0) * 60
276
6
                            + duration.seconds.unwrap_or(0);
277
6

            
278
6
                        const ONE_DAY: u64 = 24 * 60 * 60;
279
6
                        let part_day = new_time.hour() as u64 * 60 * 60
280
6
                            + new_time.minute() as u64 * 60
281
6
                            + new_time.second() as u64;
282
6
                        let gap = ONE_DAY - part_day;
283
6

            
284
6
                        if add_seconds > gap {
285
2
                            add_days += 1;
286
2
                            add_seconds -= gap;
287
2
                            new_time = new_time.add(std::time::Duration::from_secs(gap));
288
4
                        }
289

            
290
6
                        add_days += add_seconds / ONE_DAY;
291
6
                        add_seconds %= ONE_DAY;
292
6

            
293
6
                        new_date = new_date.add(std::time::Duration::from_secs(add_days * ONE_DAY));
294
6
                        new_time = new_time.add(std::time::Duration::from_secs(add_seconds));
295
6

            
296
6
                        Ok(CalendarDateTime {
297
6
                            date: new_date,
298
6
                            time: Some(new_time),
299
6
                            utc: self.utc,
300
6
                        })
301
                    } else {
302
6
                        let mut sub_days = 0;
303

            
304
6
                        if let Some(days) = duration.days {
305
2
                            sub_days += days;
306
4
                        }
307

            
308
6
                        let mut sub_seconds = duration.hours.unwrap_or(0) * 60 * 60
309
6
                            + duration.minutes.unwrap_or(0) * 60
310
6
                            + duration.seconds.unwrap_or(0);
311
6

            
312
6
                        const ONE_DAY: u64 = 24 * 60 * 60;
313
6
                        let part_day = new_time.hour() as u64 * 60 * 60
314
6
                            + new_time.minute() as u64 * 60
315
6
                            + new_time.second() as u64;
316
6

            
317
6
                        if sub_seconds > part_day {
318
2
                            sub_days += 1;
319
2
                            sub_seconds -= part_day;
320
2
                            new_time = new_time.sub(std::time::Duration::from_secs(part_day));
321
4
                        }
322

            
323
6
                        sub_days += sub_seconds / ONE_DAY;
324
6
                        sub_seconds %= ONE_DAY;
325
6

            
326
6
                        new_date = new_date.sub(std::time::Duration::from_secs(sub_days * ONE_DAY));
327
6
                        new_time = new_time.sub(std::time::Duration::from_secs(sub_seconds));
328
6

            
329
6
                        Ok(CalendarDateTime {
330
6
                            date: new_date,
331
6
                            time: Some(new_time),
332
6
                            utc: self.utc,
333
6
                        })
334
                    }
335
                }
336
            }
337
            None => {
338
                if let Some(weeks) = duration.weeks {
339
                    let new_date = if duration.sign > 0 {
340
                        self.date
341
                            .add(std::time::Duration::from_secs(weeks * 7 * 24 * 60 * 60))
342
                    } else {
343
                        self.date
344
                            .sub(std::time::Duration::from_secs(weeks * 7 * 24 * 60 * 60))
345
                    };
346
                    Ok(CalendarDateTime {
347
                        date: new_date,
348
                        time: self.time,
349
                        utc: self.utc,
350
                    })
351
                } else if let Some(days) = duration.days {
352
                    let new_date = if duration.sign > 0 {
353
                        self.date
354
                            .add(std::time::Duration::from_secs(days * 24 * 60 * 60))
355
                    } else {
356
                        self.date
357
                            .sub(std::time::Duration::from_secs(days * 24 * 60 * 60))
358
                    };
359
                    Ok(CalendarDateTime {
360
                        date: new_date,
361
                        time: self.time,
362
                        utc: self.utc,
363
                    })
364
                } else {
365
                    Err(anyhow::anyhow!(
366
                        "Duration is a time, but the calendar date time is just a date"
367
                    ))
368
                }
369
            }
370
        }
371
22
    }
372

            
373
    //
374
    // Query
375
    //
376

            
377
404
    pub fn is_date(&self) -> bool {
378
404
        self.time.is_none()
379
404
    }
380

            
381
312
    pub fn is_date_time(&self) -> bool {
382
312
        self.time.is_some()
383
312
    }
384

            
385
926
    pub fn is_utc(&self) -> bool {
386
926
        self.time.is_some() && self.utc
387
926
    }
388

            
389
    //
390
    // Get
391
    //
392

            
393
452
    pub fn date(&self) -> &time::Date {
394
452
        &self.date
395
452
    }
396

            
397
452
    pub fn time_opt(&self) -> Option<&time::Time> {
398
452
        self.time.as_ref()
399
452
    }
400

            
401
    //
402
    // Mutate
403
    //
404

            
405
132
    pub fn set_utc(&mut self, utc: bool) {
406
132
        self.utc = utc;
407
132
    }
408
}
409

            
410
impl PartialOrd for CalendarDateTime {
411
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
412
        Some(self.cmp(other))
413
    }
414
}
415

            
416
impl Ord for CalendarDateTime {
417
6
    fn cmp(&self, other: &Self) -> Ordering {
418
6
        let date_cmp = self.date.cmp(&other.date);
419
6
        if date_cmp != Ordering::Equal {
420
6
            return date_cmp;
421
        }
422

            
423
        let time_cmp = self.time.cmp(&other.time);
424
        if time_cmp != Ordering::Equal {
425
            return time_cmp;
426
        }
427

            
428
        self.utc.cmp(&other.utc)
429
6
    }
430
}
431

            
432
#[derive(Debug, PartialEq)]
433
pub enum PropertyKind {
434
    Attach,
435
    Version,
436
    DateTimeStart,
437
    Description,
438
    Organizer,
439
    TimeZoneId,
440
    Attendee,
441
    Categories,
442
    Comment,
443
    GeographicPosition,
444
    Location,
445
    PercentComplete,
446
    Priority,
447
    Resources,
448
    Status,
449
    Summary,
450
    DateTimeCompleted,
451
    DateTimeEnd,
452
    DateTimeDue,
453
    Duration,
454
    FreeBusyTime,
455
    TimeTransparency,
456
    TimeZoneName,
457
    TimeZoneOffsetTo,
458
    TimeZoneOffsetFrom,
459
    TimeZoneUrl,
460
    Contact,
461
    RecurrenceId,
462
    Related,
463
    ExceptionDateTimes,
464
    RecurrenceDateTimes,
465
    RecurrenceRule,
466
    Action,
467
    Repeat,
468
    Trigger,
469
    DateTimeCreated,
470
    DateTimeStamp,
471
    LastModified,
472
    Sequence,
473
    RequestStatus,
474
    #[allow(dead_code)]
475
    Other,
476
    UniqueIdentifier,
477
    Classification,
478
    Url,
479
    RelatedTo,
480
}
481

            
482
#[cfg(test)]
483
mod tests {
484
    use super::*;
485

            
486
    #[test]
487
2
    fn add_duration_week() {
488
2
        let cdt: CalendarDateTime = (
489
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
490
2
            time::Time::from_hms(14, 0, 0).unwrap(),
491
2
            true,
492
2
        )
493
2
            .into();
494
2

            
495
2
        let duration = Duration {
496
2
            weeks: Some(1),
497
2
            ..Default::default()
498
2
        };
499
2
        let new = cdt.add(&duration).unwrap();
500
2

            
501
2
        check_duration_invariant(cdt, new, duration);
502
2
    }
503

            
504
    #[test]
505
2
    fn add_duration_day() {
506
2
        let cdt: CalendarDateTime = (
507
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
508
2
            time::Time::from_hms(14, 30, 0).unwrap(),
509
2
            true,
510
2
        )
511
2
            .into();
512
2

            
513
2
        let duration = Duration {
514
2
            days: Some(1),
515
2
            ..Default::default()
516
2
        };
517
2
        let new = cdt.add(&duration).unwrap();
518
2

            
519
2
        check_duration_invariant(cdt, new, duration);
520
2
    }
521

            
522
    #[test]
523
2
    fn add_duration_time_same_day() {
524
2
        let cdt: CalendarDateTime = (
525
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
526
2
            time::Time::from_hms(14, 30, 10).unwrap(),
527
2
            true,
528
2
        )
529
2
            .into();
530
2

            
531
2
        let duration = Duration {
532
2
            hours: Some(1),
533
2
            minutes: Some(30),
534
2
            seconds: Some(30),
535
2
            ..Default::default()
536
2
        };
537
2
        let new = cdt.add(&duration).unwrap();
538
2

            
539
2
        check_duration_invariant(cdt, new, duration);
540
2
    }
541

            
542
    #[test]
543
2
    fn add_duration_time_next_day() {
544
2
        let cdt: CalendarDateTime = (
545
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
546
2
            time::Time::from_hms(23, 30, 0).unwrap(),
547
2
            true,
548
2
        )
549
2
            .into();
550
2

            
551
2
        let duration = Duration {
552
2
            hours: Some(10),
553
2
            minutes: Some(30),
554
2
            seconds: Some(0),
555
2
            ..Default::default()
556
2
        };
557
2
        let new = cdt.add(&duration).unwrap();
558
2

            
559
2
        check_duration_invariant(cdt, new, duration);
560
2
    }
561

            
562
    #[test]
563
2
    fn add_negative_duration_week() {
564
2
        let cdt: CalendarDateTime = (
565
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
566
2
            time::Time::from_hms(14, 0, 0).unwrap(),
567
2
            true,
568
2
        )
569
2
            .into();
570
2

            
571
2
        let duration = Duration {
572
2
            sign: -1,
573
2
            weeks: Some(5),
574
2
            ..Default::default()
575
2
        };
576
2
        let new = cdt.add(&duration).unwrap();
577
2

            
578
2
        check_duration_invariant(cdt, new, duration);
579
2
    }
580

            
581
    #[test]
582
2
    fn add_negative_duration_day() {
583
2
        let cdt: CalendarDateTime = (
584
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
585
2
            time::Time::from_hms(14, 30, 0).unwrap(),
586
2
            true,
587
2
        )
588
2
            .into();
589
2

            
590
2
        let duration = Duration {
591
2
            sign: -1,
592
2
            days: Some(3),
593
2
            ..Default::default()
594
2
        };
595
2
        let new = cdt.add(&duration).unwrap();
596
2

            
597
2
        check_duration_invariant(cdt, new, duration);
598
2
    }
599

            
600
    #[test]
601
2
    fn add_negative_duration_time_same_day() {
602
2
        let cdt: CalendarDateTime = (
603
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
604
2
            time::Time::from_hms(14, 30, 10).unwrap(),
605
2
            true,
606
2
        )
607
2
            .into();
608
2

            
609
2
        let duration = Duration {
610
2
            sign: -1,
611
2
            hours: Some(1),
612
2
            minutes: Some(30),
613
2
            seconds: Some(30),
614
2
            ..Default::default()
615
2
        };
616
2
        let new = cdt.add(&duration).unwrap();
617
2

            
618
2
        check_duration_invariant(cdt, new, duration);
619
2
    }
620

            
621
    #[test]
622
2
    fn add_negative_duration_time_previous_day() {
623
2
        let cdt: CalendarDateTime = (
624
2
            time::Date::from_calendar_date(1992, time::Month::April, 12).unwrap(),
625
2
            time::Time::from_hms(3, 30, 0).unwrap(),
626
2
            true,
627
2
        )
628
2
            .into();
629
2

            
630
2
        let duration = Duration {
631
2
            sign: -1,
632
2
            hours: Some(10),
633
2
            minutes: Some(30),
634
2
            seconds: Some(0),
635
2
            ..Default::default()
636
2
        };
637
2
        let new = cdt.add(&duration).unwrap();
638
2

            
639
2
        check_duration_invariant(cdt, new, duration);
640
2
    }
641

            
642
16
    fn check_duration_invariant(
643
16
        original: CalendarDateTime,
644
16
        new: CalendarDateTime,
645
16
        duration: Duration,
646
16
    ) {
647
16
        let original = chrono::NaiveDateTime::new(
648
16
            chrono::NaiveDate::from_ymd_opt(
649
16
                original.date.year(),
650
16
                original.date.month() as u32,
651
16
                original.date.day() as u32,
652
16
            )
653
16
            .unwrap(),
654
16
            match original.time {
655
16
                Some(time) => chrono::NaiveTime::from_hms_opt(
656
16
                    time.hour() as u32,
657
16
                    time.minute() as u32,
658
16
                    time.second() as u32,
659
16
                )
660
16
                .unwrap(),
661
                None => chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
662
            },
663
        );
664
16
        let new = chrono::NaiveDateTime::new(
665
16
            chrono::NaiveDate::from_ymd_opt(
666
16
                new.date.year(),
667
16
                new.date.month() as u32,
668
16
                new.date.day() as u32,
669
16
            )
670
16
            .unwrap(),
671
16
            match new.time {
672
16
                Some(time) => chrono::NaiveTime::from_hms_opt(
673
16
                    time.hour() as u32,
674
16
                    time.minute() as u32,
675
16
                    time.second() as u32,
676
16
                )
677
16
                .unwrap(),
678
                None => chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
679
            },
680
        );
681

            
682
16
        println!("Original: {}", original);
683
16
        println!("New: {}", new);
684
16

            
685
16
        let dur = new.signed_duration_since(original);
686
16

            
687
16
        let (sign, duration) = duration.to_std();
688
16
        assert_eq!(sign as i64 * duration.as_secs() as i64, dur.num_seconds());
689
16
    }
690
}