groesser3-gcal4ruby 0.5.51

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,446 @@
1
+ # Author:: Mike Reich (mike@seabourneconsulting.com)
2
+ # Copyright:: Copyright (C) 2010 Mike Reich
3
+ # License:: GPL v2
4
+ #--
5
+ # Licensed under the General Public License (GPL), Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ #
15
+ # Feel free to use and update, but be sure to contribute your
16
+ # code back to the project and attribute as required by the license.
17
+ #++
18
+
19
+ require 'gcal4ruby/recurrence'
20
+
21
+ module GCal4Ruby
22
+ #The Event Class represents a remote event in calendar
23
+ #
24
+ #=Usage
25
+ #All usages assume a successfully authenticated Service and valid Calendar.
26
+ #1. Create a new Event
27
+ # event = Event.new(service, {:calendar => cal, :title => "Soccer Game", :start => Time.parse("12-06-2009 at 12:30 PM"), :end => Time.parse("12-06-2009 at 1:30 PM"), :where => "Merry Playfields"})
28
+ # event.save
29
+ #
30
+ #2. Find an existing Event by title
31
+ # event = Event.find(service, {:title => "Soccer Game"})
32
+ #
33
+ #3. Find an existing Event by ID
34
+ # event = Event.find(service, {:id => event.id})
35
+ #
36
+ #4. Find all events containing the search term
37
+ # event = Event.find(service, "Soccer Game")
38
+ #
39
+ #5. Find all events on a calendar containing the search term
40
+ # event = Event.find(service, "Soccer Game", {:calendar => cal.id})
41
+ #
42
+ #6. Find events within a date range
43
+ # event = Event.find(service, "Soccer Game", {'start-min' => Time.parse("01/01/2010").utc.xmlschema, 'start-max' => Time.parse("06/01/2010").utc.xmlschema})
44
+ #
45
+ #7. Create a recurring event for every saturday
46
+ # event = Event.new(service)
47
+ # event.title = "Baseball Game"
48
+ # event.calendar = cal
49
+ # event.where = "Municipal Stadium"
50
+ # event.recurrence = Recurrence.new
51
+ # event.recurrence.start_time = Time.parse("06/20/2009 at 4:30 PM")
52
+ # event.recurrence.end_time = Time.parse("06/20/2009 at 6:30 PM")
53
+ # event.recurrence.frequency = {"weekly" => ["SA"]}
54
+ # event.save
55
+ #
56
+ #8. Create an event with a 15 minute email reminder
57
+ # event = Event.new(service)
58
+ # event.calendar = cal
59
+ # event.title = "Dinner with Kate"
60
+ # event.start_time = Time.parse("06/20/2009 at 5 pm")
61
+ # event.end_time = Time.parse("06/20/2009 at 8 pm")
62
+ # event.where = "Luigi's"
63
+ # event.reminder = [{:minutes => 15, :method => 'email'}]
64
+ # event.save
65
+ #
66
+ #9. Create an event with attendees
67
+ # event = Event.new(service)
68
+ # event.calendar = cal
69
+ # event.title = "Dinner with Kate"
70
+ # event.start_time = Time.parse("06/20/2009 at 5 pm")
71
+ # event.end_time = Time.parse("06/20/2009 at 8 pm")
72
+ # event.attendees => {:name => "Kate", :email => "kate@gmail.com"}
73
+ # event.save
74
+ #
75
+ #After an event object has been created or loaded, you can change any of the
76
+ #attributes like you would any other object. Be sure to save the event to write changes
77
+ #to the Google Calendar service.
78
+
79
+ class Event < GData4Ruby::GDataObject
80
+ EVENT_QUERY_FEED = "https://www.google.com/calendar/feeds/default/private/full/"
81
+ EVENT_XML = "<entry xmlns='http://www.w3.org/2005/Atom'
82
+ xmlns:gd='http://schemas.google.com/g/2005'>
83
+ <category scheme='http://schemas.google.com/g/2005#kind'
84
+ term='http://schemas.google.com/g/2005#event'></category>
85
+ <title type='text'></title>
86
+ <content type='text'></content>
87
+ <gd:transparency
88
+ value='http://schemas.google.com/g/2005#event.opaque'>
89
+ </gd:transparency>
90
+ <gd:eventStatus
91
+ value='http://schemas.google.com/g/2005#event.confirmed'>
92
+ </gd:eventStatus>
93
+ <gd:where valueString=''></gd:where>
94
+ <gd:when startTime=''
95
+ endTime=''></gd:when>
96
+ </entry>"
97
+ STATUS = {:confirmed => "http://schemas.google.com/g/2005#event.confirmed",
98
+ :tentative => "http://schemas.google.com/g/2005#event.tentative",
99
+ :cancelled => "http://schemas.google.com/g/2005#event.canceled"}
100
+
101
+ TRANSPARENCY = {:free => "http://schemas.google.com/g/2005#event.transparent",
102
+ :busy => "http://schemas.google.com/g/2005#event.opaque"}
103
+
104
+ #The content for the event
105
+ attr_accessor :content
106
+ #The location of the event
107
+ attr_accessor :where
108
+ #A flag for whether the event show as :free or :busy
109
+ attr_accessor :transparency
110
+ #A flag indicating the status of the event. Values can be :confirmed, :tentative or :cancelled
111
+ attr_accessor :status
112
+ #Flag indicating whether it is an all day event
113
+ attr_reader :all_day
114
+ #An array of reminders. Each item in the array is a hash representing the event reminder.
115
+ attr_reader :reminder
116
+ #The date the event was last edited
117
+ attr_reader :edited
118
+ #Id of the parent calendar
119
+ attr_reader :calendar_id
120
+
121
+ #Creates a new Event. Accepts a valid Service object and optional attributes hash.
122
+ def initialize(service, attributes = {})
123
+ super(service, attributes)
124
+ @xml = EVENT_XML
125
+ @transparency ||= :busy
126
+ @status ||= :confirmed
127
+ @attendees ||= []
128
+ @all_day ||= false
129
+ @reminder = []
130
+ attributes.each do |key, value|
131
+ self.send("#{key}=", value)
132
+ end
133
+ end
134
+
135
+ #Sets the reminder options for the event. Parameter must be an array of hashes containing a :minutes key with a value of 5 up to 40320 (4 weeks)
136
+ #and a :method key of with a value of one the following:
137
+ #alert:: causes an alert to appear when a user is viewing the calendar in a browser
138
+ #email:: sends the user an email message
139
+ def reminder=(r)
140
+ @reminder = r
141
+ end
142
+
143
+ #Returns the current event's Recurrence information
144
+ def recurrence
145
+ @recurrence
146
+ end
147
+
148
+ #Returns an array of the current attendees
149
+ def attendees
150
+ @attendees
151
+ end
152
+
153
+ def all_day=(value)
154
+ puts 'all_day value = '+value.to_s if service.debug
155
+ if value.is_a? String
156
+ @all_day = true if value.downcase == 'true'
157
+ @all_day = false if value.downcase == 'false'
158
+ else
159
+ @all_day = value
160
+ end
161
+ puts 'after all_day value = '+@all_day.to_s if service.debug
162
+ @all_day
163
+ end
164
+
165
+ #Accepts an array of email address/name pairs for attendees.
166
+ # [{:name => 'Mike Reich', :email => 'mike@seabourneconsulting.com'}]
167
+ #The email address is requried, but the name is optional
168
+ def attendees=(a)
169
+ raise ArgumentError, "Attendees must be an Array of email/name hash pairs" if not a.is_a?(Array)
170
+ @attendees = a
171
+ end
172
+
173
+ #Sets the event's recurrence information to a Recurrence object. Returns the recurrence if successful,
174
+ #false otherwise
175
+ def recurrence=(r)
176
+ raise ArgumentError, 'Recurrence must be a Recurrence object' if not r.is_a?(Recurrence)
177
+ @recurrence = r
178
+ end
179
+
180
+ #Returns a duplicate of the current event as a new Event object
181
+ def copy()
182
+ e = Event.new(service)
183
+ e.load(to_xml)
184
+ e.calendar = @calendar
185
+ return e
186
+ end
187
+
188
+ #Sets the start time of the Event. Must be a Time object or a parsable string representation
189
+ #of a time.
190
+ def start_time=(str)
191
+ raise ArgumentError, "Start Time must be either Time or String" if not str.is_a?String and not str.is_a?Time
192
+ @start_time = if str.is_a?String
193
+ Time.parse(str)
194
+ elsif str.is_a?Time
195
+ str
196
+ end
197
+ end
198
+
199
+ #Sets the end time of the Event. Must be a Time object or a parsable string representation
200
+ #of a time.
201
+ def end_time=(str)
202
+ raise ArgumentError, "End Time must be either Time or String" if not str.is_a?String and not str.is_a?Time
203
+ @end_time = if str.is_a?String
204
+ Time.parse(str)
205
+ elsif str.is_a?Time
206
+ str
207
+ end
208
+ end
209
+
210
+ #The event start time. If a recurring event, the recurrence start time.
211
+ def start_time
212
+ return @start_time ? @start_time : @recurrence ? @recurrence .start_time : nil
213
+ end
214
+
215
+ #The event end time. If a recurring event, the recurrence end time.
216
+ def end_time
217
+ return @end_time ? @end_time : @recurrence ? @recurrence.end_time : nil
218
+ end
219
+
220
+
221
+ #If the event does not exist on the Google Calendar service, save creates it. Otherwise
222
+ #updates the existing event data. Returns true on success, false otherwise.
223
+ def save
224
+ raise CalendarNotEditable if not calendar.editable
225
+ super
226
+ end
227
+
228
+ #Creates a new event
229
+ def create
230
+ service.send_request(GData4Ruby::Request.new(:post, @parent_calendar.content_uri, to_xml))
231
+ end
232
+
233
+ #Returns an XML representation of the event.
234
+ def to_xml()
235
+ xml = REXML::Document.new(super)
236
+ xml.root.elements.each(){}.map do |ele|
237
+ case ele.name
238
+ when "content"
239
+ ele.text = @content
240
+ when "when"
241
+ if not @recurrence
242
+ puts 'all_day = '+@all_day.to_s if service.debug
243
+ if @all_day
244
+ puts 'saving as all-day event' if service.debug
245
+ else
246
+ puts 'saving as timed event' if service.debug
247
+ end
248
+ ele.attributes["startTime"] = @all_day ? @start_time.strftime("%Y-%m-%d") : @start_time.utc.xmlschema
249
+ ele.attributes["endTime"] = @all_day ? @end_time.strftime("%Y-%m-%d") : @end_time.utc.xmlschema
250
+ set_reminder(ele)
251
+ else
252
+ xml.root.delete_element("/entry/gd:when")
253
+ ele = xml.root.add_element("gd:recurrence")
254
+ ele.text = @recurrence.to_recurrence_string
255
+ set_reminder(ele) if @reminder
256
+ end
257
+ when "eventStatus"
258
+ ele.attributes["value"] = STATUS[@status]
259
+ when "transparency"
260
+ ele.attributes["value"] = TRANSPARENCY[@transparency]
261
+ when "where"
262
+ ele.attributes["valueString"] = @where
263
+ when "recurrence"
264
+ puts 'recurrence element found' if service.debug
265
+ if @recurrence
266
+ puts 'setting recurrence' if service.debug
267
+ ele.text = @recurrence.to_recurrence_string
268
+ else
269
+ puts 'no recurrence, adding when' if service.debug
270
+ w = xml.root.add_element("gd:when")
271
+ xml.root.delete_element("/entry/gd:recurrence")
272
+ w.attributes["startTime"] = @all_day ? @start_time.strftime("%Y-%m-%d") : @start_time.xmlschema
273
+ w.attributes["endTime"] = @all_day ? @end_time.strftime("%Y-%m-%d") : @end_time.xmlschema
274
+ set_reminder(w)
275
+ end
276
+ end
277
+ end
278
+ if not @attendees.empty?
279
+ xml.root.elements.delete_all "gd:who"
280
+ @attendees.each do |a|
281
+ xml.root.add_element("gd:who", {"email" => a[:email], "valueString" => a[:name], "rel" => "http://schemas.google.com/g/2005#event.attendee"})
282
+ end
283
+ end
284
+ xml.to_s
285
+ end
286
+
287
+ #The event's parent calendar
288
+ def calendar
289
+ @parent_calendar = Calendar.find(service, {:id => @calendar_id}) if not @parent_calendar and @calendar_id
290
+ return @parent_calendar
291
+ end
292
+
293
+ #Sets the event's calendar
294
+ def calendar=(p)
295
+ raise ArgumentError, 'Value must be a valid Calendar object' if not p.is_a? Calendar
296
+ @parent_calendar = p
297
+ end
298
+
299
+ #Loads the event info from an XML string.
300
+ def load(string)
301
+ super(string)
302
+ @xml = string
303
+ @exists = true
304
+ xml = REXML::Document.new(string)
305
+ @etag = xml.root.attributes['etag']
306
+ xml.root.elements.each(){}.map do |ele|
307
+ case ele.name
308
+ when 'id'
309
+ @calendar_id, @id = @feed_uri.gsub("http://www.google.com/calendar/feeds/", "").split("/events/")
310
+ @id = "#{@calendar_id}/private/full/#{@id}"
311
+ when 'edited'
312
+ @edited = Time.parse(ele.text)
313
+ when 'content'
314
+ @content = ele.text
315
+ when "when"
316
+ @start_time = Time.parse(ele.attributes['startTime'])
317
+ @end_time = Time.parse(ele.attributes['endTime'])
318
+ @all_day = !ele.attributes['startTime'].include?('T')
319
+ @reminder = []
320
+ ele.elements.each("gd:reminder") do |r|
321
+ rem = {}
322
+ rem[:minutes] = r.attributes['minutes'] if r.attributes['minutes']
323
+ rem[:method] = r.attributes['method'] if r.attributes['method']
324
+ @reminder << rem
325
+ end
326
+ when "where"
327
+ @where = ele.attributes['valueString']
328
+ when "link"
329
+ if ele.attributes['rel'] == 'edit'
330
+ @edit_feed = ele.attributes['href']
331
+ end
332
+ when "who"
333
+ @attendees << {:email => ele.attributes['email'], :name => ele.attributes['valueString'], :role => ele.attributes['rel'].gsub("http://schemas.google.com/g/2005#event.", ""), :status => ele.elements["gd:attendeeStatus"] ? ele.elements["gd:attendeeStatus"].attributes['value'].gsub("http://schemas.google.com/g/2005#event.", "") : ""}
334
+ when "eventStatus"
335
+ @status = ele.attributes["value"].gsub("http://schemas.google.com/g/2005#event.", "").to_sym
336
+ when 'recurrence'
337
+ @recurrence = Recurrence.new(ele.text)
338
+ when "transparency"
339
+ @transparency = case ele.attributes["value"]
340
+ when "http://schemas.google.com/g/2005#event.transparent" then :free
341
+ when "http://schemas.google.com/g/2005#event.opaque" then :busy
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ #Reloads the event data from the Google Calendar Service. Returns true if successful,
348
+ #false otherwise.
349
+ def reload
350
+ return false if not @exists
351
+ t = Event.find(service, {:id => @id})
352
+ if t and load(t.to_xml)
353
+ return true
354
+ end
355
+ return false
356
+ end
357
+
358
+ #Finds an Event based on a text query or by an id. Parameters are:
359
+ #*service*:: A valid Service object to search.
360
+ #*query*:: either a string containing a text query to search by, or a hash containing an +id+ key with an associated id to find, or a +query+ key containint a text query to search for, or a +title+ key containing a title to search. All searches are case insensitive.
361
+ #*args*:: a hash containing optional additional query paramters to use. Limit a search to a single calendar by passing the calendar id as {:calendar => calendar.id}. See here[http://code.google.com/apis/calendar/data/2.0/developers_guide_protocol.html#RetrievingEvents] and here[http://code.google.com/apis/gdata/docs/2.0/reference.html#Queries] for a full list of possible values. Example:
362
+ # {'max-results' => '100'}
363
+ #If an ID is specified, a single instance of the event is returned if found, otherwise false.
364
+ #If a query term or title text is specified, and array of matching results is returned, or an empty array if nothing
365
+ #was found.
366
+ def self.find(service, query, args = {})
367
+ raise ArgumentError, 'query must be a hash or string' if not query.is_a? Hash and not query.is_a? String
368
+ if query.is_a? Hash and query[:id]
369
+ id = query[:id]
370
+ puts "id passed, finding event by id" if service.debug
371
+ puts "id = "+id if service.debug
372
+ d = service.send_request(GData4Ruby::Request.new(:get, "https://www.google.com/calendar/feeds/"+id, {"If-Not-Match" => "*"}))
373
+ puts d.inspect if service.debug
374
+ if d
375
+ return get_instance(service, d)
376
+ end
377
+ else
378
+ results = []
379
+ if query.is_a?(Hash)
380
+ args["q"] = query[:query] if query[:query]
381
+ args['title'] = query[:title] if query[:title]
382
+ else
383
+ args["q"] = CGI::escape(query) if query != ''
384
+ end
385
+ if args[:calendar]
386
+ cal = Calendar.find(service, {:id => args[:calendar]})
387
+ args.delete(:calendar)
388
+ ret = service.send_request(GData4Ruby::Request.new(:get, cal.content_uri, nil, nil, args))
389
+ xml = REXML::Document.new(ret.body).root
390
+ xml.elements.each("entry") do |e|
391
+ results << get_instance(service, e)
392
+ end
393
+ else
394
+ service.calendars.each do |cal|
395
+ ret = service.send_request(GData4Ruby::Request.new(:get, cal.content_uri, nil, nil, args))
396
+ xml = REXML::Document.new(ret.body).root
397
+ xml.elements.each("entry") do |e|
398
+ results << get_instance(service, e)
399
+ end
400
+ end
401
+ end
402
+ return results
403
+ end
404
+ return false
405
+ end
406
+
407
+ #Returns true if the event exists on the Google Calendar Service.
408
+ def exists?
409
+ return @exists
410
+ end
411
+
412
+ private
413
+ def set_reminder(ele)
414
+ num = ele.elements.delete_all "gd:reminder"
415
+ puts 'num = '+num.size.to_s if service.debug
416
+ if @reminder
417
+ @reminder.each do |reminder|
418
+ puts 'reminder added' if service.debug
419
+ e = ele.add_element("gd:reminder")
420
+ e.attributes['minutes'] = reminder[:minutes].to_s if reminder[:minutes]
421
+ if reminder[:method]
422
+ e.attributes['method'] = reminder[:method]
423
+ else
424
+ e.attributes['method'] = 'email'
425
+ end
426
+ end
427
+ end
428
+ end
429
+
430
+ def self.get_instance(service, d)
431
+ if d.is_a? Net::HTTPOK
432
+ xml = REXML::Document.new(d.read_body).root
433
+ if xml.name == 'feed'
434
+ xml = xml.elements.each("entry"){}[0]
435
+ end
436
+ else
437
+ xml = d
438
+ end
439
+ ele = GData4Ruby::Utils::add_namespaces(xml)
440
+ e = Event.new(service)
441
+ e.load(ele.to_s)
442
+ e
443
+ end
444
+ end
445
+ end
446
+
@@ -0,0 +1,263 @@
1
+ # Author:: Mike Reich (mike@seabourneconsulting.com)
2
+ # Copyright:: Copyright (C) 2010 Mike Reich
3
+ # License:: GPL v2
4
+ #--
5
+ # Licensed under the General Public License (GPL), Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ #
15
+ # Feel free to use and update, but be sure to contribute your
16
+ # code back to the project and attribute as required by the license.
17
+ #++
18
+
19
+ class Time
20
+ #Returns a ISO 8601 complete formatted string of the time
21
+ def complete
22
+ self.utc.strftime("%Y%m%dT%H%M%S")
23
+ end
24
+
25
+ def self.parse_complete(value)
26
+ d, h = value.split("T")
27
+ return Time.parse(d+" "+h.gsub("Z", ""))
28
+ end
29
+ end
30
+
31
+ module GCal4Ruby
32
+ #The Recurrence class stores information on an Event's recurrence. The class implements
33
+ #the RFC 2445 iCalendar recurrence description.
34
+ class Recurrence
35
+ #The event start date/time
36
+ attr_reader :start_time
37
+ #The event end date/time
38
+ attr_reader :end_time
39
+ #the event reference
40
+ attr_reader :event
41
+ #The date until which the event will be repeated
42
+ attr_reader :repeat_until
43
+ #The event frequency
44
+ attr_reader :frequency
45
+ #True if the event is all day (i.e. no start/end time)
46
+ attr_accessor :all_day
47
+
48
+ #Accepts an optional attributes hash or a string containing a properly formatted ISO 8601 recurrence rule. Returns a new Recurrence object
49
+ def initialize(vars = {})
50
+ if vars.is_a? Hash
51
+ vars.each do |key, value|
52
+ self.send("#{key}=", value)
53
+ end
54
+ elsif vars.is_a? String
55
+ self.load(vars)
56
+ end
57
+ @all_day ||= false
58
+ end
59
+
60
+ #Accepts a string containing a properly formatted ISO 8601 recurrence rule and loads it into the recurrence object.
61
+ #Contributed by John Paul Narowski.
62
+ def load(rec)
63
+ @frequency = {}
64
+ attrs = rec.split("\n")
65
+ attrs.each do |val|
66
+ key, value = val.split(":")
67
+ if key == 'RRULE'
68
+ value.split(";").each do |rr|
69
+ rr_key, rr_value = rr.split("=")
70
+ rr_key = rr_key.downcase.to_sym
71
+ unless @frequency.has_key?(rr_key)
72
+ if rr_key == :until
73
+ @repeat_until = Time.parse_complete(rr_value)
74
+ else
75
+ @frequency[rr_key] = rr_value
76
+ end
77
+ end
78
+ end
79
+ elsif key == 'INTERVAL'
80
+ @frequency[:inverval] = value.to_i
81
+ elsif key.include?("DTSTART;TZID") or key.include?("DTSTART") or key.include?('DTSTART;VALUE=DATE-TIME')
82
+ @start_time = Time.parse_complete(value)
83
+ elsif key.include?('DTSTART;VALUE=DATE')
84
+ @start_time = Time.parse(value)
85
+ @all_day = true
86
+ elsif key.include?("DTEND;TZID") or key.include?("DTEND") or key.include?('DTEND;VALUE=DATE-TIME')
87
+ @end_time = Time.parse_complete(value)
88
+ elsif key.include?('DTEND;VALUE=DATE')
89
+ @end_time = Time.parse(value)
90
+ end
91
+ end
92
+ end
93
+
94
+ def to_s
95
+ output = ''
96
+ if @frequency
97
+ f = ''
98
+ i = ''
99
+ by = ''
100
+ @frequency.each do |key, v|
101
+ if v.is_a?(Array)
102
+ if v.size > 0
103
+ value = v.join(",")
104
+ else
105
+ value = nil
106
+ end
107
+ else
108
+ value = v
109
+ end
110
+ f += "#{key.downcase} " if key != 'interval'
111
+ case key.downcase
112
+ when "secondly"
113
+ by += "every #{value} second"
114
+ when "minutely"
115
+ by += "every #{value} minute"
116
+ when "hourly"
117
+ by += "every #{value} hour"
118
+ when "weekly"
119
+ by += "on #{value}" if value
120
+ when "monthly"
121
+ by += "on #{value}"
122
+ when "yearly"
123
+ by += "on the #{value} day of the year"
124
+ when 'interval'
125
+ i += "for #{value} times"
126
+ end
127
+ end
128
+ output += f+i+by
129
+ end
130
+ if @repeat_until
131
+ output += " and repeats until #{@repeat_until.strftime("%m/%d/%Y")}"
132
+ end
133
+ output
134
+ end
135
+
136
+ #Returns a string with the correctly formatted ISO 8601 recurrence rule
137
+ def to_recurrence_string
138
+ output = ''
139
+ if @all_day
140
+ output += "DTSTART;VALUE=DATE:#{@start_time.utc.strftime("%Y%m%d")}\n"
141
+ else
142
+ output += "DTSTART;VALUE=DATE-TIME:#{@start_time.complete}\n"
143
+ end
144
+ if @all_day
145
+ output += "DTEND;VALUE=DATE:#{@end_time.utc.strftime("%Y%m%d")}\n"
146
+ else
147
+ output += "DTEND;VALUE=DATE-TIME:#{@end_time.complete}\n"
148
+ end
149
+ output += "RRULE:"
150
+ if @frequency
151
+ f = 'FREQ='
152
+ i = ''
153
+ by = ''
154
+ @frequency.each do |key, v|
155
+ if v.is_a?(Array)
156
+ if v.size > 0
157
+ value = v.join(",")
158
+ else
159
+ value = nil
160
+ end
161
+ else
162
+ value = v
163
+ end
164
+ f += "#{key.to_s.upcase};" if key != 'interval'
165
+ case key.to_s.downcase
166
+ when "secondly"
167
+ by += "BYSECOND=#{value};"
168
+ when "minutely"
169
+ by += "BYMINUTE=#{value};"
170
+ when "hourly"
171
+ by += "BYHOUR=#{value};"
172
+ when "weekly"
173
+ by += "BYDAY=#{value};" if value
174
+ when "monthly"
175
+ by += "BYDAY=#{value};"
176
+ when "yearly"
177
+ by += "BYYEARDAY=#{value};"
178
+ when 'interval'
179
+ i += "INTERVAL=#{value};"
180
+ end
181
+ end
182
+ output += f+i+by
183
+ end
184
+ if @repeat_until
185
+ output += "UNTIL=#{@repeat_until.strftime("%Y%m%d")}"
186
+ end
187
+
188
+ output += "\n"
189
+ end
190
+
191
+ #Sets the start date/time. Must be a Time object.
192
+ def start_time=(s)
193
+ if not s.is_a?(Time)
194
+ raise RecurrenceValueError, "Start must be a date or a time"
195
+ else
196
+ @start_time = s
197
+ end
198
+ end
199
+
200
+ #Sets the end Date/Time. Must be a Time object.
201
+ def end_time=(e)
202
+ if not e.is_a?(Time)
203
+ raise RecurrenceValueError, "End must be a date or a time"
204
+ else
205
+ @end_time = e
206
+ end
207
+ end
208
+
209
+ #Sets the parent event reference
210
+ def event=(e)
211
+ if not e.is_a?(Event)
212
+ raise RecurrenceValueError, "Event must be an event"
213
+ else
214
+ @event = e
215
+ end
216
+ end
217
+
218
+ #Sets the end date for the recurrence
219
+ def repeat_until=(r)
220
+ if not r.is_a?(Date)
221
+ raise RecurrenceValueError, "Repeat_until must be a date"
222
+ else
223
+ @repeat_until = r
224
+ end
225
+ end
226
+
227
+ #Sets the frequency of the recurrence. Should be a hash with one of
228
+ #"SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" as the key,
229
+ #and as the value, an array containing zero to n of the following:
230
+ #- *Secondly*: A value between 0 and 59. Causes the event to repeat on that second of each minut.
231
+ #- *Minutely*: A value between 0 and 59. Causes the event to repeat on that minute of every hour.
232
+ #- *Hourly*: A value between 0 and 23. Causes the even to repeat on that hour of every day.
233
+ #- *Daily*: No value needed - will cause the event to repeat every day until the repeat_until date.
234
+ #- *Weekly*: A value of the first two letters of a day of the week. Causes the event to repeat on that day.
235
+ #- *Monthly*: A value of a positive or negative integer (i.e. +1) prepended to a day-of-week string ('TU') to indicate the position of the day within the month. E.g. +1TU would be the first tuesday of the month.
236
+ #- *Yearly*: A value of 1 to 366 indicating the day of the year. May be negative to indicate counting down from the last day of the year.
237
+ #
238
+ #Optionally, you may specific a second hash pair to set the interval the event repeats:
239
+ # "interval" => '2'
240
+ #If the interval is missing, it is assumed to be 1.
241
+ #
242
+ #===Examples
243
+ #Repeat event every Tuesday:
244
+ # frequency = {"Weekly" => ["TU"]}
245
+ #
246
+ #Repeat every first and third Monday of the month
247
+ # frequency = {"Monthly" => ["+1MO", "+3MO"]}
248
+ #
249
+ #Repeat on the last day of every year
250
+ # frequency = {"Yearly" => [366]}
251
+ #
252
+ #Repeat every other week on Friday
253
+ # frequency = {"Weekly" => ["FR"], "interval" => "2"}
254
+
255
+ def frequency=(f)
256
+ if f.is_a?(Hash)
257
+ @frequency = f
258
+ else
259
+ raise RecurrenceValueError, "Frequency must be a hash (see documentation)"
260
+ end
261
+ end
262
+ end
263
+ end