Module: Protocol::Caldav::Ical::Expand

Defined in:
lib/protocol/caldav/ical/expand.rb

Class Method Summary collapse

Class Method Details

.expand(component, range_start:, range_end:, max_occurrences: 10000) ⇒ Object

Expand a recurring VCALENDAR into individual instances. Returns a serialized VCALENDAR string with RRULE removed and each occurrence as a separate VEVENT with RECURRENCE-ID.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/protocol/caldav/ical/expand.rb', line 14

def expand(component, range_start:, range_end:, max_occurrences: 10000)
  if component.name.casecmp?('VCALENDAR')
    base = component.find_components('VEVENT').find { |v| v.find_property('RRULE') }
    if base
      uid = base.find_property('UID')&.value
      overrides = {}
      component.find_components('VEVENT').each do |v|
        rid = v.find_property('RECURRENCE-ID')
        if rid && v.find_property('UID')&.value == uid
          rid_time = Filter::Match.send(:parse_datetime_string, rid.value.strip)
          if rid_time
            overrides[rid_time] = v
          end
        end
      end
      dtstart_prop = base.find_property('DTSTART')
      dtstart = Filter::Match.send(:parse_datetime_string, dtstart_prop.value.strip)
      if dtstart
        dtend = Filter::Match.send(:parse_ical_datetime, base, 'DTEND')
        if dtend
          duration = (dtend - dtstart).to_i
        else
          duration = 3600
        end
        rrule = base.find_property('RRULE').value.strip
        exdates = base.find_all_properties('EXDATE').filter_map do |ex|
          Filter::Match.send(:parse_datetime_string, ex.value.strip)
        end
        occurrences = Rrule.expand(
          dtstart:     dtstart,
          rrule_value: rrule,
          range_start: range_start,
          range_end:   range_end,
          exdates:     exdates,
          max_count:   max_occurrences,
        )
        instances = occurrences.map do |occ_start|
          override = overrides[occ_start]
          if override
            serialize_vevent_instance(override, occ_start)
          else
            serialize_expanded_instance(base, occ_start, duration)
          end
        end
        overrides.each do |rid_time, override_vevent|
          if occurrences.any? { |o| Rrule.send(:times_equal?, o, rid_time) }
            next
          end
          odt = Filter::Match.send(:parse_ical_datetime, override_vevent, 'DTSTART')
          unless odt && odt >= range_start && odt < range_end
            next
          end
          instances << serialize_vevent_instance(override_vevent, rid_time)
        end
        "BEGIN:VCALENDAR\r\n#{instances.join}END:VCALENDAR\r\n"
      else
        serialize_component(component)
      end
    else
      serialize_component(component)
    end
  else
    serialize_component(component)
  end
end