@@ -2,15 +2,18 @@ package core
22
33import (
44 "crypto/sha256"
5- "errors"
65 "fmt"
76 "io"
87 "time"
98
109 ics "github.com/arran4/golang-ical"
1110 "github.com/google/uuid"
11+ rrule "github.com/teambition/rrule-go"
1212)
1313
14+ // parseICal parses ical events from r to []Event.
15+ // calendar is copied to each event and used to scope deterministic IDs.
16+ // When stableIDs is false, each event gets a random ID.
1417func parseICal (r io.Reader , calendar string , stableIDs bool ) ([]Event , error ) {
1518 cal , err := ics .ParseCalendar (r )
1619 if err != nil {
@@ -25,7 +28,7 @@ func parseICal(r io.Reader, calendar string, stableIDs bool) ([]Event, error) {
2528 return nil , fmt .Errorf ("import event %d: %w" , i + 1 , err )
2629 }
2730 if stableIDs {
28- event .Id = icalEventID (calendar , icalText ( source , ics . ComponentPropertyUniqueId ), i , event )
31+ event .Id = icalEventID (calendar , source . Id ( ), i , event )
2932 }
3033 if err := event .Validate (); err != nil {
3134 return nil , fmt .Errorf ("import event %d: %w" , i + 1 , err )
@@ -47,7 +50,7 @@ func parseICalEvent(source *ics.VEvent, calendar string) (Event, error) {
4750 return Event {}, fmt .Errorf ("read DTEND: %w" , err )
4851 }
4952
50- repeat , err := parseICalRepetition (source )
53+ repeat , err := parseICalRRule (source , from )
5154 if err != nil {
5255 return Event {}, err
5356 }
@@ -63,63 +66,18 @@ func parseICalEvent(source *ics.VEvent, calendar string) (Event, error) {
6366 }, nil
6467}
6568
66- func parseICalRepetition (event * ics.VEvent ) (* Repetition , error ) {
67- if event .HasProperty (ics .ComponentPropertyRdate ) ||
68- event .HasProperty (ics .ComponentPropertyExdate ) ||
69- event .HasProperty (ics .ComponentPropertyExrule ) ||
70- event .HasProperty (ics .ComponentPropertyRecurrenceId ) {
71- return nil , errors .New ("recurrence dates and exceptions are not supported" )
72- }
73-
74- rules , err := event .GetRRules ()
75- if err != nil {
76- return nil , err
77- }
78- if len (rules ) == 0 {
69+ func parseICalRRule (event * ics.VEvent , start time.Time ) (* rrule.Set , error ) {
70+ value := icalText (event , ics .ComponentPropertyRrule )
71+ if value == "" {
7972 return nil , nil
8073 }
81- if len (rules ) != 1 {
82- return nil , errors .New ("multiple recurrence rules are not supported" )
83- }
84-
85- rule := rules [0 ]
86- if hasICalModifiers (rule ) {
87- return nil , errors .New ("recurrence modifiers are not supported" )
88- }
8974
90- frequency , err := parseICalFrequency ( rule . Freq )
75+ option , err := rrule . StrToROptionInLocation ( value , start . Location () )
9176 if err != nil {
92- return nil , err
93- }
94-
95- return & Repetition {
96- Frequency : frequency ,
97- Interval : rule .Interval ,
98- Until : rule .Until ,
99- Count : rule .Count ,
100- }, nil
101- }
102-
103- func parseICalFrequency (frequency ics.Frequency ) (Freq , error ) {
104- switch frequency {
105- case ics .FrequencyDaily :
106- return Day , nil
107- case ics .FrequencyWeekly :
108- return Week , nil
109- case ics .FrequencyMonthly :
110- return Month , nil
111- case ics .FrequencyYearly :
112- return Year , nil
113- default :
114- return Invalid , fmt .Errorf ("unsupported recurrence frequency %q" , frequency )
77+ return nil , fmt .Errorf ("parse RRULE: %w" , err )
11578 }
116- }
117-
118- func hasICalModifiers (rule * ics.RecurrenceRule ) bool {
119- return len (rule .BySecond )+ len (rule .ByMinute )+ len (rule .ByHour )+
120- len (rule .ByDay )+ len (rule .ByMonthDay )+ len (rule .ByYearDay )+
121- len (rule .ByWeekNo )+ len (rule .ByMonth )+ len (rule .BySetPos ) > 0 ||
122- rule .Wkst != ""
79+ option .Dtstart = start
80+ return newRecurrence (* option , nil )
12381}
12482
12583func icalText (event * ics.VEvent , property ics.ComponentProperty ) string {
@@ -132,13 +90,8 @@ func icalText(event *ics.VEvent, property ics.ComponentProperty) string {
13290
13391func icalEventID (calendar , uid string , index int , event Event ) uuid.UUID {
13492 if uid == "" {
93+ // fallback it uid is missing
13594 uid = fmt .Sprintf ("%d\x00 %s\x00 %s" , index , event .Title , event .From .Format (time .RFC3339Nano ))
13695 }
137- sum := sha256 .Sum256 ([]byte (calendar + "\x00 " + uid ))
138-
139- var id uuid.UUID
140- copy (id [:], sum [:16 ])
141- id [6 ] = id [6 ]& 0x0f | 0x80
142- id [8 ] = id [8 ]& 0x3f | 0x80
143- return id
96+ return uuid .NewHash (sha256 .New (), uuid .Nil , []byte (calendar + "\x00 " + uid ), 8 )
14497}
0 commit comments