16
16
17
17
$debug = ENV['DEBUG']
18
18
19
+ class Date
20
+ def inspect
21
+ self.to_s
22
+ end
23
+ end
24
+
19
25
def debug(*objs)
20
26
if $debug
21
27
pp(*objs)
@@ -30,14 +36,14 @@ module Vpim
30
36
# syntax description and examples from RFC 2445. The description is pretty
31
37
# hard to understand, but the examples are more helpful.
32
38
#
33
- # The implementation is pretty complete, but still lacks support for:
39
+ # The implementation is reasonably complete, but still lacks support for:
34
40
#
35
- # TODO - BYWEEKLY, BYWEEKNO, WKST: rules that recur by the week, or are
36
- # limited to particular weeks, not hard, but not trivial, I'll do it for the
37
- # next release
41
+ # Recurrence by date (RDATE) and exclusions (EXDATE, EXRULE).
42
+ #
43
+ # TODO - BYWEEKNO: rules that are limited to particular weeks in a year.
38
44
#
39
45
# TODO - BYHOUR, BYMINUTE, BYSECOND: trivial to do, but I don't have an
40
- # immediate need for them, I'll do it for the next release
46
+ # immediate need for them.
41
47
#
42
48
# TODO - BYSETPOS: limiting to only certain recurrences in a set (what does
43
49
# -1, last occurence, mean for an infinitely occuring rule?)
@@ -139,16 +145,18 @@ def each_until(dountil)
139
145
# most systems.
140
146
def each ( dountil = nil ) #:yield: ytime
141
147
t = @dtstart . clone
142
- count = 1
143
148
144
149
# Time.to_a => [ sec, min, hour, day, month, year, wday, yday, isdst, zone ]
145
150
146
- # Every event occurs at least once, at its start time, but only if the start
147
- # time is earlier than DOUNTIL...
151
+ # Every event occurs at its start time, but only if the start time is
152
+ # earlier than DOUNTIL...
153
+ if !dountil || t < dountil
154
+ yield t
155
+ end
156
+ count = 1
157
+
158
+ # With no recurrence, DTSTART is the only occurence.
148
159
if !@rrule
149
- if !dountil || t < dountil
150
- yield t
151
- end
152
160
return self
153
161
end
154
162
@@ -168,20 +176,21 @@ def each(dountil = nil) #:yield: ytime
168
176
#when 'YEARLY' then
169
177
# Don't need to keep track of year, all occurences are within t's
170
178
# year.
171
- when 'MONTHLY' then days . month = t . month #month = { t.month => nil }
172
- # when 'WEEKLY' then days.mday = t.month, t.mday
179
+ when 'MONTHLY' then days . month = t . month
180
+ when 'WEEKLY' then # days.month = t.month
173
181
# TODO - WEEKLY
174
- when 'DAILY' then days . mday = t . month , t . mday #month = { t.month => [ t.mday ] }
175
- when 'HOURLY' then hour = [ t . hour ]
176
- when 'MINUTELY' then min = [ t . min ]
177
- when 'SECONDLY' then sec = [ t . sec ]
182
+ when 'DAILY' then days . mday = t . month , t . mday
183
+ when 'HOURLY' then hour = [ t . hour ]
184
+ when 'MINUTELY' then min = [ t . min ]
185
+ when 'SECONDLY' then sec = [ t . sec ]
178
186
end
179
187
188
+ # debug [t, days]
180
189
# Process the BY* modifiers in RFC defined order:
181
190
# BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY,
182
191
# BYHOUR, BYMINUTE, BYSECOND and BYSETPOS
183
-
184
- bymon = [ nil ]
192
+
193
+ bymon = [ nil ]
185
194
186
195
if @by [ 'BYMONTH' ]
187
196
bymon = @by [ 'BYMONTH' ] . split ( ',' )
@@ -220,7 +229,6 @@ def each(dountil = nil) #:yield: ytime
220
229
221
230
if @by [ 'BYDAY' ]
222
231
byday = @by [ 'BYDAY' ] . scan ( /,?([+-]?[1-9]?\d *)?(SU|MO|TU|WE|TH|FR|SA)/i )
223
- # debug byday
224
232
225
233
# BYDAY means different things in different frequencies. The +n+
226
234
# is only meaningful when freq is yearly or monthly.
@@ -231,7 +239,7 @@ def each(dountil = nil) #:yield: ytime
231
239
when 'MONTHLY'
232
240
dates = byday_in_monthly ( t . year , t . month , byday )
233
241
when 'WEEKLY'
234
- # dates = byday_in_weekly(t.year, wkstart, t.month, t.day , byday)
242
+ dates = byday_in_weekly ( t . year , t . month , t . mday , @wkst , byday )
235
243
when 'DAILY' , 'HOURLY' , 'MINUTELY' , 'SECONDLY'
236
244
# Reuse the byday_in_monthly. Current day is already specified,
237
245
# so this will just eliminate the current day if its not allowed
@@ -248,29 +256,34 @@ def each(dountil = nil) #:yield: ytime
248
256
249
257
# TODO - BYSETPOS
250
258
251
- # Yield the time, if we haven't gone over COUNT, or past UNTIL, or past
252
- # the end of representable time.
253
-
254
259
hour = [ @dtstart . hour ] if !hour
255
260
min = [ @dtstart . min ] if !min
256
261
sec = [ @dtstart . sec ] if !sec
257
262
258
263
# debug days
259
264
265
+ # Yield the time, if we haven't gone over COUNT, or past UNTIL, or past
266
+ # the end of representable time.
267
+
260
268
days . each do |m , d |
261
269
hour . each do |h |
262
270
min . each do |n |
263
271
sec . each do |s |
264
- if ( @count && ( count > @count ) )
265
- return self
266
- end
267
272
y = Time . local ( t . year , m , d , h , n , s , 0 )
268
273
269
274
next if y . hour != h
270
275
271
276
# The generated set can sometimes generate results earlier
272
- # than the DTSTART, skip them.
273
- next if y < @dtstart
277
+ # than the DTSTART, skip them. Also, we already yielded
278
+ # DTSTART, skip it.
279
+ next if y <= @dtstart
280
+
281
+ count += 1
282
+
283
+ # We are done if current count is past @count.
284
+ if ( @count && ( count > @count ) )
285
+ return self
286
+ end
274
287
275
288
# We are done if current time is past @until.
276
289
if @until && ( y > @until )
@@ -282,7 +295,6 @@ def each(dountil = nil) #:yield: ytime
282
295
return self
283
296
end
284
297
yield y
285
- count += 1
286
298
end
287
299
end
288
300
end
@@ -363,30 +375,30 @@ def intersect_bymon(bymon) #:nodoc:
363
375
end
364
376
365
377
def intersect_dates ( dates ) #:nodoc:
366
- if dates
367
- # If no months are in the dayset, add all the ones in dates
368
- if !@month
369
- @month = { }
378
+ return unless dates
370
379
371
- dates . each do |d |
372
- @month [ d . mon ] = nil
373
- end
380
+ # If no months are in the dayset, add all the ones in dates
381
+ if !@month
382
+ @month = { }
383
+
384
+ dates . each do |d |
385
+ @month [ d . mon ] = nil
374
386
end
387
+ end
375
388
376
- # In each month,
377
- # if there are days,
378
- # eliminate those not in dates
379
- # otherwise
380
- # add all those in dates
381
- @month . each do |mon , days |
382
- days_in_mon = dates . find_all { |d | d . mon == mon }
383
- days_in_mon = days_in_mon . collect { |d | d . day }
384
-
385
- if days
386
- days_in_mon = days_in_mon & days
387
- end
388
- @month [ mon ] = days_in_mon
389
+ # In each month,
390
+ # if there are days,
391
+ # eliminate those not in dates
392
+ # otherwise
393
+ # add all those in dates
394
+ @month . each do |mon , days |
395
+ days_in_mon = dates . find_all { |d | d . mon == mon }
396
+ days_in_mon = days_in_mon . collect { |d | d . day }
397
+
398
+ if days
399
+ days_in_mon = days_in_mon & days
389
400
end
401
+ @month [ mon ] = days_in_mon
390
402
end
391
403
end
392
404
@@ -451,7 +463,7 @@ def byyearday(year, byyday) #:nodoc:
451
463
dates = [ ]
452
464
453
465
byyday . each do |yday |
454
- dates << Date . new2 ( year , yday [ 0 ] . to_i )
466
+ dates << Date . ordinal ( year , yday [ 0 ] . to_i )
455
467
end
456
468
dates . sort!
457
469
dates
@@ -472,6 +484,17 @@ def byday_in_monthly(year, mon, byday) #:nodoc:
472
484
dates
473
485
end
474
486
487
+ def byday_in_weekly ( year , mon , day , wkst , byday )
488
+ # debug ["day", year,mon,day,wkst,byday]
489
+ days = byday . map { |_ , byday | Date . str2wday ( byday ) }
490
+ week = DateGen . weekofdate ( year , mon , day , wkst )
491
+ # debug [ "week", dates ]
492
+ week . delete_if do |d |
493
+ !days . include? ( d . wday )
494
+ end
495
+ week
496
+ end
497
+
475
498
end
476
499
477
500
end
0 commit comments