paulsm-icalendar 1.1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/COPYING +56 -0
  2. data/GPL +340 -0
  3. data/README +266 -0
  4. data/Rakefile +109 -0
  5. data/docs/rfcs/itip_notes.txt +69 -0
  6. data/docs/rfcs/rfc2425.pdf +0 -0
  7. data/docs/rfcs/rfc2426.pdf +0 -0
  8. data/docs/rfcs/rfc2445.pdf +0 -0
  9. data/docs/rfcs/rfc2446.pdf +0 -0
  10. data/docs/rfcs/rfc2447.pdf +0 -0
  11. data/docs/rfcs/rfc3283.txt +738 -0
  12. data/examples/create_cal.rb +45 -0
  13. data/examples/parse_cal.rb +20 -0
  14. data/examples/single_event.ics +18 -0
  15. data/lib/hash_attrs.rb +34 -0
  16. data/lib/icalendar.rb +39 -0
  17. data/lib/icalendar/base.rb +43 -0
  18. data/lib/icalendar/calendar.rb +113 -0
  19. data/lib/icalendar/component.rb +442 -0
  20. data/lib/icalendar/component/alarm.rb +44 -0
  21. data/lib/icalendar/component/event.rb +129 -0
  22. data/lib/icalendar/component/freebusy.rb +38 -0
  23. data/lib/icalendar/component/journal.rb +61 -0
  24. data/lib/icalendar/component/timezone.rb +105 -0
  25. data/lib/icalendar/component/todo.rb +64 -0
  26. data/lib/icalendar/conversions.rb +150 -0
  27. data/lib/icalendar/helpers.rb +109 -0
  28. data/lib/icalendar/parameter.rb +33 -0
  29. data/lib/icalendar/parser.rb +396 -0
  30. data/lib/icalendar/rrule.rb +126 -0
  31. data/lib/icalendar/tzinfo.rb +121 -0
  32. data/lib/meta.rb +32 -0
  33. data/test/calendar_test.rb +71 -0
  34. data/test/component/event_test.rb +256 -0
  35. data/test/component/timezone_test.rb +67 -0
  36. data/test/component/todo_test.rb +13 -0
  37. data/test/component_test.rb +76 -0
  38. data/test/conversions_test.rb +97 -0
  39. data/test/coverage/STUB +0 -0
  40. data/test/fixtures/folding.ics +23 -0
  41. data/test/fixtures/life.ics +46 -0
  42. data/test/fixtures/simplecal.ics +119 -0
  43. data/test/fixtures/single_event.ics +23 -0
  44. data/test/interactive.rb +17 -0
  45. data/test/parameter_test.rb +29 -0
  46. data/test/parser_test.rb +84 -0
  47. data/test/read_write.rb +23 -0
  48. metadata +108 -0
