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.


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

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) ⇒ 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
# File 'lib/vpim/icalendar.rb', line 116

def Icalendar.create2(producer = Vpim::PRODID) #: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('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.


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

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.


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

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:


206
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
# File 'lib/vpim/icalendar.rb', line 206

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.


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

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.


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

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.


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

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.


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

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).


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

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

#fieldsObject

Used during encoding.


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

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).


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

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.


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

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.


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

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

#protocol?(method) ⇒ Boolean

Check if the protocol method is method

Returns:

  • (Boolean)

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

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

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

Push a calendar component onto the calendar.


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

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).


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

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.


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

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