vcita-gcal4ruby 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,449 @@
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 = "http://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 a calendar object as {:calender => calendar} or 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, "http://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 = args[:calendar].is_a?(Calendar) ? args[:calendar] : 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
+ puts "Event.find() searching calendar: " if service.debug
396
+ puts cal.content_uri if service.debug
397
+ puts args.inspect if service.debug
398
+ ret = service.send_request(GData4Ruby::Request.new(:get, cal.content_uri, nil, nil, args))
399
+ xml = REXML::Document.new(ret.body).root
400
+ xml.elements.each("entry") do |e|
401
+ results << get_instance(service, e)
402
+ end
403
+ end
404
+ end
405
+ return results
406
+ end
407
+ return false
408
+ end
409
+
410
+ #Returns true if the event exists on the Google Calendar Service.
411
+ def exists?
412
+ return @exists
413
+ end
414
+
415
+ private
416
+ def set_reminder(ele)
417
+ num = ele.elements.delete_all "gd:reminder"
418
+ puts 'num = '+num.size.to_s if service.debug
419
+ if @reminder
420
+ @reminder.each do |reminder|
421
+ puts 'reminder added' if service.debug
422
+ e = ele.add_element("gd:reminder")
423
+ e.attributes['minutes'] = reminder[:minutes].to_s if reminder[:minutes]
424
+ if reminder[:method]
425
+ e.attributes['method'] = reminder[:method]
426
+ else
427
+ e.attributes['method'] = 'email'
428
+ end
429
+ end
430
+ end
431
+ end
432
+
433
+ def self.get_instance(service, d)
434
+ if d.is_a? Net::HTTPOK
435
+ xml = REXML::Document.new(d.read_body).root
436
+ if xml.name == 'feed'
437
+ xml = xml.elements.each("entry"){}[0]
438
+ end
439
+ else
440
+ xml = d
441
+ end
442
+ ele = GData4Ruby::Utils::add_namespaces(xml)
443
+ e = Event.new(service)
444
+ e.load(ele.to_s)
445
+ e
446
+ end
447
+ end
448
+ end
449
+
@@ -0,0 +1,302 @@
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
+
21
+ #Returns a ISO 8601 complete formatted string of the time
22
+ def complete
23
+ self.utc.strftime("%Y%m%dT%H%M%S")
24
+ end
25
+
26
+ def self.parse_complete(value)
27
+ unless value.nil? || value.empty?
28
+ if value.include?("T")
29
+ d, h = value.split("T")
30
+ return Time.parse(d+" "+(h || "").gsub("Z", ""))
31
+ else
32
+ value = value.to_s
33
+ return Time.parse("#{value[0..3]}-#{value[4..5]}-#{value[6..7]}")
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ module GCal4Ruby
41
+ #The Recurrence class stores information on an Event's recurrence. The class implements
42
+ #the RFC 2445 iCalendar recurrence description.
43
+ class Recurrence
44
+ #The event start date/time
45
+ attr_reader :start_time
46
+ #The event end date/time
47
+ attr_reader :end_time
48
+ #the event reference
49
+ attr_reader :event
50
+ #The date until which the event will be repeated
51
+ attr_reader :repeat_until
52
+ #The event frequency
53
+ attr_reader :frequency
54
+ #True if the event is all day (i.e. no start/end time)
55
+ attr_accessor :all_day
56
+
57
+ #Accepts an optional attributes hash or a string containing a properly formatted ISO 8601 recurrence rule. Returns a new Recurrence object
58
+ def initialize(vars = {})
59
+ if vars.is_a? Hash
60
+ vars.each do |key, value|
61
+ self.send("#{key}=", value)
62
+ end
63
+ elsif vars.is_a? String
64
+ self.load(vars)
65
+ end
66
+ @all_day ||= false
67
+ end
68
+
69
+ #Accepts a string containing a properly formatted ISO 8601 recurrence rule and loads it into the recurrence object.
70
+ #Contributed by John Paul Narowski.
71
+ def load(rec)
72
+ @frequency = {}
73
+ attrs = rec.split("\n")
74
+ attrs.each do |val|
75
+ break if val == "BEGIN:VTIMEZONE" # Ignoring the time zone for now
76
+ key, value = val.split(":")
77
+ if key == 'RRULE'
78
+ args = {}
79
+ value.split(";").each do |rr|
80
+ rr_key, rr_value = rr.split("=")
81
+ rr_key = rr_key.downcase.to_sym
82
+ unless @frequency.has_key?(rr_key)
83
+ if rr_key == :until
84
+ @repeat_until = Time.parse_complete(rr_value)
85
+ else
86
+ args[rr_key] = rr_value
87
+ end
88
+ end
89
+ end
90
+ case args[:freq]
91
+ when 'DAILY'
92
+ @frequency['daily'] = true
93
+ when 'WEEKLY'
94
+ @frequency['weekly'] = args[:byday].split(',')
95
+ when 'MONTHLY'
96
+ if args[:byday]
97
+ @frequency['monthly'] = args[:byday]
98
+ @frequency[:day_of_week] = true
99
+ else
100
+ @frequency['monthly'] = args[:bymonthday].to_i
101
+ end
102
+ when 'YEARLY'
103
+ @frequency['yearly'] = args[:byyearday].to_i
104
+ end
105
+ elsif key == 'INTERVAL'
106
+ @frequency[:interval] = value.to_i unless value.nil? || value.empty?
107
+ elsif key.include?('DTSTART;VALUE=DATE')
108
+ @start_time ||= Time.parse(value)
109
+ @all_day = true
110
+ elsif key.include?("DTSTART;TZID") or key.include?("DTSTART") or key.include?('DTSTART;VALUE=DATE-TIME')
111
+ @start_time ||= Time.parse_complete(value)
112
+ elsif key.include?('DTEND;VALUE=DATE')
113
+ @end_time ||= Time.parse(value)
114
+ elsif key.include?("DTEND;TZID") or key.include?("DTEND") or key.include?('DTEND;VALUE=DATE-TIME')
115
+ @end_time ||= Time.parse_complete(value)
116
+ end
117
+ end
118
+ @frequency[:interval] = 1 unless @frequency[:interval] && @frequency[:interval].to_i > 0
119
+ end
120
+
121
+ def to_s
122
+ output = ''
123
+ if @frequency
124
+ f = ''
125
+ i = ''
126
+ by = ''
127
+ @frequency.each do |key, v|
128
+ key = key.to_s.downcase
129
+
130
+ if v.is_a?(Array)
131
+ if v.size > 0
132
+ value = v.join(",")
133
+ else
134
+ value = nil
135
+ end
136
+ else
137
+ value = v
138
+ end
139
+ f += "#{key}" if key != 'interval'
140
+ case key
141
+ when "secondly"
142
+ by += " every #{value} second"
143
+ when "minutely"
144
+ by += " every #{value} minute"
145
+ when "hourly"
146
+ by += " every #{value} hour"
147
+ when "weekly"
148
+ by += " on #{value}" if value
149
+ when "monthly"
150
+ by += " on #{value}"
151
+ when "yearly"
152
+ by += " on the #{value} day of the year"
153
+ when 'interval'
154
+ i += " for #{value} times"
155
+ end
156
+ end
157
+ output += f+i+by
158
+ end
159
+ if @repeat_until
160
+ output += " and repeats until #{@repeat_until.strftime("%m/%d/%Y")}"
161
+ end
162
+ output
163
+ end
164
+
165
+ #Returns a string with the correctly formatted ISO 8601 recurrence rule
166
+ def to_recurrence_string
167
+ output = ''
168
+ if @all_day
169
+ output += "DTSTART;VALUE=DATE:#{@start_time.utc.strftime("%Y%m%d")}\n"
170
+ else
171
+ output += "DTSTART;VALUE=DATE-TIME:#{@start_time.utc.complete}\n"
172
+ end
173
+ if @all_day
174
+ output += "DTEND;VALUE=DATE:#{@end_time.utc.strftime("%Y%m%d")}\n"
175
+ else
176
+ output += "DTEND;VALUE=DATE-TIME:#{@end_time.utc.complete}\n"
177
+ end
178
+ output += "RRULE:"
179
+ if @frequency
180
+ f = 'FREQ='
181
+ i = ''
182
+ by = ''
183
+ day_of_week = @frequency.delete(:day_of_week)
184
+ @frequency.each do |key, v|
185
+ if v.is_a?(Array)
186
+ if v.size > 0
187
+ value = v.join(",")
188
+ else
189
+ value = nil
190
+ end
191
+ else
192
+ value = v
193
+ end
194
+ f += "#{key.to_s.upcase};" if key.to_s.downcase != 'interval'
195
+ case key.to_s.downcase
196
+ when "secondly"
197
+ by += "BYSECOND=#{value};"
198
+ when "minutely"
199
+ by += "BYMINUTE=#{value};"
200
+ when "hourly"
201
+ by += "BYHOUR=#{value};"
202
+ when "weekly"
203
+ by += "BYDAY=#{value};" if value
204
+ when "monthly"
205
+ if day_of_week
206
+ by += "BYDAY=#{value};"
207
+ else
208
+ by += "BYMONTHDAY=#{value};"
209
+ end
210
+ when "yearly"
211
+ by += "BYYEARDAY=#{value};"
212
+ when 'interval'
213
+ i += "INTERVAL=#{value};"
214
+ end
215
+ end
216
+ output += f+by+i
217
+ end
218
+ if @repeat_until
219
+ output += "UNTIL=#{@repeat_until.strftime("%Y%m%d")}"
220
+ end
221
+ output += "\n"
222
+ end
223
+
224
+ #Sets the start date/time. Must be a Time object.
225
+ def start_time=(s)
226
+ if not s.is_a?(Time)
227
+ raise RecurrenceValueError, "Start must be a date or a time"
228
+ else
229
+ @start_time = s
230
+ end
231
+ end
232
+
233
+ #Sets the end Date/Time. Must be a Time object.
234
+ def end_time=(e)
235
+ if not e.is_a?(Time)
236
+ raise RecurrenceValueError, "End must be a date or a time"
237
+ else
238
+ @end_time = e
239
+ end
240
+ end
241
+
242
+ #Sets the parent event reference
243
+ def event=(e)
244
+ if not e.is_a?(Event)
245
+ raise RecurrenceValueError, "Event must be an event"
246
+ else
247
+ @event = e
248
+ end
249
+ end
250
+
251
+ #Sets the end date for the recurrence
252
+ def repeat_until=(r)
253
+ if not r.is_a?(Date)
254
+ raise RecurrenceValueError, "Repeat_until must be a date"
255
+ else
256
+ @repeat_until = r
257
+ end
258
+ end
259
+
260
+ #Sets the frequency of the recurrence. Should be a hash with one of
261
+ #"SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" as the key,
262
+ #and as the value, an array containing zero to n of the following:
263
+ #- *Secondly*: A value between 0 and 59. Causes the event to repeat on that second of each minut.
264
+ #- *Minutely*: A value between 0 and 59. Causes the event to repeat on that minute of every hour.
265
+ #- *Hourly*: A value between 0 and 23. Causes the even to repeat on that hour of every day.
266
+ #- *Daily*: A true value - will cause the event to repeat every day until the repeat_until date.
267
+ #- *Weekly*: A value of the first two letters of a day of the week. Causes the event to repeat on that day.
268
+ #- *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.
269
+ #- *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.
270
+ #
271
+ #Optionally, you may specific a second hash pair to set the interval the event repeats:
272
+ # "interval" => '2'
273
+ #If the interval is missing, it is assumed to be 1.
274
+ #
275
+ #===Examples
276
+ #Repeat event daily
277
+ # frequency = {"daily" => true}
278
+ #
279
+ #Repeat event every Tuesday:
280
+ # frequency = {"weekly" => ["TU"]}
281
+ #
282
+ #Repeat every first monday of the month
283
+ # frequency = {"monthly" => "+1MO", :day_of_week => true}
284
+ #
285
+ #Repeat on the 9th of each month regardless of the day
286
+ # frequency = {"monthly" => 9}
287
+ #
288
+ #Repeat on the last day of every year
289
+ # frequency = {"Yearly" => 366}
290
+ #
291
+ #Repeat every other week on Friday
292
+ # frequency = {"Weekly" => ["FR"], "interval" => "2"}
293
+
294
+ def frequency=(f)
295
+ if f.is_a?(Hash)
296
+ @frequency = f
297
+ else
298
+ raise RecurrenceValueError, "Frequency must be a hash (see documentation)"
299
+ end
300
+ end
301
+ end
302
+ end