@@ -0,0 +1,126 @@
1
+ =begin
2
+ Copyright (C) 2008 Rick (http://github.com/rubyredrick)
3
+
4
+ This library is free software; you can redistribute it and/or modify it
5
+ under the same terms as the ruby language itself, see the file COPYING for
6
+ details.
7
+ =end
8
+
9
+ require 'date'
10
+ require 'uri'
11
+ require 'stringio'
12
+
13
+ module Icalendar
14
+
15
+ # This class is not yet fully functional..
16
+ #
17
+ # Gem versions < 1.1.0.0 used to return a string for the recurrence_rule component,
18
+ # but now it returns this Icalendar::RRule class. ie It's not backwards compatible!
19
+ #
20
+ # To get the original RRULE value from a parsed feed, use the 'orig_value' property.
21
+ #
22
+ # Example:
23
+ # rules = event.recurrence_rules.map{ |rule| rule.orig_value }
24
+
25
+ class RRule < Icalendar::Base
26
+
27
+ class Weekday
28
+ def initialize(day, position)
29
+ @day, @position = day, position
30
+ end
31
+
32
+ def to_s
33
+ "#{@position}#{@day}"
34
+ end
35
+ end
36
+
37
+ def initialize(name, params, value, parser)
38
+ @value = value
39
+ frequency_match = value.match(/FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/)
40
+ raise Icalendar::InvalidPropertyValue.new("FREQ must be specified for RRULE values") unless frequency_match
41
+ @frequency = frequency_match[1]
42
+ @until = parse_date_val("UNTIL", value)
43
+ @count = parse_int_val("COUNT", value)
44
+ raise Icalendar::InvalidPropertyValue.new("UNTIL and COUNT must not both be specified for RRULE values") if [@until, @count].compact.length > 1
45
+ @interval = parse_int_val("INTERVAL", value)
46
+ @by_list = {:bysecond => parse_int_list("BYSECOND", value)}
47
+ @by_list[:byminute] = parse_int_list("BYMINUTE",value)
48
+ @by_list[:byhour] = parse_int_list("BYHOUR", value)
49
+ @by_list[:byday] = parse_weekday_list("BYDAY", value)
50
+ @by_list[:bymonthday] = parse_int_list("BYMONTHDAY", value)
51
+ @by_list[:byyearday] = parse_int_list("BYYEARDAY", value)
52
+ @by_list[:byweekno] = parse_int_list("BYWEEKNO", value)
53
+ @by_list[:bymonth] = parse_int_list("BYMONTH", value)
54
+ @by_list[:bysetpos] = parse_int_list("BYSETPOS", value)
55
+ @wkst = parse_wkstart(value)
56
+ end
57
+
58
+ # Returns the original pre-parsed RRULE value.
59
+ def orig_value
60
+ @value
61
+ end
62
+
63
+ def to_ical
64
+ result = ["FREQ=#{@frequency}"]
65
+ result << ";UNTIL=#{@until.to_ical}" if @until
66
+ result << ";COUNT=#{@count}" if @count
67
+ result << ";INTERVAL=#{@interval}" if @interval
68
+ @by_list.each do |key, value|
69
+ result << ";#{key.to_s.upcase}=#{value}" if value
70
+ end
71
+ result << ";WKST=#{@wkst}" if @wkst
72
+ result.join
73
+ end
74
+
75
+ def parse_date_val(name, string)
76
+ match = string.match(/;#{name}=(.*?)(;|$)/)
77
+ match ? DateTime.parse(match[1]) : nil
78
+ end
79
+
80
+ def parse_int_val(name, string)
81
+ match = string.match(/;#{name}=(\d+)(;|$)/)
82
+ match ? match[1].to_i : nil
83
+ end
84
+
85
+ def parse_int_list(name, string)
86
+ match = string.match(/;#{name}=([+-]?.*?)(;|$)/)
87
+ if match
88
+ match[1].split(",").map {|int| int.to_i}
89
+ else
90
+ nil
91
+ end
92
+ end
93
+
94
+ def parse_weekday_list(name, string)
95
+ match = string.match(/;#{name}=(.*?)(;|$)/)
96
+ if match
97
+ match[1].split(",").map {|weekday|
98
+ wd_match = weekday.match(/([+-]?\d*)(SU|MO|TU|WE|TH|FR|SA)/)
99
+ Weekday.new(wd_match[2], wd_match[1])
100
+ }
101
+ else
102
+ nil
103
+ end
104
+ end
105
+
106
+ def parse_wkstart(string)
107
+ match = string.match(/;WKSTART=(SU|MO|TU|WE|TH|FR|SA)(;|$)/)
108
+ if match
109
+ %w{SU MO TU WE TH FR SA}.index(match[1])
110
+ else
111
+ nil
112
+ end
113
+ end
114
+
115
+ # TODO: Incomplete
116
+ def occurrences_of_event_starting(event, datetime)
117
+ initial_start = event.dtstart
118
+ (0...@count).map {|day_offset|
119
+ occurrence = event.clone
120
+ occurrence.dtstart = initial_start + day_offset
121
+ occurrence.clone
122
+ }
123
+ end
124
+ end
125
+
126
+ end
@@ -0,0 +1,121 @@
1
+ =begin
2
+ Copyright (C) 2008 Sean Dague
3
+
4
+ This library is free software; you can redistribute it and/or modify it
5
+ under the same terms as the ruby language itself, see the file COPYING for
6
+ details.
7
+ =end
8
+
9
+ # The following adds a bunch of mixins to the tzinfo class, with the
10
+ # intent on making it very easy to load in tzinfo data for generating
11
+ # ical events. With this you can do the following:
12
+ #
13
+ # require "icalendar/tzinfo"
14
+ #
15
+ # estart = DateTime.new(2008, 12, 29, 8, 0, 0)
16
+ # eend = DateTime.new(2008, 12, 29, 11, 0, 0)
17
+ # tstring = "America/Chicago"
18
+ #
19
+ # tz = TZInfo::Timezone.get(tstring)
20
+ # cal = Calendar.new
21
+ # # the mixins now generate all the timezone info for the date in question
22
+ # timezone = tz.ical_timezone(estart)
23
+ # cal.add(timezone)
24
+ #
25
+ # cal.event do
26
+ # dtstart estart
27
+ # dtend eend
28
+ # summary "Meeting with the man."
29
+ # description "Have a long lunch meeting and decide nothing..."
30
+ # klass "PRIVATE"
31
+ # end
32
+ #
33
+ # puts cal.to_ical
34
+ #
35
+ # The recurance rule calculations are hacky, and only start at the
36
+ # beginning of the current dst transition. I doubt this works for non
37
+ # dst areas yet. However, for a standard dst flipping zone, this
38
+ # seems to work fine (tested in Mozilla Thunderbird + Lightning).
39
+ # Future goal would be making this better.
40
+
41
+ # require "rubygems"
42
+ # require "tzinfo"
43
+
44
+ module TZInfo
45
+ class Timezone
46
+ def ical_timezone(date)
47
+ period = period_for_local(date)
48
+ timezone = Icalendar::Timezone.new
49
+ timezone.timezone_id = identifier
50
+ timezone.add(period.daylight)
51
+ timezone.add(period.standard)
52
+ return timezone
53
+ end
54
+ end
55
+
56
+ class TimezoneTransitionInfo
57
+ def offset_from
58
+ a = previous_offset.utc_total_offset
59
+ sprintf("%2.2d%2.2d", (a / 3600).to_i, ((a / 60) % 60).to_i)
60
+ end
61
+
62
+ def offset_to
63
+ a = offset.utc_total_offset
64
+ sprintf("%2.2d%2.2d", (a / 3600).to_i, ((a / 60) % 60).to_i)
65
+ end
66
+
67
+ def rrule
68
+ start = local_start.to_datetime
69
+ # this is somewhat of a hack, but seems to work ok
70
+ [sprintf(
71
+ "FREQ=YEARLY;BYMONTH=%d;BYDAY=%d%s",
72
+ start.month,
73
+ ((start.day - 1)/ 7).to_i + 1,
74
+ start.strftime("%a").upcase[0,2]
75
+ )]
76
+ end
77
+
78
+ def dtstart
79
+ local_start.to_datetime.strftime("%Y%m%dT%H%M%S")
80
+ end
81
+
82
+ end
83
+
84
+ class TimezonePeriod
85
+ def daylight
86
+ day = Icalendar::Daylight.new
87
+ if dst?
88
+ day.timezone_name = abbreviation.to_s
89
+ day.timezone_offset_from = start_transition.offset_from
90
+ day.timezone_offset_to = start_transition.offset_to
91
+ day.dtstart = start_transition.dtstart
92
+ day.recurrence_rules = start_transition.rrule
93
+ else
94
+ day.timezone_name = abbreviation.to_s.sub("ST","DT")
95
+ day.timezone_offset_from = end_transition.offset_from
96
+ day.timezone_offset_to = end_transition.offset_to
97
+ day.dtstart = end_transition.dtstart
98
+ day.recurrence_rules = end_transition.rrule
99
+ end
100
+ return day
101
+ end
102
+
103
+ def standard
104
+ std = Icalendar::Standard.new
105
+ if dst?
106
+ std.timezone_name = abbreviation.to_s.sub("DT","ST")
107
+ std.timezone_offset_from = end_transition.offset_from
108
+ std.timezone_offset_to = end_transition.offset_to
109
+ std.dtstart = end_transition.dtstart
110
+ std.recurrence_rules = end_transition.rrule
111
+ else
112
+ std.timezone_name = abbreviation.to_s
113
+ std.timezone_offset_from = start_transition.offset_from
114
+ std.timezone_offset_to = start_transition.offset_to
115
+ std.dtstart = start_transition.dtstart
116
+ std.recurrence_rules = start_transition.rrule
117
+ end
118
+ return std
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,32 @@
1
+ # A set of methods to help create meta-programming gizmos.
2
+ class Object
3
+ # The metaclass is the singleton behind every object.
4
+ def metaclass
5
+ class << self
6
+ self
7
+ end
8
+ end
9
+
10
+ # Evaluates the block in the context of the metaclass
11
+ def meta_eval &blk
12
+ metaclass.instance_eval &blk
13
+ end
14
+
15
+ # Acts like an include except it adds the module's methods
16
+ # to the metaclass so they act like class methods.
17
+ def meta_include mod
18
+ meta_eval do
19
+ include mod
20
+ end
21
+ end
22
+
23
+ # Adds methods to a metaclass
24
+ def meta_def name, &blk
25
+ meta_eval { define_method name, &blk }
26
+ end
27
+
28
+ # Defines an instance method within a class
29
+ def class_def name, &blk
30
+ class_eval { define_method name, &blk }
31
+ end
32
+ end
@@ -0,0 +1,71 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'test/unit'
4
+ require 'icalendar'
5
+
6
+ require 'date'
7
+
8
+ class TestCalendar < Test::Unit::TestCase
9
+ include Icalendar
10
+ # Generate a calendar using the raw api, and then spit it out
11
+ # as a string. Parse the string and make sure everything matches up.
12
+ def test_raw_generation
13
+ # Create a fresh calendar
14
+ cal = Calendar.new
15
+
16
+ cal.calscale = "GREGORIAN"
17
+ cal.version = "3.2"
18
+ cal.prodid = "test-prodid"
19
+
20
+ # Now generate the string and then parse it so we can verify
21
+ # that everything was set, generated and parsed correctly.
22
+ calString = cal.to_ical
23
+
24
+ cals = Parser.new(calString).parse
25
+
26
+ cal2 = cals.first
27
+ assert_equal("GREGORIAN", cal2.calscale)
28
+ assert_equal("3.2", cal2.version)
29
+ assert_equal("test-prodid", cal2.prodid)
30
+ end
31
+
32
+ def test_block_creation
33
+ cal = Calendar.new
34
+ cal.event do
35
+ self.dtend = "19970903T190000Z"
36
+ self.summary = "This is my summary"
37
+ end
38
+
39
+ event = cal.event
40
+ event.dtend "19970903T190000Z", {:TZID => "Europe/Copenhagen"}
41
+ event.summary "This is my summary"
42
+
43
+ ev = cal.events.each do |ev|
44
+ assert_equal("19970903T190000Z", ev.dtend)
45
+ assert_equal("This is my summary", ev.summary)
46
+ end
47
+ end
48
+
49
+ def test_find
50
+ cal = Calendar.new
51
+
52
+ # add some events so we actually have to search
53
+ 10.times do
54
+ cal.event
55
+ cal.todo
56
+ cal.journal
57
+ cal.freebusy
58
+ end
59
+ event = cal.events[5]
60
+ assert_equal(event, cal.find_event(event.uid))
61
+
62
+ todo = cal.todos[5]
63
+ assert_equal(todo, cal.find_todo(todo.uid))
64
+
65
+ journal = cal.journals[5]
66
+ assert_equal(journal, cal.find_journal(journal.uid))
67
+
68
+ freebusy = cal.freebusys[5]
69
+ assert_equal(freebusy, cal.find_freebusy(freebusy.uid))
70
+ end
71
+ end
@@ -0,0 +1,256 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
+
3
+ require 'test/unit'
4
+ require 'icalendar'
5
+
6
+ unless defined? 1.days
7
+ class Integer
8
+ def days
9
+ self * 60 * 60 * 24
10
+ end
11
+ end
12
+ end
13
+
14
+ # Define a test event
15
+ testEvent = <<EOS
16
+ BEGIN:VEVENT
17
+ UID:19970901T130000Z-123401@host.com
18
+ DTSTAMP:19970901T1300Z
19
+ DTSTART:19970903T163000Z
20
+ DTEND:19970903T190000Z
21
+ SUMMARY:Annual Employee Review
22
+ CLASS:PRIVATE
23
+ CATEGORIES:BUSINESS,HUMAN RESOURCES
24
+ END:VEVENT
25
+ EOS
26
+
27
+ class TestEvent < Test::Unit::TestCase
28
+
29
+ # Create a calendar with an event for each test.
30
+ def setup
31
+ @cal = Icalendar::Calendar.new
32
+ @event = Icalendar::Event.new
33
+ end
34
+
35
+ def test_new
36
+ assert(@event)
37
+ end
38
+
39
+ # Properties that can only occur once per event
40
+ def test_single_properties
41
+ @event.ip_class = "PRIVATE"
42
+
43
+ @cal.add_event(@event)
44
+
45
+ cals = Icalendar::Parser.new(@cal.to_ical).parse
46
+ cal2 = cals.first
47
+ event2 = cal2.events.first
48
+
49
+ assert_equal("PRIVATE", event2.ip_class)
50
+ end
51
+ end
52
+
53
+ class TestEventWithSpecifiedTimezone < Test::Unit::TestCase
54
+
55
+ def setup
56
+ src = <<EOS
57
+ BEGIN:VCALENDAR
58
+ METHOD:PUBLISH
59
+ CALSCALE:GREGORIAN
60
+ VERSION:2.0
61
+ BEGIN:VEVENT
62
+ UID:19970901T130000Z-123401@host.com
63
+ DTSTAMP:19970901T1300Z
64
+ DTSTART;TZID=America/Chicago:19970903T163000
65
+ DTEND;TZID=America/Chicago:19970903T190000
66
+ SUMMARY:Annual Employee Review
67
+ CLASS:PRIVATE
68
+ CATEGORIES:BUSINESS,HUMAN RESOURCES
69
+ END:VEVENT
70
+ END:VCALENDAR
71
+ EOS
72
+ @calendar = Icalendar.parse(src).first
73
+ @event = @calendar.events.first
74
+ end
75
+
76
+ def test_event_is_parsed
77
+ assert_not_nil(@event)
78
+ end
79
+
80
+ def test_dtstart_should_understand_icalendar_tzid
81
+ assert_respond_to(@event.dtstart, :icalendar_tzid)
82
+ end
83
+
84
+ def test_dtstart_tzid_should_be_correct
85
+ puts "#{@event.dtstart.icalendar_tzid} #{@event.dtstart}"
86
+ assert_equal("America/Chicago",@event.dtstart.icalendar_tzid)
87
+ end
88
+
89
+ def test_dtend_tzid_should_be_correct
90
+ assert_equal("America/Chicago",@event.dtend.icalendar_tzid)
91
+ end
92
+
93
+ end
94
+
95
+ class TestEventWithZuluTimezone < Test::Unit::TestCase
96
+
97
+ def setup
98
+ src = <<EOS
99
+ BEGIN:VCALENDAR
100
+ METHOD:PUBLISH
101
+ CALSCALE:GREGORIAN
102
+ VERSION:2.0
103
+ BEGIN:VEVENT
104
+ UID:19970901T130000Z-123401@host.com
105
+ DTSTAMP:19970901T1300Z
106
+ DTSTART:19970903T163000Z
107
+ DTEND:19970903T190000Z
108
+ SUMMARY:Annual Employee Review
109
+ CLASS:PRIVATE
110
+ CATEGORIES:BUSINESS,HUMAN RESOURCES
111
+ END:VEVENT
112
+ END:VCALENDAR
113
+ EOS
114
+ @calendar = Icalendar.parse(src).first
115
+ @event = @calendar.events.first
116
+ end
117
+
118
+ def test_event_is_parsed
119
+ assert_not_nil(@event)
120
+ end
121
+
122
+ def test_dtstart_tzid_should_be_correct
123
+ puts "#{@event.dtstart.icalendar_tzid} #{@event.dtstart}"
124
+ assert_equal("UTC",@event.dtstart.icalendar_tzid)
125
+ end
126
+
127
+ def test_dtend_tzid_should_be_correct
128
+ assert_equal("UTC",@event.dtend.icalendar_tzid)
129
+ end
130
+
131
+ end
132
+
133
+ class TestEventWithFloatingTimezone < Test::Unit::TestCase
134
+
135
+ def setup
136
+ src = <<EOS
137
+ BEGIN:VCALENDAR
138
+ METHOD:PUBLISH
139
+ CALSCALE:GREGORIAN
140
+ VERSION:2.0
141
+ BEGIN:VEVENT
142
+ UID:19970901T130000Z-123401@host.com
143
+ DTSTAMP:19970901T1300Z
144
+ DTSTART:19970903T163000
145
+ DTEND:19970903T190000
146
+ SUMMARY:Annual Employee Review
147
+ CLASS:PRIVATE
148
+ CATEGORIES:BUSINESS,HUMAN RESOURCES
149
+ END:VEVENT
150
+ END:VCALENDAR
151
+ EOS
152
+ @calendar = Icalendar.parse(src).first
153
+ @event = @calendar.events.first
154
+ end
155
+
156
+ def test_event_is_parsed
157
+ assert_not_nil(@event)
158
+ end
159
+
160
+ def test_dtstart_tzid_should_be_nil
161
+ puts "#{@event.dtstart.icalendar_tzid.inspect} #{@event.dtstart}"
162
+ assert_nil(@event.dtstart.icalendar_tzid)
163
+ end
164
+
165
+ def test_dtend_tzid_should_be_nil
166
+ assert_nil(@event.dtend.icalendar_tzid)
167
+ end
168
+
169
+ end
170
+
171
+ class TestAllDayEventWithoutTime < Test::Unit::TestCase
172
+
173
+ def setup
174
+ src = <<EOS
175
+ BEGIN:VCALENDAR
176
+ VERSION:2.0
177
+ X-WR-CALNAME:New Event
178
+ PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
179
+ X-WR-RELCALID:3A016BE7-8932-4456-8ABD-C8F7EEC5963A
180
+ X-WR-TIMEZONE:Europe/London
181
+ CALSCALE:GREGORIAN
182
+ METHOD:PUBLISH
183
+ BEGIN:VEVENT
184
+ DTSTART;VALUE=DATE:20090110
185
+ DTEND;VALUE=DATE:20090111
186
+ SUMMARY:New Event
187
+ UID:3829F33C-F601-49AC-A3A5-C3AC4A6A3483
188
+ SEQUENCE:4
189
+ DTSTAMP:20090109T184719Z
190
+ END:VEVENT
191
+ END:VCALENDAR
192
+ EOS
193
+ @calendar = Icalendar.parse(src).first
194
+ @event = @calendar.events.first
195
+ end
196
+
197
+ def test_event_is_parsed
198
+ assert_not_nil(@event)
199
+ end
200
+
201
+ def test_dtstart_set_correctly
202
+ assert_equal("20090110", @event.dtstart.to_ical)
203
+ end
204
+
205
+ end
206
+
207
+ class TestRecurringEventWithCount < Test::Unit::TestCase
208
+ # DTSTART;TZID=US-Eastern:19970902T090000
209
+ # RRULE:FREQ=DAILY;COUNT=10
210
+ # ==> (1997 9:00 AM EDT)September 2-11
211
+
212
+ def setup
213
+ src = <<EOS
214
+ BEGIN:VCALENDAR
215
+ METHOD:PUBLISH
216
+ CALSCALE:GREGORIAN
217
+ VERSION:2.0
218
+ BEGIN:VEVENT
219
+ UID:19970901T130000Z-123401@host.com
220
+ DTSTAMP:19970901T1300Z
221
+ DTSTART:19970902T090000Z
222
+ DTEND:19970902T100000Z
223
+ RRULE:FREQ=DAILY;COUNT=10
224
+ SUMMARY:Annual Employee Review
225
+ CLASS:PRIVATE
226
+ CATEGORIES:BUSINESS,HUMAN RESOURCES
227
+ END:VEVENT
228
+ END:VCALENDAR
229
+ EOS
230
+ @calendar = Icalendar.parse(src).first
231
+ @event = @calendar.events.first
232
+ end
233
+
234
+ def test_event_is_parsed
235
+ assert_not_nil(@event)
236
+ end
237
+
238
+ def test_recurrence_rules_should_return_a_recurrence_rule_array
239
+ assert_equal 1, @event.recurrence_rules.length
240
+ assert_kind_of(Icalendar::RRule, @event.recurrence_rules.first)
241
+ end
242
+
243
+ def test_occurrences_after_with_start_before_start_at_should_return_count_occurrences
244
+ assert_equal 10, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).length
245
+ end
246
+
247
+ # def test_occurrences_after_with_start_before_start_at_should_return_an_event_with_the_dtstart_as_the_first_event
248
+ # assert_equal @event.dtstart.to_s, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).first.dtstart.to_s
249
+ # end
250
+ #
251
+ # def test_occurrences_after_with_start_before_start_at_should_return_events_with_the_correct_dtstart_values
252
+ # expected = (0..9).map {|delta| (@event.dtstart + delta).to_s}
253
+ # assert_equal expected, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).map {|occurence| occurence.dtstart.to_s}
254
+ # end
255
+ end
256
+