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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
374
    //
375
    // Query
376
    //
377

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

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

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

            
390
    //
391
    // Get
392
    //
393

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

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

            
402
    //
403
    // Mutate
404
    //
405

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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