Class: Vpim::Icalendar

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Vpim
Defined in:
lib/vpim/icalendar.rb,
lib/vpim/vtodo.rb,
lib/vpim/vevent.rb,
lib/vpim/address.rb,
lib/vpim/vjournal.rb,
lib/vpim/property/base.rb,
lib/vpim/property/common.rb,
lib/vpim/property/location.rb,
lib/vpim/property/priority.rb,
lib/vpim/property/resources.rb,
lib/vpim/property/recurrence.rb

Overview

An iCalendar.

A Calendar is some meta-information followed by a sequence of components.

Defined components are Event, Todo, Freebusy, Journal, and Timezone, each of which are represented by their own class, though they share many properties in common. For example, Event and Todo may both contain multiple Alarm components.

Reference

The iCalendar format is specified by a series of IETF documents:

  • rfc2445.txt: Internet Calendaring and Scheduling Core Object Specification

  • rfc2446.txt: iCalendar Transport-Independent Interoperability Protocol (iTIP) Scheduling Events, BusyTime, To-dos and Journal Entries

  • rfc2447.txt: iCalendar Message-Based Interoperability Protocol

iCalendar and vCalendar

iCalendar files have VERSION:2.0 and vCalendar have VERSION:1.0. iCalendar (RFC 2445) is based on vCalendar, but but is not very compatible. While much appears to be similar, the recurrence rule syntax is completely different.

iCalendars are usually transmitted in files with .ics extensions.

Defined Under Namespace

Modules: Bnf, Property, Set Classes: Address, Vevent, Vjournal, Vtodo

Constant Summary

Constants included from Vpim

PRODID, VERSION

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Vpim

array_datetime_to_time, decode_date, decode_date_list, decode_date_time, decode_date_time_list, decode_date_time_to_datetime, decode_date_to_date, decode_integer, decode_list, decode_text, decode_text_list, decode_time, decode_time_list, decode_time_to_time, encode_date, encode_date_time, encode_paramtext, encode_paramvalue, encode_text, encode_text_list, encode_time, expand, outer_inner, unfold, version

Constructor Details

#initialize(fields) ⇒ Icalendar

Create a new Icalendar object from fields, an array of DirectoryInfo::Field objects.

When decoding Calendar data, you would usually use Icalendar.decode(), which decodes the data into the field arrays, and calls this method for each Calendar it finds.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/vpim/icalendar.rb', line 65

def initialize(fields) #:nodoc:
  # seperate into the outer-level fields, and the arrays of component
  # fields
  outer, inner = Vpim.outer_inner(fields)

  # Make a dirinfo out of outer, and check its an iCalendar
  @properties = DirectoryInfo.create(outer)
  @properties.check_begin_end('VCALENDAR')

  @components = []

  # could use #constants instead of this
  factory = {
    'VEVENT' => Vevent,
    'VTODO' => Vtodo,
    'VJOURNAL' => Vjournal,
    # TODO - VTIMEZONE
  }

  inner.each do |component|
    name = component.first.value

    if klass = factory[name]
      @components << klass.new(component)
    end
  end
end

Class Method Details

.create(fields = []) ⇒ Object

Create a new Icalendar object with the minimal set of fields for a valid Calendar. If specified, fields must be an array of DirectoryInfo::Field objects to add. They can override the the default Calendar fields, so, for example, this can be used to set a custom PRODID field.



137
138
139
140
141
142
143
144
145
146
# File 'lib/vpim/icalendar.rb', line 137

def Icalendar.create(fields=[])
  di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )

  DirectoryInfo::Field.create_array(fields).each { |f| di.push_unique f }

  di.push_unique DirectoryInfo::Field.create('PRODID',   Vpim::PRODID)
  di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")

  new(di.to_a)
end

.create2(producer = Vpim::PRODID, tzinfo_id = nil) ⇒ Object

The producer ID defaults to Vpim::PRODID but you can set it to something specific to your application.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/vpim/icalendar.rb', line 116

