paulsm-icalendar 1.1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+