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: Internet Calendaring and Scheduling Core Object Specification

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

  • RFC2447: 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 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.



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

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.



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

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.



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

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.



163
164
165
166
167
# File 'lib/vpim/icalendar.rb', line 163

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.



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

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:



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

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.



99
100
101
# File 'lib/vpim/icalendar.rb', line 99

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.



315
316
317
# File 'lib/vpim/icalendar.rb', line 315

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.



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

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.



359
360
361
362
363
364
# File 'lib/vpim/icalendar.rb', line 359

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.



182
183
184
185
186
187
# File 'lib/vpim/icalendar.rb', line 182

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



367
368
369
# File 'lib/vpim/icalendar.rb', line 367

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

#fieldsObject

Used during encoding.



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

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



377
378
379
# File 'lib/vpim/icalendar.rb', line 377

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.



290
291
292
293
294
# File 'lib/vpim/icalendar.rb', line 290

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.



304
305
306
307
# File 'lib/vpim/icalendar.rb', line 304

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

#protocol?(method) ⇒ Boolean

Check if the protocol method is method

Returns:

  • (Boolean)


205
206
207
# File 'lib/vpim/icalendar.rb', line 205

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

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

Push a calendar component onto the calendar.



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

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



372
373
374
# File 'lib/vpim/icalendar.rb', line 372

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.



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

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