Class: Tilia::VObject::Recur::EventIterator
- Inherits:
-
Object
- Object
- Tilia::VObject::Recur::EventIterator
- Defined in:
- lib/tilia/v_object/recur/event_iterator.rb
Overview
This class is used to determine new for a recurring event, when the next events occur.
This iterator may loop infinitely in the future, therefore it is important that if you use this class, you set hard limits for the amount of iterations you want to handle.
Note that currently there is not full support for the entire iCalendar specification, as it’s very complex and contains a lot of permutations that’s not yet used very often in software.
For the focus has been on features as they actually appear in Calendaring software, but this may well get expanded as needed / on demand
The following RRULE properties are supported
* UNTIL
* INTERVAL
* COUNT
* FREQ=DAILY
* BYDAY
* BYHOUR
* BYMONTH
* FREQ=WEEKLY
* BYDAY
* BYHOUR
* WKST
* FREQ=MONTHLY
* BYMONTHDAY
* BYDAY
* BYSETPOS
* FREQ=YEARLY
* BYMONTH
* BYMONTHDAY (only if BYMONTH is also set)
* BYDAY (only if BYMONTH is also set)
Anything beyond this is ‘undefined’, which means that it may get ignored, or you may get unexpected results. The effect is that in some applications the specified recurrence may look incorrect, or is missing.
The recurrence iterator also does not yet support THISANDFUTURE.
Instance Method Summary collapse
-
#current ⇒ Time
Returns the date for the current position of the iterator.
-
#dt_end ⇒ Time
This method returns the end date for the current iteration of the event.
-
#dt_start ⇒ Time
This method returns the start date for the current iteration of the event.
- #each ⇒ Object
-
#event_object ⇒ VEvent
Returns a VEVENT for the current iterations of the event.
-
#fast_forward(date_time) ⇒ Object
Quickly jump to a date in the future.
-
#infinite? ⇒ Boolean
Returns true if this recurring event never ends.
-
#initialize(input, uid = nil, time_zone = nil) ⇒ EventIterator
constructor
Creates the iterator.
-
#key ⇒ Fixnum
Returns the current position of the iterator.
-
#next ⇒ void
Advances the iterator with one step.
-
#rewind ⇒ Object
Sets the iterator back to the starting point.
-
#to_a ⇒ Time
The next date from the rrule parser.
- #to_enum ⇒ Object
-
#valid ⇒ Boolean
This is called after next, to see if the iterator is still at a valid position, or if it’s at the end.
Constructor Details
#initialize(input, uid = nil, time_zone = nil) ⇒ EventIterator
Creates the iterator.
There’s three ways to set up the iterator.
-
You can pass a VCALENDAR component and a UID.
-
You can pass an array of VEVENTs (all UIDS should match).
-
You can pass a single VEVENT component.
Only the second method is recomended. The other 1 and 3 will be removed at some point in the future.
The uid parameter is only required for the first method.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 72 def initialize(input, uid = nil, time_zone = nil) @overridden_events = [] @exceptions = {} @all_day = false time_zone = ActiveSupport::TimeZone.new('UTC') if time_zone.nil? @time_zone = time_zone if input.is_a?(Array) events = input elsif input.is_a?(Component::VEvent) # Single instance mode. events = [input] else # Calendar + UID mode. uid = uid.to_s if uid.blank? fail ArgumentError, 'The UID argument is required when a VCALENDAR is passed to this constructor' end unless input.key?('VEVENT') fail ArgumentError, 'No events found in this calendar' end events = input.by_uid(uid) end events.each do |vevent| if !vevent.key?('RECURRENCE-ID') @master_event = vevent else @exceptions[vevent['RECURRENCE-ID'].date_time(@time_zone).to_i] = true @overridden_events << vevent end end unless @master_event # No base event was found. CalDAV does allow cases where only # overridden instances are stored. # # In this particular case, we're just going to grab the first # event and use that instead. This may not always give the # desired result. if @overridden_events.size == 0 fail ArgumentError, "This VCALENDAR did not have an event with UID: #{uid}" end @master_event = @overridden_events.shift end @start_date = @master_event['DTSTART'].date_time(@time_zone) @all_day = !@master_event['DTSTART'].time? if @master_event.key?('EXDATE') @master_event['EXDATE'].each do |ex_date| ex_date.date_times(@time_zone).each do |dt| @exceptions[dt.to_i] = true end end end if @master_event.key?('DTEND') @event_duration = (@master_event['DTEND'].date_time(@time_zone).to_i - @start_date.to_i).seconds elsif @master_event.key?('DURATION') @event_duration = @master_event['DURATION'].date_interval elsif @all_day @event_duration = 1.day else @event_duration = 0.seconds end if @master_event.key?('RDATE') @recur_iterator = Recur::RDateIterator.new( @master_event['RDATE'].parts, @start_date ) elsif @master_event.key?('RRULE') @recur_iterator = Recur::RRuleIterator.new( @master_event['RRULE'].parts, @start_date ) else @recur_iterator = Recur::RRuleIterator.new( { 'FREQ' => 'DAILY', 'COUNT' => 1 }, @start_date ) end rewind fail Recur::NoInstancesException, 'This recurrence rule does not generate any valid instances' unless valid end |
Instance Method Details
#current ⇒ Time
Returns the date for the current position of the iterator.
170 171 172 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 170 def current @current_date.clone if @current_date end |
#dt_end ⇒ Time
This method returns the end date for the current iteration of the event.
186 187 188 189 190 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 186 def dt_end return nil unless valid @current_date + @event_duration end |
#dt_start ⇒ Time
This method returns the start date for the current iteration of the event.
178 179 180 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 178 def dt_start @current_date.clone if @current_date end |
#each ⇒ Object
403 404 405 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 403 def each to_enum.each { |i| yield(i) } end |
#event_object ⇒ VEvent
Returns a VEVENT for the current iterations of the event.
This VEVENT will have a recurrence id, and it’s DTSTART and DTEND altered.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 198 def event_object return @current_overridden_event if @current_overridden_event event = @master_event.clone event.delete('RRULE') event.delete('EXDATE') event.delete('RDATE') event.delete('EXRULE') event.delete('RECURRENCE-ID') floating = event['DTSTART'].floating? event['DTSTART'].date_time = dt_start event['DTSTART'].floating = floating if event.key?('DTEND') floating = event['DTEND'].floating? event['DTEND'].date_time = dt_end event['DTEND'].floating = floating end # Including a RECURRENCE-ID to the object, unless this is the first # object. # # The inner recurIterator is always one step ahead, this is why we're # checking for the key being higher than 1. if @recur_iterator.key > 1 recurid = event['DTSTART'].clone recurid.name = 'RECURRENCE-ID' event.add(recurid) end event end |
#fast_forward(date_time) ⇒ Object
Quickly jump to a date in the future.
325 326 327 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 325 def fast_forward(date_time) self.next while valid && dt_end < date_time end |
#infinite? ⇒ Boolean
Returns true if this recurring event never ends.
332 333 334 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 332 def infinite? @recur_iterator.infinite? end |
#key ⇒ Fixnum
Returns the current position of the iterator.
This is for us simply a 0-based index.
236 237 238 239 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 236 def key # The counter is always 1 ahead. @counter - 1 end |
#next ⇒ void
This method returns an undefined value.
Advances the iterator with one step.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 277 def next @current_overridden_event = nil @counter += 1 if @next_date # We had a stored value. next_date = @next_date @next_date = nil else # We need to ask rruleparser for the next date. # We need to do this until we find a date that's not in the # exception list. loop do unless @recur_iterator.valid next_date = nil break end next_date = @recur_iterator.current @recur_iterator.next break unless @exceptions.key?(next_date.to_i) end end # next_date now contains what rrule thinks is the next one, but an # overridden event may cut ahead. if @overridden_events_index.any? = @overridden_events_index.keys[-1] offset = @overridden_events_index[] if !next_date || < next_date.to_i # Overridden event comes first. @current_overridden_event = @overridden_events[offset] # Putting the rrule next date aside. @next_date = next_date @current_date = @current_overridden_event['DTSTART'].date_time(@time_zone) # Ensuring that this item will only be used once. @overridden_events_index.delete() # Exit point! return nil end end @current_date = next_date end |
#rewind ⇒ Object
Sets the iterator back to the starting point.
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 255 def rewind @recur_iterator.rewind # re-creating overridden event index. index = {} @overridden_events.each_with_index do |event, key| stamp = event['DTSTART'].date_time(@time_zone).to_i index[stamp] = key end index = index.to_a.sort { |a, b| b[0] <=> a[0] }.to_h @counter = 0 @overridden_events_index = index @current_overridden_event = nil @next_date = nil @current_date = @start_date.clone self.next end |
#to_a ⇒ Time
The next date from the rrule parser.
Sometimes we need to temporary store the next date, because an overridden event came before.
RUBY: attr_accessor :next_date
393 394 395 396 397 398 399 400 401 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 393 def to_a fail 'Can not convert infinite event to array!' if infinite? list = [] to_enum.each do |date| list << date end list end |
#to_enum ⇒ Object
407 408 409 410 411 412 413 414 415 416 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 407 def to_enum copy = clone copy.rewind Enumerator.new do |yielder| while copy.valid yielder << copy.dt_start copy.next end end end |
#valid ⇒ Boolean
This is called after next, to see if the iterator is still at a valid position, or if it’s at the end.
245 246 247 248 249 250 251 252 |
# File 'lib/tilia/v_object/recur/event_iterator.rb', line 245 def valid if @counter > Settings.max_recurrences && Settings.max_recurrences != -1 fail MaxInstancesExceededException, "Recurring events are only allowed to generate #{Settings.max_recurrences}" end !@current_date.nil? end |