def Icalendar.create2(producer = Vpim::PRODID, tzinfo_id = nil) #:yield: self
  # FIXME - make the primary API
  di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )

  di.push_unique DirectoryInfo::Field.create('PRODID', producer.to_str)
  di.push_unique DirectoryInfo::Field.create('X-WR-TIMEZONE', tzinfo_id) unless tzinfo_id.nil?
  di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")

  cal = new(di.to_a)

  if block_given?
    yield cal
  end

  cal
end

.create_reply(fields = []) ⇒ Object

Create a new Icalendar object with a protocol method of REPLY.

Meeting requests, and such, are Calendar containers with a protocol method of REQUEST, and contains some number of Events, Todos, etc., that may need replying to. In order to reply to any of these components of a request, you must first build a Calendar object to hold your reply components.

This method builds the reply Calendar, you then will add to it replies to the specific components of the request Calendar that you are replying to. If you have any particular fields that you want to be in the Calendar, other than the defaults, then can be supplied as fields, an array of Field objects.



161
162
163
164
165
# File 'lib/vpim/icalendar.rb', line 161

def Icalendar.create_reply(fields=[])
  fields << DirectoryInfo::Field.create('METHOD', 'REPLY')

  Icalendar.create(fields)
end

.decode(cal, e = nil) ⇒ Object

Decode iCalendar data into an array of Icalendar objects.

Since iCalendars are self-delimited (by a BEGIN:VCALENDAR and an END:VCALENDAR), multiple iCalendars can be concatenated into a single file.

cal must be String or IO, or implement #each by returning each line in the input as those classes do.



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/vpim/icalendar.rb', line 255

def Icalendar.decode(cal, e = nil)
  entities = Vpim.expand(Vpim.decode(cal))

  # Since all iCalendars must have a begin/end, the top-level should
  # consist entirely of entities/arrays, even if its a single iCalendar.
  if entities.detect { |e| ! e.kind_of? Array }
    raise "Not a valid iCalendar"
  end

  calendars = []

  entities.each do |e|
    calendars << new(e)
  end

  calendars
end

.decode_duration(str) ⇒ Object

:nodoc:



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/vpim/icalendar.rb', line 207

def Icalendar.decode_duration(str) #:nodoc:
  unless match = %r{\s*#{Bnf::DURATION}\s*}.match(str)
    raise InvalidEncodingError, "duration not valid (#{str})"
  end
  dur = 0

  # Remember: match[0] is the whole match string, match[1] is $1, etc.

  # Week
  if match[2]
    dur = match[2].to_i
  end
  # Days
  dur *= 7
  if match[3]
    dur += match[3].to_i
  end
  # Hours
  dur *= 24
  if match[4]
    dur += match[4].to_i
  end
  # Minutes
  dur *= 60
  if match[5]
    dur += match[5].to_i
  end
  # Seconds
  dur *= 60
  if match[6]
    dur += match[6].to_i
  end

  if match[1] && match[1] == '-'
    dur = -dur
  end

  dur
end

Instance Method Details

#add_event(&block) ⇒ Object

Add an event to this calendar.

Yields an event maker, Icalendar::Vevent::Maker.



96
97
98
# File 'lib/vpim/icalendar.rb', line 96

def add_event(&block) #:yield:event
  push Vevent::Maker.make( &block )
end

#calscaleObject

The value of the CALSCALE: property, or “GREGORIAN” if CALSCALE: is not present.

This is of academic interest only. There aren’t any other calendar scales defined, and given that its hard enough just dealing with Gregorian calendars, there probably won’t be.



313
314
315
# File 'lib/vpim/icalendar.rb', line 313

def calscale
  (@properties['CALSCALE'] || 'GREGORIAN').upcase
end

#components(klass = Object) ⇒ Object

The array of all supported calendar components. If a class is provided, return only the components of that class.

If a block is provided, yield the components instead of returning them.

Examples:

calendar.components(Vpim::Icalendar::Vevent)
=> array of all calendar components

calendar.components(Vpim::Icalendar::Vtodo) {|c| c... }
=> yield all todo components

calendar.components {|c| c... }
=> yield all components

Note - use of this is mildly deprecated in favour of #each, #events, #todos, #journals because those won’t return timezones, and will return Enumerators if called without a block.



335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/vpim/icalendar.rb', line 335

def components(klass=Object) #:yields:component
  klass ||= Object

  unless block_given?
    return @components.select{|c| klass === c}.freeze
  end

  @components.each do |c|
    if klass === c
      yield c
    end
  end
  self
end

#each(klass = nil, &block) ⇒ Object

Enumerate the top-level calendar components. Yields them if a block is provided, otherwise returns an Enumerator.

This skips components that are only internally meaningful to iCalendar, such as timezone definitions.



357
358
359
360
361
362
# File 'lib/vpim/icalendar.rb', line 357

def each(klass=nil, &block) # :yield: component
  unless block
    return Enumerable::Enumerator.new(self, :each, klass)
  end
  components(klass, &block)
end

#encode(width = nil) ⇒ Object Also known as: to_s

Encode the Calendar as a string. The width is the maximum width of the encoded lines, it can be specified, but is better left to the default.



180
181
182
183
184
185
# File 'lib/vpim/icalendar.rb', line 180

def encode(width=nil)
  # We concatenate the fields of all objects, create a DirInfo, then
  # encode it.
  di = DirectoryInfo.create(self.fields.flatten)
  di.encode(width)
end

#events(&block) ⇒ Object

Short-hand for #each(Icalendar::Vevent).



365
366
367
# File 'lib/vpim/icalendar.rb', line 365

def events(&block) #:yield: Vevent
  each(Icalendar::Vevent, &block)
end

#fieldsObject

Used during encoding.



168
169
170
171
172
173
174
175
176
# File 'lib/vpim/icalendar.rb', line 168

def fields # :nodoc:
  f = @properties.to_a
  last = f.pop
  # Use of #each means we won't encode components in our View, but also
  # that we won't encode timezones... but we don't decode/support timezones
  # anyhow, so fix later.
  each { |c| f << c.fields }
  f.push last
end

#journals(&block) ⇒ Object

Short-hand for #each(Icalendar::Vjournal).



375
376
377
# File 'lib/vpim/icalendar.rb', line 375

def journals(&block) #:yield: Vjournal
  each(Icalendar::Vjournal, &block)
end

#producerObject

The value of the PRODID field, an unstructured string meant to identify the software which encoded the Calendar data.



288
289
290
291
292
# File 'lib/vpim/icalendar.rb', line 288

def producer
  #f = @properties.field('PRODID')
  #f && f.to_text
  @properties.text('PRODID').first
end

#protocolObject

The value of the METHOD field. Protocol methods are used when iCalendars are exchanged in a calendar messaging system, such as iTIP or iMIP. When METHOD is not specified, the Calendar object is merely being used to transport a snapshot of some calendar information; without the intention of conveying a scheduling semantic.

Note that this method can’t be called method, thats already a method of Object.



302
303
304
305
# File 'lib/vpim/icalendar.rb', line 302

def protocol
  m = @properties['METHOD']
  m ? m.upcase : m
end

#protocol?(method) ⇒ Boolean

Check if the protocol method is method

Returns:

  • (Boolean)


203
204
205
# File 'lib/vpim/icalendar.rb', line 203

def protocol?(method)
  Vpim::Methods.casecmp?(protocol, method)
end

#push(component) ⇒ Object Also known as: <<

Push a calendar component onto the calendar.



190
191
192
193
194
195
196
197
198
# File 'lib/vpim/icalendar.rb', line 190

def push(component)
  case component
    when Vevent, Vtodo, Vjournal
      @components << component
    else
      raise ArgumentError, "can't add a #{component.type} to a calendar"
  end
  self
end

#todos(&block) ⇒ Object

Short-hand for #each(Icalendar::Vtodo).



370
371
372
# File 'lib/vpim/icalendar.rb', line 370

def todos(&block) #:yield: Vtodo
  each(Icalendar::Vtodo, &block)
end

#versionObject

The iCalendar version multiplied by 10 as an Integer. iCalendar must have a version of 20, and vCalendar must have a version of 10.



275
276
277
278
279
280
281
282
283
284
# File 'lib/vpim/icalendar.rb', line 275

def version
  v = @properties['VERSION']

  unless v
    raise InvalidEncodingError, "Invalid calendar, no version field!"
  end

  v = v.to_f * 10
  v = v.to_i
end