parent
3f8c9f0ae6
commit
2bae9db309
@ -0,0 +1,56 @@
|
|||||||
|
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
||||||
|
You can redistribute it and/or modify it under either the terms of the GPL
|
||||||
|
(see the file GPL), or the conditions below:
|
||||||
|
|
||||||
|
1. You may make and give away verbatim copies of the source form of the
|
||||||
|
software without restriction, provided that you duplicate all of the
|
||||||
|
original copyright notices and associated disclaimers.
|
||||||
|
|
||||||
|
2. You may modify your copy of the software in any way, provided that
|
||||||
|
you do at least ONE of the following:
|
||||||
|
|
||||||
|
a) place your modifications in the Public Domain or otherwise
|
||||||
|
make them Freely Available, such as by posting said
|
||||||
|
modifications to Usenet or an equivalent medium, or by allowing
|
||||||
|
the author to include your modifications in the software.
|
||||||
|
|
||||||
|
b) use the modified software only within your corporation or
|
||||||
|
organization.
|
||||||
|
|
||||||
|
c) give non-standard binaries non-standard names, with
|
||||||
|
instructions on where to get the original software distribution.
|
||||||
|
|
||||||
|
d) make other distribution arrangements with the author.
|
||||||
|
|
||||||
|
3. You may distribute the software in object code or binary form,
|
||||||
|
provided that you do at least ONE of the following:
|
||||||
|
|
||||||
|
a) distribute the binaries and library files of the software,
|
||||||
|
together with instructions (in the manual page or equivalent)
|
||||||
|
on where to get the original distribution.
|
||||||
|
|
||||||
|
b) accompany the distribution with the machine-readable source of
|
||||||
|
the software.
|
||||||
|
|
||||||
|
c) give non-standard binaries non-standard names, with
|
||||||
|
instructions on where to get the original software distribution.
|
||||||
|
|
||||||
|
d) make other distribution arrangements with the author.
|
||||||
|
|
||||||
|
4. You may modify and include the part of the software into any other
|
||||||
|
software (possibly commercial). But some files in the distribution
|
||||||
|
are not written by the author, so that they are not under these terms.
|
||||||
|
|
||||||
|
For the list of those files and their copying conditions, see the
|
||||||
|
file LEGAL.
|
||||||
|
|
||||||
|
5. The scripts and library files supplied as input to or produced as
|
||||||
|
output from the software do not automatically fall under the
|
||||||
|
copyright of the software, but belong to whomever generated them,
|
||||||
|
and may be sold commercially, and may be aggregated with this
|
||||||
|
software.
|
||||||
|
|
||||||
|
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE.
|
@ -0,0 +1,141 @@
|
|||||||
|
= iCalendar -- Internet calendaring, Ruby style
|
||||||
|
|
||||||
|
This is a Ruby library for dealing with iCalendar files. Rather than
|
||||||
|
explaining myself, here is the introduction from RFC-2445, which
|
||||||
|
defines the format:
|
||||||
|
|
||||||
|
The use of calendaring and scheduling has grown considerably in the
|
||||||
|
last decade. Enterprise and inter-enterprise business has become
|
||||||
|
dependent on rapid scheduling of events and actions using this
|
||||||
|
information technology. However, the longer term growth of calendaring
|
||||||
|
and scheduling, is currently limited by the lack of Internet standards
|
||||||
|
for the message content types that are central to these knowledgeware
|
||||||
|
applications. This memo is intended to progress the level of
|
||||||
|
interoperability possible between dissimilar calendaring and
|
||||||
|
scheduling applications. This memo defines a MIME content type for
|
||||||
|
exchanging electronic calendaring and scheduling information. The
|
||||||
|
Internet Calendaring and Scheduling Core Object Specification, or
|
||||||
|
iCalendar, allows for the capture and exchange of information normally
|
||||||
|
stored within a calendaring and scheduling application; such as a
|
||||||
|
Personal Information Manager (PIM) or a Group Scheduling product.
|
||||||
|
|
||||||
|
The iCalendar format is suitable as an exchange format between
|
||||||
|
applications or systems. The format is defined in terms of a MIME
|
||||||
|
content type. This will enable the object to be exchanged using
|
||||||
|
several transports, including but not limited to SMTP, HTTP, a file
|
||||||
|
system, desktop interactive protocols such as the use of a memory-
|
||||||
|
based clipboard or drag/drop interactions, point-to-point asynchronous
|
||||||
|
communication, wired-network transport, or some form of unwired
|
||||||
|
transport such as infrared might also be used.
|
||||||
|
|
||||||
|
Now for some examples:
|
||||||
|
|
||||||
|
## Probably want to start with this
|
||||||
|
|
||||||
|
require 'rubygems' # Unless you install from the tarball or zip.
|
||||||
|
require 'icalendar'
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
include Icalendar # Probably do this in your class to limit namespace overlap
|
||||||
|
|
||||||
|
## Creating calendars and events is easy.
|
||||||
|
|
||||||
|
# Create a calendar with an event (standard method)
|
||||||
|
cal = Calendar.new
|
||||||
|
cal.event do
|
||||||
|
dtstart Date.new(2005, 04, 29)
|
||||||
|
dtend Date.new(2005, 04, 28)
|
||||||
|
summary "Meeting with the man."
|
||||||
|
description "Have a long lunch meeting and decide nothing..."
|
||||||
|
klass "PRIVATE"
|
||||||
|
end
|
||||||
|
|
||||||
|
cal.publish
|
||||||
|
|
||||||
|
## Or you can make events like this
|
||||||
|
event = Event.new
|
||||||
|
event.start = DateTime.civil(2006, 6, 23, 8, 30)
|
||||||
|
event.summary = "A great event!"
|
||||||
|
cal.add_event(event)
|
||||||
|
|
||||||
|
event2 = cal.event # This automatically adds the event to the calendar
|
||||||
|
event2.start = DateTime.civil(2006, 6, 24, 8, 30)
|
||||||
|
event2.summary = "Another great event!"
|
||||||
|
|
||||||
|
# Now with support for property parameters
|
||||||
|
params = {"ALTREP" =>['"http://my.language.net"'], "LANGUAGE" => ["SPANISH"]}
|
||||||
|
|
||||||
|
cal.event do
|
||||||
|
dtstart Date.new(2005, 04, 29)
|
||||||
|
dtend Date.new(2005, 04, 28)
|
||||||
|
summary "This is a summary with params.", params
|
||||||
|
end
|
||||||
|
|
||||||
|
# We can output the calendar as a string to write to a file,
|
||||||
|
# network port, database etc.
|
||||||
|
cal_string = cal.to_ical
|
||||||
|
puts cal_string
|
||||||
|
|
||||||
|
== Parsing iCalendars:
|
||||||
|
|
||||||
|
# Open a file or pass a string to the parser
|
||||||
|
cal_file = File.open("single_event.ics")
|
||||||
|
|
||||||
|
# Parser returns an array of calendars because a single file
|
||||||
|
# can have multiple calendars.
|
||||||
|
cals = Icalendar.parse(cal_file)
|
||||||
|
cal = cals.first
|
||||||
|
|
||||||
|
# Now you can access the cal object in just the same way I created it
|
||||||
|
event = cal.events.first
|
||||||
|
|
||||||
|
puts "start date-time: " + event.dtstart
|
||||||
|
puts "summary: " + event.summary
|
||||||
|
|
||||||
|
== Finders:
|
||||||
|
|
||||||
|
Often times in web apps and other interactive applications you'll need to
|
||||||
|
lookup items in a calendar to make changes or get details. Now you can find
|
||||||
|
everything by the unique id automatically associated with all components.
|
||||||
|
|
||||||
|
cal = Calendar.new
|
||||||
|
10.times { cal.event } # Create 10 events with only default data.
|
||||||
|
some_event = cal.events[5] # Grab it from the array of events
|
||||||
|
|
||||||
|
# Use the uid as the key in your app
|
||||||
|
key = some_event.uid
|
||||||
|
|
||||||
|
# so later you can find it.
|
||||||
|
same_event = cal.find_event(key)
|
||||||
|
|
||||||
|
== Examples:
|
||||||
|
|
||||||
|
Check the unit tests for examples of most things you'll want to do, but please
|
||||||
|
send me example code or let me know what's missing.
|
||||||
|
|
||||||
|
== Download
|
||||||
|
|
||||||
|
The latest release version of this library can be found at
|
||||||
|
|
||||||
|
* http://rubyforge.org/projects/icalendar/
|
||||||
|
|
||||||
|
Documentation can be found at
|
||||||
|
|
||||||
|
* http://icalendar.rubyforge.org/
|
||||||
|
|
||||||
|
== Installation
|
||||||
|
|
||||||
|
It's all about rubygems:
|
||||||
|
|
||||||
|
$ sudo gem install icalendar
|
||||||
|
|
||||||
|
== License
|
||||||
|
|
||||||
|
This library is released under the same license as Ruby itself.
|
||||||
|
|
||||||
|
== Support & Contributions
|
||||||
|
|
||||||
|
The iCalendar library homepage is http://icalendar.rubyforge.org/
|
||||||
|
|
||||||
|
There is an icalendar-devel@rubyforge.org mailing list that can be
|
||||||
|
used for asking questions, making comments or submitting patches.
|
@ -0,0 +1,110 @@
|
|||||||
|
require 'rubygems'
|
||||||
|
require 'rake'
|
||||||
|
require 'rake/testtask'
|
||||||
|
require 'rake/rdoctask'
|
||||||
|
require 'rake/clean'
|
||||||
|
require 'rake/contrib/sshpublisher'
|
||||||
|
|
||||||
|
PKG_VERSION = "1.0.2"
|
||||||
|
|
||||||
|
$VERBOSE = nil
|
||||||
|
TEST_CHANGES_SINCE = Time.now - 600 # Recent tests = changed in last 10 minutes
|
||||||
|
|
||||||
|
desc "Run all the unit tests"
|
||||||
|
task :default => [ :test, :lines ]
|
||||||
|
|
||||||
|
desc "Run the unit tests in test"
|
||||||
|
Rake::TestTask.new(:test) { |t|
|
||||||
|
t.libs << "test"
|
||||||
|
t.test_files = FileList['test/*_test.rb', 'test/component/*_test.rb']
|
||||||
|
t.verbose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# rcov code coverage
|
||||||
|
rcov_path = '/usr/local/bin/rcov'
|
||||||
|
rcov_test_output = "./test/coverage"
|
||||||
|
rcov_exclude = "interactive.rb,read_write.rb,fixtures"
|
||||||
|
|
||||||
|
# Add our created paths to the 'rake clobber' list
|
||||||
|
CLOBBER.include(rcov_test_output)
|
||||||
|
|
||||||
|
desc 'Removes all previous unit test coverage information'
|
||||||
|
task (:reset_unit_test_coverage) do |t|
|
||||||
|
rm_rf rcov_unit_test_output
|
||||||
|
mkdir rcov_unit_test_output
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run all unit tests with Rcov to measure coverage'
|
||||||
|
Rake::TestTask.new(:rcov) do |t|
|
||||||
|
t.libs << "test"
|
||||||
|
t.pattern = 'test/**/*_test.rb'
|
||||||
|
t.ruby_opts << rcov_path
|
||||||
|
t.ruby_opts << "-o #{rcov_test_output}"
|
||||||
|
t.ruby_opts << "-x #{rcov_exclude}"
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate the RDoc documentation
|
||||||
|
Rake::RDocTask.new(:doc) { |rdoc|
|
||||||
|
rdoc.main = 'README'
|
||||||
|
rdoc.rdoc_files.include('lib/**/*.rb', 'README')
|
||||||
|
rdoc.rdoc_files.include('GPL', 'COPYING')
|
||||||
|
rdoc.rdoc_dir = 'docs/api'
|
||||||
|
rdoc.title = "iCalendar -- Internet Calendaring for Ruby"
|
||||||
|
rdoc.options << "--include=examples --line-numbers --inline-source"
|
||||||
|
rdoc.options << "--accessor=ical_component,ical_property,ical_multi_property"
|
||||||
|
}
|
||||||
|
|
||||||
|
Gem::manage_gems
|
||||||
|
require 'rake/gempackagetask'
|
||||||
|
|
||||||
|
spec = Gem::Specification.new do |s|
|
||||||
|
s.name = "icalendar"
|
||||||
|
s.version = PKG_VERSION
|
||||||
|
s.homepage = "http://icalendar.rubyforge.org/"
|
||||||
|
s.platform = Gem::Platform::RUBY
|
||||||
|
s.summary = "A ruby implementation of the iCalendar specification (RFC-2445)."
|
||||||
|
s.description = "Implements the iCalendar specification (RFC-2445) in Ruby. This allows for the generation and parsing of .ics files, which are used by a variety of calendaring applications."
|
||||||
|
|
||||||
|
s.files = FileList["{test,lib,docs,examples}/**/*"].to_a
|
||||||
|
s.files += ["Rakefile", "README", "COPYING", "GPL" ]
|
||||||
|
s.require_path = "lib"
|
||||||
|
s.autorequire = "icalendar"
|
||||||
|
s.has_rdoc = true
|
||||||
|
s.extra_rdoc_files = ["README", "COPYING", "GPL"]
|
||||||
|
s.rdoc_options.concat ['--main', 'README']
|
||||||
|
|
||||||
|
s.author = "Jeff Rose"
|
||||||
|
s.email = "rosejn@gmail.com"
|
||||||
|
end
|
||||||
|
|
||||||
|
Rake::GemPackageTask.new(spec) do |pkg|
|
||||||
|
pkg.gem_spec = spec
|
||||||
|
pkg.need_tar = true
|
||||||
|
pkg.need_zip = true
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Install the gem globally (requires sudo)'
|
||||||
|
task :install => :package do |t|
|
||||||
|
`sudo gem install pkg/icalendar-#{PKG_VERSION}.gem`
|
||||||
|
end
|
||||||
|
|
||||||
|
task :lines do
|
||||||
|
lines = 0
|
||||||
|
codelines = 0
|
||||||
|
Dir.foreach("lib/icalendar") { |file_name|
|
||||||
|
next unless file_name =~ /.*rb/
|
||||||
|
|
||||||
|
f = File.open("lib/icalendar/" + file_name)
|
||||||
|
|
||||||
|
while line = f.gets
|
||||||
|
lines += 1
|
||||||
|
next if line =~ /^\s*$/
|
||||||
|
next if line =~ /^\s*#/
|
||||||
|
codelines += 1
|
||||||
|
end
|
||||||
|
}
|
||||||
|
puts "\n------------------------------\n"
|
||||||
|
puts "Total Lines: #{lines}"
|
||||||
|
puts "Lines of Code: #{codelines}"
|
||||||
|
end
|
@ -0,0 +1,69 @@
|
|||||||
|
Set of methods used for transactions:
|
||||||
|
|
||||||
|
PUBLISH:
|
||||||
|
- Publish a calendar entry to one or more users.
|
||||||
|
|
||||||
|
REQUEST:
|
||||||
|
- Used to schedule a calendar entry with other users.
|
||||||
|
- Require REPLY messages from others.
|
||||||
|
- Used by Organizers to update status of entries.
|
||||||
|
|
||||||
|
REPLY:
|
||||||
|
- Attendees use this to send information back to Organizers who have sent a
|
||||||
|
REQUEST message.
|
||||||
|
|
||||||
|
ADD:
|
||||||
|
- Add one or more instances to an existing entry.
|
||||||
|
|
||||||
|
CANCEL:
|
||||||
|
- Cancel a calendar item.
|
||||||
|
|
||||||
|
REFRESH:
|
||||||
|
- Attendee's use this to get the latest version of an item.
|
||||||
|
|
||||||
|
COUNTER:
|
||||||
|
- Used by an attendee to negotiate a change in a calendar entry.
|
||||||
|
|
||||||
|
DECLINE-COUNTER:
|
||||||
|
- Used by an organizer to decline a COUNTER message.
|
||||||
|
|
||||||
|
|
||||||
|
Transports:
|
||||||
|
|
||||||
|
Real-time vs. Store-and-forward?
|
||||||
|
|
||||||
|
Entry Status:
|
||||||
|
- Only the organizer can set the STATUS property
|
||||||
|
- Attendee's use the "partstat" parameter of the ATTENDEE property to convey
|
||||||
|
their personal status.
|
||||||
|
- Initial value of "partstat" is set to "NEEDS-ACTION" by organizer
|
||||||
|
- Modifying this state is part of an attendee's REPLY message.
|
||||||
|
|
||||||
|
Sequence Property:
|
||||||
|
- Used to tell manage different versions of an entry
|
||||||
|
- Has specific rules so look at these
|
||||||
|
|
||||||
|
Handling messages should be done in this manner:
|
||||||
|
|
||||||
|
1. The primary key for referencing a particular iCalendar component
|
||||||
|
is the "UID" property value. To reference an instance of a
|
||||||
|
recurring component, the primary key is composed of the "UID" and
|
||||||
|
the "RECURRENCE-ID" properties.
|
||||||
|
2. The secondary key for referencing a component is the "SEQUENCE"
|
||||||
|
property value. For components where the "UID" is the same, the
|
||||||
|
component with the highest numeric value for the "SEQUENCE"
|
||||||
|
property obsoletes all other revisions of the component with
|
||||||
|
lower values.
|
||||||
|
3. "Attendees" send "REPLY" messages to the "Organizer". For
|
||||||
|
replies where the "UID" property value is the same, the value of
|
||||||
|
the "SEQUENCE" property indicates the revision of the component
|
||||||
|
to which the "Attendee" is replying. The reply with the highest
|
||||||
|
numeric value for the "SEQUENCE" property obsoletes all other
|
||||||
|
replies with lower values.
|
||||||
|
4. In situations where the "UID" and "SEQUENCE" properties match,
|
||||||
|
the "DTSTAMP" property is used as the tie-breaker. The component
|
||||||
|
with the latest "DTSTAMP" overrides all others. Similarly, for
|
||||||
|
"Attendee" responses where the "UID" property values match and
|
||||||
|
the "SEQUENCE" property values match, the response with the
|
||||||
|
latest "DTSTAMP" overrides all others.
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,738 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<TITLE>RFC 3283 (rfc3283) - Guide to Internet Calendaring</TITLE>
|
||||||
|
<META name="description" content="RFC 3283 - Guide to Internet Calendaring">
|
||||||
|
<script language="JavaScript1.2">
|
||||||
|
function erfc(s)
|
||||||
|
{document.write("<A href=\"/rfccomment.php?rfcnum="+s+"\" target=\"_blank\" onclick=\"window.open('/rfccomment.php?rfcnum="+s+"','Popup','toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=680,height=530,left=30,top=43'); return false;\")>Comment on RFC "+s+"</A>\n");}
|
||||||
|
//-->
|
||||||
|
</script>
|
||||||
|
</HEAD>
|
||||||
|
<BODY BGCOLOR="#ffffff" TEXT="#000000">
|
||||||
|
<P ALIGN=CENTER><IMG SRC="/images/library.jpg" HEIGHT=62 WIDTH=150 BORDER="0" ALIGN="MIDDLE" ALT=""></P>
|
||||||
|
<H1 ALIGN=CENTER>RFC 3283 (RFC3283)</H1>
|
||||||
|
<P ALIGN=CENTER>Internet RFC/STD/FYI/BCP Archives</P>
|
||||||
|
|
||||||
|
<DIV ALIGN=CENTER>[ <a href="/rfcs/">RFC Index</a> | <A HREF="/rfcs/rfcsearch.html">RFC Search</A> | <a href="/faqs/">Usenet FAQs</a> | <a href="/contrib/">Web FAQs</a> | <a href="/docs/">Documents</a> | <a href="http://www.city-data.com/">Cities</a> ]
|
||||||
|
<P>
|
||||||
|
<STRONG>Alternate Formats:</STRONG>
|
||||||
|
<A HREF="/ftp/rfc/rfc3283.txt">rfc3283.txt</A></DIV>
|
||||||
|
<p align=center><script language="JavaScript"><!--
|
||||||
|
erfc("3283");
|
||||||
|
// --></script></p>
|
||||||
|
<h3 align=center>RFC 3283 - Guide to Internet Calendaring</h3>
|
||||||
|
<HR SIZE=2 NOSHADE>
|
||||||
|
<PRE>
|
||||||
|
|
||||||
|
Network Working Group B. Mahoney
|
||||||
|
Request for Comments: 3283 MIT
|
||||||
|
Category: Informational G. Babics
|
||||||
|
Steltor
|
||||||
|
A. Taler
|
||||||
|
June 2002
|
||||||
|
|
||||||
|
Guide to Internet Calendaring
|
||||||
|
|
||||||
|
Status of this Memo
|
||||||
|
|
||||||
|
This memo provides information for the Internet community. It does
|
||||||
|
not specify an Internet standard of any kind. Distribution of this
|
||||||
|
memo is unlimited.
|
||||||
|
|
||||||
|
Copyright Notice
|
||||||
|
|
||||||
|
Copyright (C) The Internet Society (2002). All Rights Reserved.
|
||||||
|
|
||||||
|
Abstract
|
||||||
|
|
||||||
|
This document describes the various Internet calendaring and
|
||||||
|
scheduling standards and works in progress, and the relationships
|
||||||
|
between them. Its intent is to provide a context for these
|
||||||
|
documents, assist in their understanding, and potentially aid in the
|
||||||
|
design of standards-based calendaring and scheduling systems. The
|
||||||
|
standards addressed are <A HREF="/rfcs/rfc2445.html">RFC 2445</A> (iCalendar), <A HREF="/rfcs/rfc2446.html">RFC 2446</A> (iTIP), and
|
||||||
|
<A HREF="/rfcs/rfc2447.html">RFC 2447</A> (iMIP). The work in progress addressed is "Calendar Access
|
||||||
|
Protocol" (CAP). This document also describes issues and problems
|
||||||
|
that are not solved by these protocols, and that could be targets for
|
||||||
|
future work.
|
||||||
|
|
||||||
|
Table of Contents
|
||||||
|
|
||||||
|
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||||
|
1.1 Terminology . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||||
|
1.2 Concepts and Relationships . . . . . . . . . . . . . . . . . 4
|
||||||
|
2. Requirements . . . . . . . . . . . . . . . . . . . . . . . . 4
|
||||||
|
2.1 Fundamental Needs . . . . . . . . . . . . . . . . . . . . . 4
|
||||||
|
2.2 Protocol Requirements . . . . . . . . . . . . . . . . . . . 5
|
||||||
|
3. Solutions . . . . . . . . . . . . . . . . . . . . . . . . . 7
|
||||||
|
3.1 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . 7
|
||||||
|
3.2 Systems . . . . . . . . . . . . . . . . . . . . . . . . . . 8
|
||||||
|
3.2.1 Standalone Single-user System . . . . . . . . . . . . . . . 8
|
||||||
|
3.2.2 Single-user Systems Communicating . . . . . . . . . . . . . 8
|
||||||
|
3.2.3 Single-user with Multiple CUAs . . . . . . . . . . . . . . . 9
|
||||||
|
3.2.4 Single-user with Multiple Calendars . . . . . . . . . . . . 9
|
||||||
|
|
||||||
|
3.2.5 Users Communicating on a Multi-user System . . . . . . . . . 10
|
||||||
|
3.2.6 Users Communicating through Different Multi-user Systems . . 10
|
||||||
|
4. Important Aspects . . . . . . . . . . . . . . . . . . . . . 10
|
||||||
|
4.1 Timezones . . . . . . . . . . . . . . . . . . . . . . . . . 10
|
||||||
|
4.2 Choice of Transport . . . . . . . . . . . . . . . . . . . . 11
|
||||||
|
4.3 Security . . . . . . . . . . . . . . . . . . . . . . . . . . 11
|
||||||
|
4.4 Amount of data . . . . . . . . . . . . . . . . . . . . . . . 11
|
||||||
|
4.5 Recurring Components . . . . . . . . . . . . . . . . . . . . 11
|
||||||
|
5. Open Issues . . . . . . . . . . . . . . . . . . . . . . . . 11
|
||||||
|
5.1 Scheduling People, not Calendars . . . . . . . . . . . . . . 12
|
||||||
|
5.2 Administration . . . . . . . . . . . . . . . . . . . . . . . 12
|
||||||
|
5.3 Notification . . . . . . . . . . . . . . . . . . . . . . . . 12
|
||||||
|
6. Security Considerations . . . . . . . . . . . . . . . . . . 12
|
||||||
|
6.1 Access Control . . . . . . . . . . . . . . . . . . . . . . . 12
|
||||||
|
6.2 Authentication . . . . . . . . . . . . . . . . . . . . . . . 12
|
||||||
|
6.3 Using E-mail . . . . . . . . . . . . . . . . . . . . . . . . 13
|
||||||
|
6.4 Other Issues . . . . . . . . . . . . . . . . . . . . . . . . 13
|
||||||
|
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . 13
|
||||||
|
References . . . . . . . . . . . . . . . . . . . . . . . . . 14
|
||||||
|
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . 15
|
||||||
|
Full Copyright Statement . . . . . . . . . . . . . . . . . . 16
|
||||||
|
|
||||||
|
1. Introduction
|
||||||
|
|
||||||
|
Calendaring and scheduling protocols are intended to aid individuals
|
||||||
|
in obtaining calendaring information and scheduling meetings across
|
||||||
|
the Internet, to aid organizations in providing calendaring
|
||||||
|
information on the Internet, and to provide for organizations looking
|
||||||
|
for a calendaring and scheduling solution to deploy internally.
|
||||||
|
|
||||||
|
It is the intent of this document to provide a context for these
|
||||||
|
documents, assist in their understanding, and potentially help in the
|
||||||
|
design of standards-based calendaring and scheduling systems.
|
||||||
|
|
||||||
|
Problems not solved by these protocols, as well as security issues to
|
||||||
|
be kept in mind, are discussed at the end of the document.
|
||||||
|
|
||||||
|
1.1 Terminology
|
||||||
|
|
||||||
|
This memo uses much of the same terminology as iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>],
|
||||||
|
iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>], iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>], and [CAP]. The following
|
||||||
|
definitions are provided as an introduction; the definitions in the
|
||||||
|
protocol specifications themselves should be considered canonical.
|
||||||
|
|
||||||
|
Calendar
|
||||||
|
|
||||||
|
A collection of events, to-dos, journal entries, etc. A calendar
|
||||||
|
could be the content of a person or resource's agenda; it could
|
||||||
|
also be a collection of data serving a more specialized need.
|
||||||
|
Calendars are the basic storage containers for calendaring
|
||||||
|
information.
|
||||||
|
|
||||||
|
Calendar Access Rights
|
||||||
|
|
||||||
|
A set of rules defining who may perform what operations, such as
|
||||||
|
reading or writing information, on a given calendar.
|
||||||
|
|
||||||
|
Calendar Service
|
||||||
|
|
||||||
|
A running server application that provides access to a number of
|
||||||
|
calendar stores.
|
||||||
|
|
||||||
|
Calendar Store (CS)
|
||||||
|
|
||||||
|
A data store of a calendar service. A calendar service may have
|
||||||
|
several calendar stores, and each store may contain several
|
||||||
|
calendars, as well as properties and components outside of those
|
||||||
|
calendars.
|
||||||
|
|
||||||
|
Calendar User (CU)
|
||||||
|
|
||||||
|
An entity (often a human) that accesses calendar information.
|
||||||
|
|
||||||
|
Calendar User Agent (CUA)
|
||||||
|
|
||||||
|
Software with which the calendar user communicates with a calendar
|
||||||
|
service or local calendar store to access calendar information.
|
||||||
|
|
||||||
|
Component
|
||||||
|
|
||||||
|
A piece of calendar data such as an event, a to-do or an alarm.
|
||||||
|
Information about components is stored as properties of those
|
||||||
|
components.
|
||||||
|
|
||||||
|
Delegator
|
||||||
|
|
||||||
|
A calendar user who has assigned his or her participation in a
|
||||||
|
scheduled calendar component (e.g. a VEVENT) to another calendar
|
||||||
|
user (sometimes called the delegate or delegatee). An example of
|
||||||
|
a delegator is a busy executive sending an employee to a meeting
|
||||||
|
in his or her place.
|
||||||
|
|
||||||
|
Delegate
|
||||||
|
|
||||||
|
A calendar user (sometimes called the delegatee) who has been
|
||||||
|
assigned to participate in a scheduled calendar component (e.g. a
|
||||||
|
VEVENT) in place of one of the attendees in that component
|
||||||
|
(sometimes called the delegator). An example of a delegate is a
|
||||||
|
team member sent to a particular meeting.
|
||||||
|
|
||||||
|
Designate
|
||||||
|
|
||||||
|
A calendar user authorized to act on behalf of another calendar
|
||||||
|
user. An example of a designate is an assistant scheduling
|
||||||
|
meetings for his or her superior.
|
||||||
|
|
||||||
|
Local Store
|
||||||
|
|
||||||
|
A CS that is on the same device as the CUA.
|
||||||
|
|
||||||
|
Property
|
||||||
|
|
||||||
|
A description of some element of a component, such as a start
|
||||||
|
time, title or location.
|
||||||
|
|
||||||
|
Remote Store
|
||||||
|
|
||||||
|
A CS that is not on the same device as the CUA.
|
||||||
|
|
||||||
|
1.2 Concepts and Relationships
|
||||||
|
|
||||||
|
iCalendar is the language used to describe calendar objects. iTIP
|
||||||
|
describes a way to use the iCalendar language to do scheduling. iMIP
|
||||||
|
describes how to do iTIP scheduling via e-mail. CAP describes a way
|
||||||
|
to use the iCalendar language to access a calendar store in real-
|
||||||
|
time.
|
||||||
|
|
||||||
|
The relationship between calendaring protocols is similar to that
|
||||||
|
between e-mail protocols. In those terms, iCalendar is analogous to
|
||||||
|
<A HREF="/rfcs/rfc2822.html">RFC 2822</A>, iTIP and iMIP are analogous to the Simple Mail Transfer
|
||||||
|
Protocol (SMTP), and CAP is analogous to the Post Office Protocol
|
||||||
|
(POP) or Internet Message Access Protocol (IMAP).
|
||||||
|
|
||||||
|
2. Requirements
|
||||||
|
|
||||||
|
2.1 Fundamental Needs
|
||||||
|
|
||||||
|
The following scenarios illustrate people and organizations' basic
|
||||||
|
calendaring and scheduling needs:
|
||||||
|
|
||||||
|
a] A doctor wishes to keep track of all her appointments.
|
||||||
|
|
||||||
|
Need: To read and manipulate one's own calendar with only one CUA.
|
||||||
|
|
||||||
|
b] A busy musician wants to maintain her schedule with multiple
|
||||||
|
devices, such as through an Internet-based agenda and with a PDA.
|
||||||
|
|
||||||
|
Need: To read and manipulate one's own calendar, possibly with
|
||||||
|
solutions from different vendors.
|
||||||
|
|
||||||
|
c] A software development team wishes to more effectively schedule
|
||||||
|
their time through viewing each other's calendar information.
|
||||||
|
|
||||||
|
Need: To share calendar information between users of the same
|
||||||
|
calendar service.
|
||||||
|
|
||||||
|
d] A teacher wants his students to schedule appointments during
|
||||||
|
his office hours.
|
||||||
|
|
||||||
|
Need: To schedule calendar events, to-dos and journals with other
|
||||||
|
users of the same calendar service.
|
||||||
|
|
||||||
|
e] A movie theater wants to publish its schedule for prospective
|
||||||
|
customers.
|
||||||
|
|
||||||
|
Need: To share calendar information with users of other calendar
|
||||||
|
services, possibly from a number of different vendors.
|
||||||
|
|
||||||
|
f] A social club wants to schedule calendar entries effectively
|
||||||
|
with its members.
|
||||||
|
|
||||||
|
Need: To schedule calendar events and to-dos with users of other
|
||||||
|
calendar services, possibly from a number of different vendors.
|
||||||
|
|
||||||
|
2.2 Protocol Requirements
|
||||||
|
|
||||||
|
Some of these needs can be met by proprietary solutions (a, c, d),
|
||||||
|
but others can not (b, e, f). These latter scenarios show that
|
||||||
|
standard protocols are required for accessing information in a
|
||||||
|
calendar store and scheduling calendar entries. In addition, these
|
||||||
|
protocols require a common data format for representing calendar
|
||||||
|
information.
|
||||||
|
|
||||||
|
These requirements are met by the following protocol specifications.
|
||||||
|
|
||||||
|
- Data format: iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>]
|
||||||
|
|
||||||
|
iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] provides a data format for representing
|
||||||
|
calendar information, to be used and exchanged by other protocols.
|
||||||
|
iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] can also be used in other contexts, such as a
|
||||||
|
drag-and-drop interface, or an export/import feature. All the
|
||||||
|
other calendaring protocols depend on iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>], so all
|
||||||
|
elements of a standards-based calendaring and scheduling systems
|
||||||
|
will have to be able to interpret iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>].
|
||||||
|
|
||||||
|
- Scheduling protocol: iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>]
|
||||||
|
|
||||||
|
iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] describes the messages used to schedule calendar
|
||||||
|
events. Within iTIP messages, events are represented in iCalendar
|
||||||
|
[<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] format, and have semantics that identify the message as
|
||||||
|
being an invitation to a meeting, an acceptance of an invitation,
|
||||||
|
or the assignment of a task.
|
||||||
|
|
||||||
|
iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] messages are used in the scheduling workflow,
|
||||||
|
where users exchange messages in order to organize things such as
|
||||||
|
events and to-dos. CUAs generate and interpret iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>]
|
||||||
|
messages at the direction of the calendar user. With iTIP [RFC-
|
||||||
|
2446] users can create, modify, delete, reply to, counter, and
|
||||||
|
decline counters to the various iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] components.
|
||||||
|
Furthermore, users can also request the free/busy time of other
|
||||||
|
people.
|
||||||
|
|
||||||
|
iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] is transport-independent, and has one specified
|
||||||
|
transport binding: iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>] binds iTIP to e-mail. In
|
||||||
|
addition [CAP] will provide a real-time binding of iTIP [RFC-
|
||||||
|
2446], allowing CUAs to perform calendar management and scheduling
|
||||||
|
over a single connection.
|
||||||
|
|
||||||
|
- Calendar management protocol: [CAP]
|
||||||
|
|
||||||
|
[CAP] describes the messages used to manage calendars on a
|
||||||
|
calendar store. These messages use iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] to
|
||||||
|
describe various components such as events and to-dos. These
|
||||||
|
messages make it possible to perform iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] operations,
|
||||||
|
as well as other operations relating to a calendar store such as
|
||||||
|
searching, creating calendars, specifying calendar properties, and
|
||||||
|
specifying calendar access rights.
|
||||||
|
|
||||||
|
3. Solutions
|
||||||
|
|
||||||
|
3.1 Examples
|
||||||
|
|
||||||
|
Returning to the scenarios presented in section 2.1, the calendaring
|
||||||
|
protocols can be used in the following ways:
|
||||||
|
|
||||||
|
a] The doctor can use a proprietary CUA with a local store, and
|
||||||
|
perhaps use iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] as a storage mechanism. This
|
||||||
|
would allow her to easily import her data store into another
|
||||||
|
application that supports iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>].
|
||||||
|
|
||||||
|
b] The musician who wishes to access her agenda from anywhere can
|
||||||
|
use a [CAP]-enabled calendar service accessible over the Internet.
|
||||||
|
She can then use any available [CAP] clients to access the data.
|
||||||
|
|
||||||
|
A proprietary system that provides access through a Web-based
|
||||||
|
interface could also be employed, but the use of [CAP] would be
|
||||||
|
superior in that it would allow the use of third party
|
||||||
|
applications, such as PDA synchronization tools.
|
||||||
|
|
||||||
|
c] The development team can use a calendar service which supports
|
||||||
|
[CAP], and each member can use a [CAP]-enabled CUA of their
|
||||||
|
choice.
|
||||||
|
|
||||||
|
Alternatively, each member could use an iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>]-enabled
|
||||||
|
CUA, and they could book meetings over e-mail. This solution has
|
||||||
|
the drawback that it is difficult to examine other users' agendas,
|
||||||
|
making the organization of meetings more difficult.
|
||||||
|
|
||||||
|
Proprietary solutions are also available, but they require that
|
||||||
|
all members use clients by the same vendor, and disallow the use
|
||||||
|
of third party applications.
|
||||||
|
|
||||||
|
d] The teacher can set up a calendar service, and have students
|
||||||
|
book time through any of the iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] bindings. [CAP]
|
||||||
|
provides real-time access, but could require additional
|
||||||
|
configuration. iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>] would be the easiest to configure,
|
||||||
|
but may require more e-mail processing.
|
||||||
|
|
||||||
|
If [CAP] access is provided then determining the state of the
|
||||||
|
teacher's schedule is straightforward. If not, this can be
|
||||||
|
determined through iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] free/busy requests. Non-
|
||||||
|
standard methods could also be employed, such as serving up
|
||||||
|
iCalendar [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>], HTML, or XML over HTTP.
|
||||||
|
|
||||||
|
A proprietary system could also be used, but would require that
|
||||||
|
all students be able to use software from a specific vendor.
|
||||||
|
|
||||||
|
e] [CAP] would be preferred for publishing a movie theater's
|
||||||
|
schedule, since it provides advanced access and search
|
||||||
|
capabilities. It also allows easy integration with customers'
|
||||||
|
calendar systems.
|
||||||
|
|
||||||
|
Non-standard methods such as serving data over HTTP could also be
|
||||||
|
employed, but would be harder to integrate with customers'
|
||||||
|
systems.
|
||||||
|
|
||||||
|
Using a completely proprietary solution would be very difficult,
|
||||||
|
if not impossible, since it would require every user to install
|
||||||
|
and use the proprietary software.
|
||||||
|
|
||||||
|
f] The social club could distribute meeting information in the
|
||||||
|
form of iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] messages, sent via e-mail using iMIP
|
||||||
|
[<A HREF="/rfcs/rfc2447.html">RFC-2447</A>]. The club could distribute meeting invitations, as
|
||||||
|
well as a full published agenda.
|
||||||
|
|
||||||
|
Alternatively, the club could provide access to a [CAP]-enabled
|
||||||
|
calendar service. However, this solution would be more expensive
|
||||||
|
since it requires the maintenance of a server.
|
||||||
|
|
||||||
|
3.2 Systems
|
||||||
|
|
||||||
|
The following diagrams illustrate possible systems and their usage of
|
||||||
|
the various protocols.
|
||||||
|
|
||||||
|
3.2.1 Standalone Single-user System
|
||||||
|
|
||||||
|
A single user system that does not communicate with other systems
|
||||||
|
need not employ any of the protocols. However, it may use iCalendar
|
||||||
|
[<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] as a data format in some places.
|
||||||
|
|
||||||
|
----------- O
|
||||||
|
| CUA w/ | -+- user
|
||||||
|
|local store| A
|
||||||
|
----------- / \
|
||||||
|
|
||||||
|
3.2.2 Single-user Systems Communicating
|
||||||
|
|
||||||
|
Users with single-user systems may schedule meetings with each others
|
||||||
|
using iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>]. The easiest binding of iTIP [<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] to use
|
||||||
|
would be iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>], since messages can be held in the users'
|
||||||
|
mail queues, which we assume to already exist. [CAP] could also be
|
||||||
|
used.
|
||||||
|
|
||||||
|
O ----------- ----------- O
|
||||||
|
-+- | CUA w/ | -----[iMIP]----- | CUA w/ | -+- user
|
||||||
|
A |local store| Internet |local store| A
|
||||||
|
/ \ ----------- ----------- / \
|
||||||
|
|
||||||
|
3.2.3 Single-user with Multiple CUAs
|
||||||
|
|
||||||
|
A single user may use more than one CUA to access his or her
|
||||||
|
calendar. The user may use a PDA, a Web client, a PC, or some other
|
||||||
|
device, depending on accessibility. Some of these clients may have
|
||||||
|
local stores and others may not. Those with local stores need to
|
||||||
|
synchronize the data on the CUA with the data on the CS.
|
||||||
|
|
||||||
|
-----------
|
||||||
|
| CUA w | -----[CAP]----------+
|
||||||
|
|local store| |
|
||||||
|
O ----------- ----------
|
||||||
|
-+- | CS |
|
||||||
|
A | |
|
||||||
|
/ \ ----------
|
||||||
|
----------- |
|
||||||
|
| CUA w/o | -----[CAP]----------+
|
||||||
|
|local store|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
3.2.4 Single-user with Multiple Calendars
|
||||||
|
|
||||||
|
A single user may have many independent calendars; for example, one
|
||||||
|
may contain work-related information and another personal
|
||||||
|
information. The CUA may or may not have a local store. If it does,
|
||||||
|
then it needs to synchronize the data of the CUA with the data on
|
||||||
|
both of the CS.
|
||||||
|
|
||||||
|
----------
|
||||||
|
+------------[CAP]------ | CS |
|
||||||
|
| | |
|
||||||
|
O ----------- ----------
|
||||||
|
-+- | CUA |
|
||||||
|
A | |
|
||||||
|
/ \ -----------
|
||||||
|
| ----------
|
||||||
|
+------------[CAP]------ | CS |
|
||||||
|
| |
|
||||||
|
----------
|
||||||
|
|
||||||
|
3.2.5 Users Communicating on a Multi-user System
|
||||||
|
|
||||||
|
Users on a multi-user system may schedule meetings with each other
|
||||||
|
using [CAP]-enabled CUAs and services. The CUAs may or may not have
|
||||||
|
local stores. Those with local stores need to synchronize the data
|
||||||
|
on the CUAs with the data on the CS.
|
||||||
|
|
||||||
|
O -----------
|
||||||
|
-+- | CUA w | -----[CAP]----------+
|
||||||
|
A |local store| |
|
||||||
|
/ \ ----------- ----------
|
||||||
|
| CS |
|
||||||
|
| |
|
||||||
|
----------
|
||||||
|
O ----------- |
|
||||||
|
-+- | CUA w/o | -----[CAP]----------+
|
||||||
|
A |local store|
|
||||||
|
/ \ -----------
|
||||||
|
|
||||||
|
3.2.6 Users Communicating through Different Multi-user Systems
|
||||||
|
|
||||||
|
Users on a multi-user system may need to schedule meetings with users
|
||||||
|
on a different multi-user system. The services can communicate using
|
||||||
|
[CAP] or iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>].
|
||||||
|
|
||||||
|
O ----------- ----------
|
||||||
|
-+- | CUA w | -----[CAP]-------| CS |
|
||||||
|
A |local store| | |
|
||||||
|
/ \ ----------- ----------
|
||||||
|
|
|
||||||
|
[CAP] or [iMIP]
|
||||||
|
|
|
||||||
|
O ----------- ----------
|
||||||
|
-+- | CUA w/o | -----[CAP]-------| CS |
|
||||||
|
A |local store| | |
|
||||||
|
/ \ ----------- ----------
|
||||||
|
|
||||||
|
4. Important Aspects
|
||||||
|
|
||||||
|
There are a number of important aspects of these calendaring
|
||||||
|
standards of which people, especially implementers, should be aware.
|
||||||
|
|
||||||
|
4.1 Timezones
|
||||||
|
|
||||||
|
The dates and times in components can refer to a specific time zone.
|
||||||
|
Time zones can be defined in a central store, or they may be defined
|
||||||
|
by a user to fit his or her needs. All users and applications should
|
||||||
|
be aware of time zones and time zone differences. New time zones may
|
||||||
|
|
||||||
|
need to be added, and others removed. Two different vendors may
|
||||||
|
describe the same time zone differently (such as by using a different
|
||||||
|
name).
|
||||||
|
|
||||||
|
4.2 Choice of Transport
|
||||||
|
|
||||||
|
There are issues to be aware of in choosing between a network
|
||||||
|
protocol such as [CAP], or a store and forward protocol, such as iMIP
|
||||||
|
[<A HREF="/rfcs/rfc2447.html">RFC-2447</A>].
|
||||||
|
|
||||||
|
The use of a network ("on-the-wire") mechanism may require some
|
||||||
|
organizations to make provisions to allow calendaring traffic to
|
||||||
|
traverse a corporate firewall on the required ports. Depending on
|
||||||
|
the organizational culture, this may be a challenging social
|
||||||
|
exercise.
|
||||||
|
|
||||||
|
The use of an email-based mechanism exposes time-sensitive data to
|
||||||
|
unbounded latency. Large or heavily utilized mail systems may
|
||||||
|
experience an unacceptable delay in message receipt.
|
||||||
|
|
||||||
|
4.3 Security
|
||||||
|
|
||||||
|
See the "Security Considerations" (Section 6) section below.
|
||||||
|
|
||||||
|
4.4 Amount of data
|
||||||
|
|
||||||
|
In some cases, a component may be very large, for instance, a
|
||||||
|
component with a very large attachment. Some applications may be
|
||||||
|
low-bandwidth or may be limited in the amount of data they can store.
|
||||||
|
Maximum component size may be set in [CAP]. It can also be
|
||||||
|
controlled in iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>] by restricting the maximum size of the
|
||||||
|
e-mail that the application can download.
|
||||||
|
|
||||||
|
4.5 Recurring Components
|
||||||
|
|
||||||
|
In iCAL [<A HREF="/rfcs/rfc2445.html">RFC-2445</A>], one can specify complex recurrence rules for
|
||||||
|
VEVENTs, VTODOs, and VJOURNALs. One must be careful to correctly
|
||||||
|
interpret these recurrence rules and pay extra attention to being
|
||||||
|
able to interoperate using them.
|
||||||
|
|
||||||
|
5. Open Issues
|
||||||
|
|
||||||
|
Many issues are not currently resolved by these protocols, and many
|
||||||
|
desirable features are not yet provided. Some of the more prominent
|
||||||
|
ones are outlined below.
|
||||||
|
|
||||||
|
5.1 Scheduling People, not Calendars
|
||||||
|
|
||||||
|
Meetings are scheduled with people; however, people may have many
|
||||||
|
calendars, and may store these calendars in many places. There may
|
||||||
|
also be many routes to contact them. The calendaring protocols do
|
||||||
|
not attempt to provide unique access for contacting a given person.
|
||||||
|
Instead, 'calendar addresses' are booked, which may be e-mail
|
||||||
|
addresses or individual calendars. It is up to the users themselves
|
||||||
|
to orchestrate mechanisms to ensure that the bookings go to the right
|
||||||
|
place.
|
||||||
|
|
||||||
|
5.2 Administration
|
||||||
|
|
||||||
|
The calendaring protocols do not address the issues of administering
|
||||||
|
users and calendars on a calendar service. This must be handled by
|
||||||
|
proprietary mechanisms for each implementation.
|
||||||
|
|
||||||
|
5.3 Notification
|
||||||
|
|
||||||
|
People often wish to be notified of upcoming events, new events, or
|
||||||
|
changes to existing events. The calendaring protocols do not attempt
|
||||||
|
to address these needs in a real-time system. Instead, the ability
|
||||||
|
to store alarm information on events is provided, which can be used
|
||||||
|
to provide client-side notification of upcoming events. To organize
|
||||||
|
notification of new or changed events, clients have to poll the data
|
||||||
|
store.
|
||||||
|
|
||||||
|
6. Security Considerations
|
||||||
|
|
||||||
|
6.1 Access Control
|
||||||
|
|
||||||
|
There has to be reasonable granularity in the configuration options
|
||||||
|
for access to data through [CAP], so that what should be released to
|
||||||
|
requesters is released, and what shouldn't is not. Details of
|
||||||
|
handling this are described in [CAP].
|
||||||
|
|
||||||
|
6.2 Authentication
|
||||||
|
|
||||||
|
Access control must be coupled with a good authentication system, so
|
||||||
|
that the right people get the right information. For [CAP], this
|
||||||
|
means requiring authentication before any database access can be
|
||||||
|
performed, and checking access rights and authentication credentials
|
||||||
|
before releasing information. [CAP] uses the Simple Authentication
|
||||||
|
Security Layer (SASL) for this authentication. In iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>],
|
||||||
|
this may present some challenges, as authentication is often not a
|
||||||
|
consideration in store-and-forward protocols.
|
||||||
|
|
||||||
|
Authentication is also important for scheduling, in that receivers of
|
||||||
|
scheduling messages should be able to validate the apparent sender.
|
||||||
|
Since scheduling messages are wrapped in MIME [<A HREF="/rfcs/rfc2045.html">RFC-2045</A>], signing and
|
||||||
|
encryption are freely available. For messages transmitted over mail,
|
||||||
|
this is the only available alternative. It is suggested that
|
||||||
|
developers take care in implementing the security features in iMIP
|
||||||
|
[<A HREF="/rfcs/rfc2447.html">RFC-2447</A>], bearing in mind that the concept and need may be foreign
|
||||||
|
or non-obvious to users, yet essential for the system to function as
|
||||||
|
they might expect.
|
||||||
|
|
||||||
|
The real-time protocols provide for the authentication of users, and
|
||||||
|
the preservation of that authentication information, allowing for
|
||||||
|
validation by the receiving end-user or server.
|
||||||
|
|
||||||
|
6.3 Using E-mail
|
||||||
|
|
||||||
|
Because scheduling information can be transmitted over mail without
|
||||||
|
any authentication information, e-mail spoofing is extremely easy if
|
||||||
|
the receiver is not checking for authentication. It is suggested
|
||||||
|
that implementers consider requiring authentication as a default,
|
||||||
|
using mechanisms such as are described in Section 3 of iMIP [RFC-
|
||||||
|
2447]. The use of e-mail, and the potential for anonymous
|
||||||
|
connections, means that 'calendar spam' is possible. Developers
|
||||||
|
should consider this threat when designing systems, particularly
|
||||||
|
those that allow for automated request processing.
|
||||||
|
|
||||||
|
6.4 Other Issues
|
||||||
|
|
||||||
|
The current security context should be obvious to users. Because the
|
||||||
|
underlying mechanisms may not be clear to users, efforts to make
|
||||||
|
clear the current state in the UI should be made. One example of
|
||||||
|
this is the 'lock' icon used in some Web browsers during secure
|
||||||
|
connections.
|
||||||
|
|
||||||
|
With both iMIP [<A HREF="/rfcs/rfc2447.html">RFC-2447</A>] and [CAP], the possibilities of Denial of
|
||||||
|
Service attacks must be considered. The ability to flood a calendar
|
||||||
|
system with bogus requests is likely to be exploited once these
|
||||||
|
systems become widely deployed, and detection and recovery methods
|
||||||
|
will need to be considered.
|
||||||
|
|
||||||
|
Acknowledgments
|
||||||
|
|
||||||
|
Thanks to the following, who have participated in the development of
|
||||||
|
this document:
|
||||||
|
|
||||||
|
Eric Busboom, Pat Egen, David Madeo, Shawn Packwood, Bruce Kahn,
|
||||||
|
Alan Davies, Robb Surridge.
|
||||||
|
|
||||||
|
References
|
||||||
|
|
||||||
|
[<A HREF="/rfcs/rfc2445.html">RFC-2445</A>] Dawson, F. and D. Stenerson, "Internet Calendaring and
|
||||||
|
Scheduling Core Object Specification - iCalendar", RFC
|
||||||
|
2445, November 1998.
|
||||||
|
|
||||||
|
[<A HREF="/rfcs/rfc2446.html">RFC-2446</A>] Silverberg, S., Mansour, S., Dawson, F. and R. Hopson,
|
||||||
|
"iCalendar Transport-Independent Interoperability Protocol
|
||||||
|
(iTIP): Scheduling Events, Busy Time, To-dos and Journal
|
||||||
|
Entries", <A HREF="/rfcs/rfc2446.html">RFC 2446</A>, November 1998.
|
||||||
|
|
||||||
|
[<A HREF="/rfcs/rfc2447.html">RFC-2447</A>] Dawson, F., Mansour, S. and S. Silverberg, "iCalendar
|
||||||
|
Message-Based Interoperability Protocol - iMIP", <A HREF="/rfcs/rfc2447.html">RFC 2447</A>,
|
||||||
|
November 1998.
|
||||||
|
|
||||||
|
[<A HREF="/rfcs/rfc2045.html">RFC-2045</A>] Freed, N. and N. Borenstein, "Multipurpose Internet Mail
|
||||||
|
Extensions (MIME) - Part One: Format of Internet Message
|
||||||
|
Bodies", <A HREF="/rfcs/rfc2045.html">RFC 2045</A>, November 1996.
|
||||||
|
|
||||||
|
[CAP] Mansour, S., Royer, D., Babics, G., and Hill, P.,
|
||||||
|
"Calendar Access Protocol (CAP)", Work in Progress.
|
||||||
|
|
||||||
|
Authors' Addresses
|
||||||
|
|
||||||
|
Bob Mahoney
|
||||||
|
MIT
|
||||||
|
E40-327
|
||||||
|
77 Massachusetts Avenue
|
||||||
|
Cambridge, MA 02139
|
||||||
|
US
|
||||||
|
|
||||||
|
Phone: (617) 253-0774
|
||||||
|
EMail: <A HREF="mailto:bobmah@mit.edu">bobmah@mit.edu</A>
|
||||||
|
|
||||||
|
George Babics
|
||||||
|
Steltor
|
||||||
|
2000 Peel Street
|
||||||
|
Montreal, Quebec H3A 2W5
|
||||||
|
CA
|
||||||
|
|
||||||
|
Phone: (514) 733-8500 x4201
|
||||||
|
EMail: <A HREF="mailto:georgeb@steltor.com">georgeb@steltor.com</A>
|
||||||
|
|
||||||
|
Alexander Taler
|
||||||
|
|
||||||
|
EMail: <A HREF="mailto:alex@0--0.org">alex@0--0.org</A>
|
||||||
|
|
||||||
|
Full Copyright Statement
|
||||||
|
|
||||||
|
Copyright (C) The Internet Society (2002). All Rights Reserved.
|
||||||
|
|
||||||
|
This document and translations of it may be copied and furnished to
|
||||||
|
others, and derivative works that comment on or otherwise explain it
|
||||||
|
or assist in its implementation may be prepared, copied, published
|
||||||
|
and distributed, in whole or in part, without restriction of any
|
||||||
|
kind, provided that the above copyright notice and this paragraph are
|
||||||
|
included on all such copies and derivative works. However, this
|
||||||
|
document itself may not be modified in any way, such as by removing
|
||||||
|
the copyright notice or references to the Internet Society or other
|
||||||
|
Internet organizations, except as needed for the purpose of
|
||||||
|
developing Internet standards in which case the procedures for
|
||||||
|
copyrights defined in the Internet Standards process must be
|
||||||
|
followed, or as required to translate it into languages other than
|
||||||
|
English.
|
||||||
|
|
||||||
|
The limited permissions granted above are perpetual and will not be
|
||||||
|
revoked by the Internet Society or its successors or assigns.
|
||||||
|
|
||||||
|
This document and the information contained herein is provided on an
|
||||||
|
"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
|
||||||
|
TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
||||||
|
BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
|
||||||
|
HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
Acknowledgement
|
||||||
|
|
||||||
|
Funding for the RFC Editor function is currently provided by the
|
||||||
|
Internet Society.
|
||||||
|
|
||||||
|
</PRE>
|
||||||
|
<p align=center><script language="JavaScript"><!--
|
||||||
|
erfc("3283");
|
||||||
|
// --></script></p>
|
||||||
|
<br>
|
||||||
|
<div align="center">
|
||||||
|
<table border="0" cellpadding="3" width="100%" cellspacing="3">
|
||||||
|
<tr><td width="45%">
|
||||||
|
<p align="left">Previous: <a href="/rfcs/rfc3282.html">RFC 3282 - Content Language Headers</a>
|
||||||
|
</p></td><td width="10%"> </td><td width="45%">
|
||||||
|
<p align="right">Next: <a href="/rfcs/rfc3284.html">RFC 3284 - The VCDIFF Generic Differencing and Compression Data Format</a>
|
||||||
|
</p></td></tr></table></div><p align="right"> </p>
|
||||||
|
<HR SIZE=2 NOSHADE>
|
||||||
|
<DIV ALIGN=CENTER>[ <a href="/rfcs/">RFC Index</a> | <A HREF="/rfcs/rfcsearch.html">RFC Search</A> | <a href="/faqs/">Usenet FAQs</a> | <a href="/contrib/">Web FAQs</a> | <a href="/docs/">Documents</a> | <a href="http://www.city-data.com/">Cities</a> ]
|
||||||
|
<P>
|
||||||
|
</DIV>
|
||||||
|
<ADDRESS>
|
||||||
|
<P ALIGN=CENTER>
|
||||||
|
|
||||||
|
</P>
|
||||||
|
</ADDRESS>
|
||||||
|
</SMALL>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
|
|
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
## Need this so we can require the library from the samples directory
|
||||||
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||||
|
|
||||||
|
require 'rubygems' # Unless you install from the tarball or zip.
|
||||||
|
require 'icalendar'
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
include Icalendar # Probably do this in your class to limit namespace overlap
|
||||||
|
|
||||||
|
## Creating calendars and events is easy.
|
||||||
|
|
||||||
|
# Create a calendar with an event (standard method)
|
||||||
|
cal = Calendar.new
|
||||||
|
cal.event do
|
||||||
|
dtstart Date.new(2005, 04, 29)
|
||||||
|
dtend Date.new(2005, 04, 28)
|
||||||
|
summary "Meeting with the man."
|
||||||
|
description "Have a long lunch meeting and decide nothing..."
|
||||||
|
klass "PRIVATE"
|
||||||
|
end
|
||||||
|
|
||||||
|
## Or you can make events like this
|
||||||
|
event = Event.new
|
||||||
|
event.start = DateTime.civil(2006, 6, 23, 8, 30)
|
||||||
|
event.summary = "A great event!"
|
||||||
|
cal.add_event(event)
|
||||||
|
|
||||||
|
event2 = cal.event # This automatically adds the event to the calendar
|
||||||
|
event2.start = DateTime.civil(2006, 6, 24, 8, 30)
|
||||||
|
event2.summary = "Another great event!"
|
||||||
|
|
||||||
|
# Now with support for property parameters
|
||||||
|
params = {"ALTREP" =>['"http://my.language.net"'], "LANGUAGE" => ["SPANISH"]}
|
||||||
|
|
||||||
|
cal.event do
|
||||||
|
dtstart Date.new(2005, 04, 29)
|
||||||
|
dtend Date.new(2005, 04, 28)
|
||||||
|
summary "This is a summary with params.", params
|
||||||
|
end
|
||||||
|
|
||||||
|
# We can output the calendar as a string to write to a file,
|
||||||
|
# network port, database etc.
|
||||||
|
cal_string = cal.to_ical
|
||||||
|
puts cal_string
|
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
## Need this so we can require the library from the samples directory
|
||||||
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||||
|
|
||||||
|
require 'icalendar'
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
# Open a file or string to parse
|
||||||
|
cal_file = File.open("../test/life.ics")
|
||||||
|
|
||||||
|
# Parser returns an array of calendars because a single file
|
||||||
|
# can have multiple calendar objects.
|
||||||
|
cals = Icalendar::parse(cal_file)
|
||||||
|
cal = cals.first
|
||||||
|
|
||||||
|
# Now you can access the cal object in just the same way I created it
|
||||||
|
event = cal.events.first
|
||||||
|
|
||||||
|
puts "start date-time: " + event.dtstart.to_s
|
||||||
|
puts "summary: " + event.summary
|
@ -0,0 +1,18 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:bsprodidfortestabc123
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:bsuidfortestabc123
|
||||||
|
SUMMARY:This is a really long summary
|
||||||
|
to test the method of unfolding lines
|
||||||
|
so I'm just going to ma
|
||||||
|
ke it
|
||||||
|
a whol
|
||||||
|
e
|
||||||
|
bunch of lines.
|
||||||
|
CLASS:PRIVATE
|
||||||
|
DTSTART;TZID=US-Mountain:20050120T170000
|
||||||
|
DTEND:20050120T184500
|
||||||
|
DTSTAMP:20050118T211523Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
require File.join(File.dirname(__FILE__), 'lib', 'icalendar')
|
||||||
|
|
@ -0,0 +1,34 @@
|
|||||||
|
# A module which adds some generators for hash based accessors.
|
||||||
|
module HashAttrs
|
||||||
|
|
||||||
|
def hash_reader(hash_sym, syms)
|
||||||
|
syms.each do |id|
|
||||||
|
id = id.to_s.downcase
|
||||||
|
func = Proc.new do
|
||||||
|
hash = instance_variable_get(hash_sym)
|
||||||
|
hash[id.to_sym]
|
||||||
|
end
|
||||||
|
|
||||||
|
self.send(:define_method, id, func)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hash_writer(hash_sym, syms)
|
||||||
|
syms.each do |id|
|
||||||
|
id = id.to_s.downcase
|
||||||
|
|
||||||
|
func = Proc.new do |val|
|
||||||
|
hash = instance_variable_get(hash_sym)
|
||||||
|
hash[id.to_sym] = val
|
||||||
|
end
|
||||||
|
|
||||||
|
self.send(:define_method, id+'=', func)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hash_accessor(hash, *syms)
|
||||||
|
hash_reader(hash, syms)
|
||||||
|
hash_writer(hash, syms)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,36 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
$:.unshift(File.dirname(__FILE__))
|
||||||
|
|
||||||
|
### Base classes and mixin modules
|
||||||
|
|
||||||
|
# to_ical methods for built-in classes
|
||||||
|
require 'icalendar/conversions'
|
||||||
|
|
||||||
|
# Meta-programming helper methods
|
||||||
|
require 'meta'
|
||||||
|
|
||||||
|
# Hash attributes mixin module
|
||||||
|
require 'hash_attrs'
|
||||||
|
|
||||||
|
require 'icalendar/base'
|
||||||
|
require 'icalendar/component'
|
||||||
|
|
||||||
|
# Calendar and components
|
||||||
|
require 'icalendar/calendar'
|
||||||
|
require 'icalendar/component/event'
|
||||||
|
require 'icalendar/component/journal'
|
||||||
|
require 'icalendar/component/todo'
|
||||||
|
require 'icalendar/component/freebusy'
|
||||||
|
require 'icalendar/component/timezone'
|
||||||
|
require 'icalendar/component/alarm'
|
||||||
|
|
||||||
|
# Calendar parser
|
||||||
|
require 'icalendar/parser'
|
||||||
|
|
@ -0,0 +1,43 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
require 'logger'
|
||||||
|
|
||||||
|
module Icalendar #:nodoc:
|
||||||
|
|
||||||
|
# A simple error class to differentiate iCalendar library exceptions
|
||||||
|
# from ruby language exceptions or others.
|
||||||
|
class IcalendarError < StandardError #:nodoc:
|
||||||
|
end
|
||||||
|
|
||||||
|
# Exception used when the library encounters a bogus calendar component.
|
||||||
|
class UnknownComponentClass < IcalendarError
|
||||||
|
end
|
||||||
|
|
||||||
|
# Exception used when the library encounters a bogus property type.
|
||||||
|
class UnknownPropertyMethod< IcalendarError
|
||||||
|
end
|
||||||
|
|
||||||
|
# Exception used when the library encounters a bogus property value.
|
||||||
|
class InvalidPropertyValue < IcalendarError
|
||||||
|
end
|
||||||
|
|
||||||
|
# This class serves as the base class for just about everything in
|
||||||
|
# the library so that the logging system can be configured in one place.
|
||||||
|
class Base
|
||||||
|
@@logger = Logger.new(STDERR)
|
||||||
|
@@logger.level = Logger::FATAL
|
||||||
|
|
||||||
|
def self.debug
|
||||||
|
@@logger.level = Logger::DEBUG
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.quiet
|
||||||
|
@@logger.level = Logger::FATAL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,104 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
module Icalendar
|
||||||
|
|
||||||
|
class Calendar < Component
|
||||||
|
ical_component :events, :todos, :journals, :freebusys, :timezones
|
||||||
|
|
||||||
|
ical_property :calscale, :calendar_scale
|
||||||
|
ical_property :prodid, :product_id
|
||||||
|
ical_property :version
|
||||||
|
ical_property :ip_method
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("VCALENDAR")
|
||||||
|
|
||||||
|
# Set some defaults
|
||||||
|
self.calscale = "GREGORIAN" # Who knows, but this is the only one in the spec.
|
||||||
|
self.prodid = "iCalendar-Ruby" # Current product... Should be overwritten by apps that use the library
|
||||||
|
self.version = "2.0" # Version of the specification
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(&block)
|
||||||
|
e = Event.new
|
||||||
|
self.add_component e
|
||||||
|
|
||||||
|
e.instance_eval &block if block
|
||||||
|
|
||||||
|
e
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_event(uid)
|
||||||
|
self.events.find {|e| e.uid == uid}
|
||||||
|
end
|
||||||
|
|
||||||
|
def todo(&block)
|
||||||
|
e = Todo.new
|
||||||
|
self.add_component e
|
||||||
|
|
||||||
|
e.instance_eval &block if block
|
||||||
|
|
||||||
|
e
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_todo(uid)
|
||||||
|
self.todos.find {|t| t.uid == uid}
|
||||||
|
end
|
||||||
|
|
||||||
|
def journal(&block)
|
||||||
|
e = Journal.new
|
||||||
|
self.add_component e
|
||||||
|
|
||||||
|
e.instance_eval &block if block
|
||||||
|
|
||||||
|
e
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_journal(uid)
|
||||||
|
self.journals.find {|j| j.uid == uid}
|
||||||
|
end
|
||||||
|
|
||||||
|
def freebusy(&block)
|
||||||
|
e = Freebusy.new
|
||||||
|
self.add_component e
|
||||||
|
|
||||||
|
e.instance_eval &block if block
|
||||||
|
|
||||||
|
e
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_freebusy(uid)
|
||||||
|
self.freebusys.find {|f| f.uid == uid}
|
||||||
|
end
|
||||||
|
|
||||||
|
def timezone(&block)
|
||||||
|
e = Timezone.new
|
||||||
|
self.add_component e
|
||||||
|
|
||||||
|
e.instance_eval &block if block
|
||||||
|
|
||||||
|
e
|
||||||
|
end
|
||||||
|
|
||||||
|
# The "PUBLISH" method in a "VEVENT" calendar component is an
|
||||||
|
# unsolicited posting of an iCalendar object. Any CU may add published
|
||||||
|
# components to their calendar. The "Organizer" MUST be present in a
|
||||||
|
# published iCalendar component. "Attendees" MUST NOT be present. Its
|
||||||
|
# expected usage is for encapsulating an arbitrary event as an
|
||||||
|
# iCalendar object. The "Organizer" may subsequently update (with
|
||||||
|
# another "PUBLISH" method), add instances to (with an "ADD" method),
|
||||||
|
# or cancel (with a "CANCEL" method) a previously published "VEVENT"
|
||||||
|
# calendar component.
|
||||||
|
def publish
|
||||||
|
self.ip_method = "PUBLISH"
|
||||||
|
end
|
||||||
|
|
||||||
|
end # class Calendar
|
||||||
|
|
||||||
|
end # module Icalendar
|
@ -0,0 +1,438 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
module Icalendar
|
||||||
|
require 'socket'
|
||||||
|
|
||||||
|
MAX_LINE_LENGTH = 75
|
||||||
|
|
||||||
|
class Geo < Icalendar::Base
|
||||||
|
attr_accessor :latitude, :longitude
|
||||||
|
alias :lat :latitude
|
||||||
|
alias :long :longitude
|
||||||
|
|
||||||
|
def initialize(lat, long)
|
||||||
|
@lat = lat
|
||||||
|
@long = long
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_ical
|
||||||
|
"#{@lat.to_ical};#{@long.to_ical}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# The body of the iCalendar object consists of a sequence of calendar
|
||||||
|
# properties and one or more calendar components. The calendar
|
||||||
|
# properties are attributes that apply to the calendar as a whole. The
|
||||||
|
# calendar components are collections of properties that express a
|
||||||
|
# particular calendar semantic. For example, the calendar component can
|
||||||
|
# specify an Event, a Todo, a Journal entry, Timezone information, or
|
||||||
|
# Freebusy time information, or an Alarm.
|
||||||
|
class Component < Icalendar::Base
|
||||||
|
|
||||||
|
meta_include HashAttrs
|
||||||
|
|
||||||
|
attr_reader :name
|
||||||
|
attr_accessor :properties
|
||||||
|
|
||||||
|
@@multi_properties = {}
|
||||||
|
@@multiline_properties = {}
|
||||||
|
|
||||||
|
def initialize(name)
|
||||||
|
@name = name
|
||||||
|
@components = Hash.new([])
|
||||||
|
@properties = {}
|
||||||
|
|
||||||
|
@@logger.info("New #{@name[1,@name.size].capitalize}...")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add a sub-component to the current component object.
|
||||||
|
def add_component(component)
|
||||||
|
key = (component.class.to_s.downcase + 's').gsub('icalendar::', '').to_sym
|
||||||
|
|
||||||
|
unless @components.has_key? key
|
||||||
|
@components[key] = []
|
||||||
|
end
|
||||||
|
|
||||||
|
@components[key] << component
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add a component to the calendar.
|
||||||
|
alias add add_component
|
||||||
|
|
||||||
|
# Add an event to the calendar.
|
||||||
|
alias add_event add_component
|
||||||
|
|
||||||
|
# Add a todo item to the calendar.
|
||||||
|
alias add_todo add_component
|
||||||
|
|
||||||
|
# Add a journal item to the calendar.
|
||||||
|
alias add_journal add_component
|
||||||
|
|
||||||
|
def remove_component(component)
|
||||||
|
key = (component.class.to_s.downcase + 's').gsub('icalendar::', '').to_sym
|
||||||
|
|
||||||
|
if @components.has_key? key
|
||||||
|
@components[key].delete(component)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove a component from the calendar.
|
||||||
|
alias remove remove_component
|
||||||
|
|
||||||
|
# Remove an event from the calendar.
|
||||||
|
alias remove_event remove_component
|
||||||
|
|
||||||
|
# Remove a todo item from the calendar.
|
||||||
|
alias remove_todo remove_component
|
||||||
|
|
||||||
|
# Remove a journal item from the calendar.
|
||||||
|
alias remove_journal remove_component
|
||||||
|
|
||||||
|
# Used to generate unique component ids
|
||||||
|
def new_uid
|
||||||
|
"#{DateTime.now}_#{rand(999999999)}@#{Socket.gethostname}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Output in the icalendar format
|
||||||
|
def to_ical
|
||||||
|
print_component do
|
||||||
|
s = ""
|
||||||
|
@components.each_value do |comps|
|
||||||
|
comps.each { |component| s << component.to_ical }
|
||||||
|
end
|
||||||
|
s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print this icalendar component
|
||||||
|
def print_component
|
||||||
|
# Begin a new component
|
||||||
|
"BEGIN:#{@name.upcase}\r\n" +
|
||||||
|
|
||||||
|
# Then the properties
|
||||||
|
print_properties +
|
||||||
|
|
||||||
|
# sub components
|
||||||
|
yield +
|
||||||
|
|
||||||
|
# End of this component
|
||||||
|
"END:#{@name.upcase}\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print this components properties
|
||||||
|
def print_properties
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
@properties.each do |key,val|
|
||||||
|
# Take out underscore for property names that conflicted
|
||||||
|
# with built-in words.
|
||||||
|
if key =~ /ip_.*/
|
||||||
|
key = key[3..-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Property name
|
||||||
|
unless multiline_property?(key)
|
||||||
|
prelude = "#{key.gsub(/_/, '-').upcase}" +
|
||||||
|
|
||||||
|
# Possible parameters
|
||||||
|
print_parameters(val)
|
||||||
|
|
||||||
|
# Property value
|
||||||
|
value = ":#{val.to_ical}"
|
||||||
|
escaped = prelude + value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
|
||||||
|
s << escaped.slice!(0, MAX_LINE_LENGTH) << "\r\n " while escaped.size > MAX_LINE_LENGTH
|
||||||
|
s << escaped << "\r\n"
|
||||||
|
s.gsub!(/ *$/, '')
|
||||||
|
else
|
||||||
|
prelude = "#{key.gsub(/_/, '-').upcase}"
|
||||||
|
val.each do |v|
|
||||||
|
params = print_parameters(v)
|
||||||
|
value = ":#{v.to_ical}"
|
||||||
|
escaped = prelude + params + value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
|
||||||
|
s << escaped.slice!(0, MAX_LINE_LENGTH) << "\r\n " while escaped.size > MAX_LINE_LENGTH
|
||||||
|
s << escaped << "\r\n"
|
||||||
|
s.gsub!(/ *$/, '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print the parameters for a specific property
|
||||||
|
def print_parameters(val)
|
||||||
|
s = ""
|
||||||
|
return s unless val.respond_to?(:ical_params) and not val.ical_params.nil?
|
||||||
|
|
||||||
|
val.ical_params.each do |key,val|
|
||||||
|
s << ";#{key}"
|
||||||
|
val = [ val ] unless val.is_a?(Array)
|
||||||
|
|
||||||
|
# Possible parameter values
|
||||||
|
unless val.empty?
|
||||||
|
s << "="
|
||||||
|
sep = "" # First entry comes after = sign, but then we need commas
|
||||||
|
val.each do |pval|
|
||||||
|
if pval.respond_to? :to_ical
|
||||||
|
s << sep << pval.to_ical
|
||||||
|
sep = ","
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
s
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Look into the x-property, x-param stuff...
|
||||||
|
# This would really only be needed for subclassing to add additional
|
||||||
|
# properties to an application using the API.
|
||||||
|
def custom_property(name, value)
|
||||||
|
@properties[name] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def multi_property?(name)
|
||||||
|
@@multi_properties.has_key?(name.downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
def multiline_property?(name)
|
||||||
|
@@multiline_properties.has_key?(name.downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make it protected so we can monitor usage...
|
||||||
|
protected
|
||||||
|
|
||||||
|
def Component.ical_component(*syms)
|
||||||
|
hash_accessor :@components, *syms
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define a set of methods supporting a new property
|
||||||
|
def Component.ical_property(property, alias_name = nil, prop_name = nil)
|
||||||
|
property = "#{property}".strip.downcase
|
||||||
|
alias_name = "#{alias_name}".strip.downcase unless alias_name.nil?
|
||||||
|
# If a prop_name was given then we use that for the actual storage
|
||||||
|
property = "#{prop_name}".strip.downcase unless prop_name.nil?
|
||||||
|
|
||||||
|
generate_getter(property, alias_name)
|
||||||
|
generate_setter(property, alias_name)
|
||||||
|
generate_query(property, alias_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define a set of methods defining a new property, which
|
||||||
|
# supports multiple values for the same property name.
|
||||||
|
def Component.ical_multi_property(property, singular, plural)
|
||||||
|
property = "#{property}".strip.downcase.gsub(/-/, '_')
|
||||||
|
plural = "#{plural}".strip.downcase
|
||||||
|
|
||||||
|
# Set this key so the parser knows to use an array for
|
||||||
|
# storing this property type.
|
||||||
|
@@multi_properties["#{property}"] = true
|
||||||
|
|
||||||
|
generate_multi_getter(property, plural)
|
||||||
|
generate_multi_setter(property, plural)
|
||||||
|
generate_multi_query(property, plural)
|
||||||
|
generate_multi_adder(property, singular)
|
||||||
|
generate_multi_remover(property, singular)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define a set of methods defining a new property, which
|
||||||
|
# supports multiple values in multiple lines with same property name
|
||||||
|
def Component.ical_multiline_property(property, singular, plural)
|
||||||
|
@@multiline_properties["#{property}"] = true
|
||||||
|
ical_multi_property(property, singular, plural)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def Component.generate_getter(property, alias_name)
|
||||||
|
unless instance_methods.include? property
|
||||||
|
code = <<-code
|
||||||
|
def #{property}(val = nil, params = nil)
|
||||||
|
return @properties["#{property}"] if val.nil?
|
||||||
|
|
||||||
|
unless val.respond_to?(:to_ical)
|
||||||
|
raise(NotImplementedError, "Value of type (" + val.class.to_s + ") does not support to_ical method!")
|
||||||
|
end
|
||||||
|
|
||||||
|
unless params.nil?
|
||||||
|
# Extend with the parameter methods only if we have to...
|
||||||
|
unless val.respond_to?(:ical_params)
|
||||||
|
val.class.class_eval { attr_accessor :ical_params }
|
||||||
|
end
|
||||||
|
val.ical_params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
@properties["#{property}"] = val
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 219
|
||||||
|
alias_method("#{alias_name}", "#{property}") unless alias_name.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Component.generate_setter(property, alias_name)
|
||||||
|
setter = property + '='
|
||||||
|
unless instance_methods.include? setter
|
||||||
|
code = <<-code
|
||||||
|
def #{setter}(val)
|
||||||
|
#{property}(val)
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 233
|
||||||
|
alias_method("#{alias_name}=", "#{property+'='}") unless alias_name.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Component.generate_query(property, alias_name)
|
||||||
|
query = "#{property}?"
|
||||||
|
unless instance_methods.include? query
|
||||||
|
code = <<-code
|
||||||
|
def #{query}
|
||||||
|
@properties.has_key?("#{property.downcase}")
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 226
|
||||||
|
|
||||||
|
alias_method("#{alias_name}\?", "#{query}") unless alias_name.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Component.generate_multi_getter(property, plural)
|
||||||
|
# Getter for whole array
|
||||||
|
unless instance_methods.include? plural
|
||||||
|
code = <<-code
|
||||||
|
def #{plural}(a = nil)
|
||||||
|
if a.nil?
|
||||||
|
@properties["#{property}"] || []
|
||||||
|
else
|
||||||
|
self.#{plural}=(a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 186
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Component.generate_multi_setter(property, plural)
|
||||||
|
# Setter for whole array
|
||||||
|
unless instance_methods.include? plural+'+'
|
||||||
|
code = <<-code
|
||||||
|
def #{plural}=(a)
|
||||||
|
if a.respond_to?(:to_ary)
|
||||||
|
a.to_ary.each do |val|
|
||||||
|
unless val.respond_to?(:to_ical)
|
||||||
|
raise(NotImplementedError, "Property values do not support to_ical method!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@properties["#{property}"] = a.to_ary
|
||||||
|
else
|
||||||
|
raise ArgumentError, "#{plural} is a multi-property that must be an array! Use the add_[property] method to add single entries."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 198
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Component.generate_multi_query(property, plural)
|
||||||
|
# Query for any of these properties
|
||||||
|
unless instance_methods.include? plural+'?'
|
||||||
|
code = <<-code
|
||||||
|
def #{plural}?
|
||||||
|
@properties.has_key?("#{property}")
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 210
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Component.generate_multi_adder(property, singular)
|
||||||
|
adder = "add_"+singular.to_s
|
||||||
|
# Add another item to this properties array
|
||||||
|
unless instance_methods.include? adder
|
||||||
|
code = <<-code
|
||||||
|
def #{adder}(val, params = {})
|
||||||
|
unless val.respond_to?(:to_ical)
|
||||||
|
raise(NotImplementedError, "Property value object does not support to_ical method!")
|
||||||
|
end
|
||||||
|
|
||||||
|
unless params.nil?
|
||||||
|
# Extend with the parameter methods only if we have to...
|
||||||
|
unless val.respond_to?(:ical_params)
|
||||||
|
val.class.class_eval { attr_accessor :ical_params }
|
||||||
|
end
|
||||||
|
val.ical_params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
if @properties.has_key?("#{property}")
|
||||||
|
@properties["#{property}"] << val
|
||||||
|
else
|
||||||
|
@properties["#{property}"] = [val]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 289
|
||||||
|
alias_method("add_#{property.downcase}", "#{adder}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Component.generate_multi_remover(property, singular)
|
||||||
|
# Remove an item from this properties array
|
||||||
|
unless instance_methods.include? "remove_#{singular}"
|
||||||
|
code = <<-code
|
||||||
|
def remove_#{singular}(a)
|
||||||
|
if @properties.has_key?("#{property}")
|
||||||
|
@properties["#{property}"].delete(a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
class_eval code, "component.rb", 303
|
||||||
|
alias_method("remove_#{property.downcase}", "remove_#{singular}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, *args)
|
||||||
|
@@logger.debug("Inside method_missing...")
|
||||||
|
method_name = method_name.to_s.downcase
|
||||||
|
|
||||||
|
unless method_name =~ /x_.*/
|
||||||
|
raise NoMethodError, "Method Name: #{method_name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# x-properties are accessed with underscore but stored with a dash so
|
||||||
|
# they output correctly and we don't have to special case the
|
||||||
|
# output code, which would require checking every property.
|
||||||
|
if args.size > 0 # Its a setter
|
||||||
|
# Pull off the possible equals
|
||||||
|
@properties[method_name[/x_[^=]*/].gsub('x_', 'x-')] = args.first
|
||||||
|
else # Or its a getter
|
||||||
|
return @properties[method_name.gsub('x_', 'x-')]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
public
|
||||||
|
|
||||||
|
def respond_to?(method_name)
|
||||||
|
unless method_name.to_s.downcase =~ /x_.*/
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
end # class Component
|
||||||
|
end
|
@ -0,0 +1,44 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
module Icalendar
|
||||||
|
# An Alarm calendar component is a grouping of component
|
||||||
|
# properties that is a reminder or alarm for an event or a
|
||||||
|
# to-do. For example, it may be used to define a reminder for a
|
||||||
|
# pending Event or an overdue Todo.
|
||||||
|
class Alarm < Component
|
||||||
|
|
||||||
|
# Single properties
|
||||||
|
ical_property :action
|
||||||
|
ical_property :description
|
||||||
|
ical_property :trigger
|
||||||
|
ical_property :summary
|
||||||
|
|
||||||
|
# Single but must appear together
|
||||||
|
ical_property :duration
|
||||||
|
ical_property :repeat
|
||||||
|
|
||||||
|
# Single and only occurring once
|
||||||
|
|
||||||
|
ical_property :created
|
||||||
|
ical_property :last_modified
|
||||||
|
ical_property :timestamp
|
||||||
|
ical_property :sequence
|
||||||
|
|
||||||
|
# Multi properties
|
||||||
|
ical_multiline_property :attendee, :attendee, :attendees
|
||||||
|
ical_multi_property :attach, :attachment, :attachments
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("VALARM")
|
||||||
|
|
||||||
|
# Almost everyone just wants to display so I make it the
|
||||||
|
# default so it works for most people right away...
|
||||||
|
action "DISPLAY"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,123 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
module Icalendar
|
||||||
|
# A Event calendar component is a grouping of component
|
||||||
|
# properties, and possibly including Alarm calendar components, that
|
||||||
|
# represents a scheduled amount of time on a calendar. For example, it
|
||||||
|
# can be an activity; such as a one-hour long, department meeting from
|
||||||
|
# 8:00 AM to 9:00 AM, tomorrow. Generally, an event will take up time
|
||||||
|
# on an individual calendar.
|
||||||
|
class Event < Component
|
||||||
|
ical_component :alarms
|
||||||
|
|
||||||
|
## Single instance properties
|
||||||
|
|
||||||
|
# Access classification (PUBLIC, PRIVATE, CONFIDENTIAL...)
|
||||||
|
ical_property :ip_class, :klass
|
||||||
|
|
||||||
|
# Date & time of creation
|
||||||
|
ical_property :created
|
||||||
|
|
||||||
|
# Complete description of the calendar component
|
||||||
|
ical_property :description
|
||||||
|
|
||||||
|
# Specifies date-time when calendar component begins
|
||||||
|
ical_property :dtstart, :start
|
||||||
|
|
||||||
|
# Latitude & longitude for specified activity
|
||||||
|
ical_property :geo, :geo_location
|
||||||
|
|
||||||
|
# Date & time this item was last modified
|
||||||
|
ical_property :last_modified
|
||||||
|
|
||||||
|
# Specifies the intended venue for this activity
|
||||||
|
ical_property :location
|
||||||
|
|
||||||
|
# Defines organizer of this item
|
||||||
|
ical_property :organizer
|
||||||
|
|
||||||
|
# Defines relative priority for this item (1-9... 1 = best)
|
||||||
|
ical_property :priority
|
||||||
|
|
||||||
|
# Indicate date & time when this item was created
|
||||||
|
ical_property :dtstamp, :timestamp
|
||||||
|
|
||||||
|
# Revision sequence number for this item
|
||||||
|
ical_property :sequence, :seq
|
||||||
|
|
||||||
|
# Defines overall status or confirmation of this item
|
||||||
|
ical_property :status
|
||||||
|
ical_property :summary
|
||||||
|
ical_property :transp, :transparency
|
||||||
|
|
||||||
|
# Defines a persistent, globally unique id for this item
|
||||||
|
ical_property :uid, :unique_id
|
||||||
|
|
||||||
|
# Defines a URL associated with this item
|
||||||
|
ical_property :url
|
||||||
|
ical_property :recurid, :recurrence_id
|
||||||
|
|
||||||
|
## Single but mutually exclusive properties (Not testing though)
|
||||||
|
|
||||||
|
# Specifies a date and time that this item ends
|
||||||
|
ical_property :dtend, :end
|
||||||
|
|
||||||
|
# Specifies a positive duration time
|
||||||
|
ical_property :duration
|
||||||
|
|
||||||
|
## Multi-instance properties
|
||||||
|
|
||||||
|
# Associates a URI or binary blob with this item
|
||||||
|
ical_multi_property :attach, :attachment, :attachments
|
||||||
|
|
||||||
|
# Defines an attendee for this calendar item
|
||||||
|
ical_multiline_property :attendee, :attendee, :attendees
|
||||||
|
|
||||||
|
# Defines the categories for a calendar component (school, work...)
|
||||||
|
ical_multi_property :categories, :category, :categories
|
||||||
|
|
||||||
|
# Simple comment for the calendar user.
|
||||||
|
ical_multi_property :comment, :comment, :comments
|
||||||
|
|
||||||
|
# Contact information associated with this item.
|
||||||
|
ical_multi_property :contact, :contact, :contacts
|
||||||
|
ical_multi_property :exdate, :exception_date, :exception_dates
|
||||||
|
ical_multi_property :exrule, :exception_rule, :exception_rules
|
||||||
|
ical_multi_property :rstatus, :request_status, :request_statuses
|
||||||
|
|
||||||
|
# Used to represent a relationship between two calendar items
|
||||||
|
ical_multi_property :related_to, :related_to, :related_tos
|
||||||
|
ical_multi_property :resources, :resource, :resources
|
||||||
|
|
||||||
|
# Used with the UID & SEQUENCE to identify a specific instance of a
|
||||||
|
# recurring calendar item.
|
||||||
|
ical_multi_property :rdate, :recurrence_date, :recurrence_dates
|
||||||
|
ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("VEVENT")
|
||||||
|
|
||||||
|
# Now doing some basic initialization
|
||||||
|
sequence 0
|
||||||
|
timestamp DateTime.now
|
||||||
|
uid new_uid
|
||||||
|
end
|
||||||
|
|
||||||
|
def alarm(&block)
|
||||||
|
a = Alarm.new
|
||||||
|
self.add a
|
||||||
|
|
||||||
|
a.instance_eval &block if block
|
||||||
|
|
||||||
|
a
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,37 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
module Icalendar
|
||||||
|
# A Freebusy calendar component is a grouping of
|
||||||
|
# component properties that represents either a request for, a reply to
|
||||||
|
# a request for free or busy time information or a published set of
|
||||||
|
# busy time information.
|
||||||
|
class Freebusy < Component
|
||||||
|
# Single properties
|
||||||
|
ical_property :contact
|
||||||
|
ical_property :dtstart, :start
|
||||||
|
ical_property :dtend, :end
|
||||||
|
ical_property :dtstamp, :timestamp
|
||||||
|
ical_property :duration
|
||||||
|
ical_property :organizer
|
||||||
|
ical_property :uid, :user_id
|
||||||
|
ical_property :url
|
||||||
|
|
||||||
|
# Multi-properties
|
||||||
|
ical_multiline_property :attendee, :attendee, :attendees
|
||||||
|
ical_multi_property :comment, :comment, :comments
|
||||||
|
ical_multiline_property :freebusy, :freebusy, :freebusys
|
||||||
|
ical_multi_property :rstatus, :request_status, :request_statuses
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("VFREEBUSY")
|
||||||
|
|
||||||
|
timestamp DateTime.now
|
||||||
|
uid new_uid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,61 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
module Icalendar
|
||||||
|
# A Journal calendar component is a grouping of
|
||||||
|
# component properties that represent one or more descriptive text
|
||||||
|
# notes associated with a particular calendar date. The "DTSTART"
|
||||||
|
# property is used to specify the calendar date that the journal entry
|
||||||
|
# is associated with. Generally, it will have a DATE value data type,
|
||||||
|
# but it can also be used to specify a DATE-TIME value data type.
|
||||||
|
# Examples of a journal entry include a daily record of a legislative
|
||||||
|
# body or a journal entry of individual telephone contacts for the day
|
||||||
|
# or an ordered list of accomplishments for the day. The Journal
|
||||||
|
# calendar component can also be used to associate a document with a
|
||||||
|
# calendar date.
|
||||||
|
class Journal < Component
|
||||||
|
|
||||||
|
# Single properties
|
||||||
|
ical_property :ip_class
|
||||||
|
ical_property :created
|
||||||
|
ical_property :description
|
||||||
|
ical_property :dtstart, :start
|
||||||
|
ical_property :last_modified
|
||||||
|
ical_property :organizer
|
||||||
|
ical_property :dtstamp, :timestamp
|
||||||
|
ical_property :sequence, :seq
|
||||||
|
ical_property :status
|
||||||
|
ical_property :summary
|
||||||
|
ical_property :uid, :user_id
|
||||||
|
ical_property :url
|
||||||
|
ical_property :recurid, :recurrence_id
|
||||||
|
|
||||||
|
# Multi-properties
|
||||||
|
ical_multi_property :attach, :attachment, :attachments
|
||||||
|
ical_multiline_property :attendee, :attendee, :attendees
|
||||||
|
ical_multi_property :categories, :category, :categories
|
||||||
|
ical_multi_property :comment, :comment, :comments
|
||||||
|
ical_multi_property :contact, :contact, :contacts
|
||||||
|
ical_multi_property :exdate, :exception_date, :exception_dates
|
||||||
|
ical_multi_property :exrule, :exception_rule, :exception_rules
|
||||||
|
ical_multi_property :rstatus, :request_status, :request_statuses
|
||||||
|
ical_multi_property :related_to, :related_to, :related_tos
|
||||||
|
ical_multi_property :resources, :resource, :resources
|
||||||
|
ical_multi_property :rdate, :recurrence_date, :recurrence_dates
|
||||||
|
ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("VJOURNAL")
|
||||||
|
|
||||||
|
sequence 0
|
||||||
|
timestamp DateTime.now
|
||||||
|
uid new_uid
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,87 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
module Icalendar
|
||||||
|
# A Timezone is unambiguously defined by the set of time
|
||||||
|
# measurement rules determined by the governing body for a given
|
||||||
|
# geographic area. These rules describe at a minimum the base offset
|
||||||
|
# from UTC for the time zone, often referred to as the Standard Time
|
||||||
|
# offset. Many locations adjust their Standard Time forward or backward
|
||||||
|
# by one hour, in order to accommodate seasonal changes in number of
|
||||||
|
# daylight hours, often referred to as Daylight Saving Time. Some
|
||||||
|
# locations adjust their time by a fraction of an hour. Standard Time
|
||||||
|
# is also known as Winter Time. Daylight Saving Time is also known as
|
||||||
|
# Advanced Time, Summer Time, or Legal Time in certain countries. The
|
||||||
|
# following table shows the changes in time zone rules in effect for
|
||||||
|
# New York City starting from 1967. Each line represents a description
|
||||||
|
# or rule for a particular observance.
|
||||||
|
class Timezone < Component
|
||||||
|
ical_component :standard, :daylight
|
||||||
|
|
||||||
|
# Single properties
|
||||||
|
ical_property :dtstart, :start
|
||||||
|
ical_property :tzoffsetto, :timezone_offset_to
|
||||||
|
ical_property :tzoffsetfrom, :timezone_offset_from
|
||||||
|
ical_property :tzid, :timezone_id
|
||||||
|
ical_property :tzname, :timezone_name
|
||||||
|
|
||||||
|
ical_property :created
|
||||||
|
ical_property :last_modified
|
||||||
|
ical_property :timestamp
|
||||||
|
ical_property :sequence
|
||||||
|
|
||||||
|
# Multi-properties
|
||||||
|
ical_multi_property :comment, :comment, :comments
|
||||||
|
ical_multi_property :rdate, :recurrence_date, :recurrence_dates
|
||||||
|
ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
|
||||||
|
|
||||||
|
# Define a custom add component method because standard and daylight
|
||||||
|
# are the only components that can occur just once with their parent.
|
||||||
|
def add_component(component)
|
||||||
|
key = component.class.to_s.downcase.gsub('icalendar::','').to_sym
|
||||||
|
@components[key] = component
|
||||||
|
end
|
||||||
|
|
||||||
|
# Also need a custom to_ical because typically it iterates over an array
|
||||||
|
# of components.
|
||||||
|
def to_ical
|
||||||
|
print_component do
|
||||||
|
s = ""
|
||||||
|
@components.each_value do |comp|
|
||||||
|
s << comp.to_ical
|
||||||
|
end
|
||||||
|
s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(name = "VTIMEZONE")
|
||||||
|
super(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# A Standard component is a sub-component of the Timezone component which
|
||||||
|
# is used to describe the standard time offset.
|
||||||
|
class Standard < Timezone
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("STANDARD")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# A Daylight component is a sub-component of the Timezone component which
|
||||||
|
# is used to describe the time offset for what is commonly known as
|
||||||
|
# daylight savings time.
|
||||||
|
class Daylight < Timezone
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("DAYLIGHT")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -0,0 +1,64 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
module Icalendar
|
||||||
|
# A Todo calendar component is a grouping of component
|
||||||
|
# properties and possibly Alarm calendar components that represent
|
||||||
|
# an action-item or assignment. For example, it can be used to
|
||||||
|
# represent an item of work assigned to an individual; such as "turn in
|
||||||
|
# travel expense today".
|
||||||
|
class Todo < Component
|
||||||
|
ical_component :alarms
|
||||||
|
|
||||||
|
# Single properties
|
||||||
|
ical_property :ip_class
|
||||||
|
ical_property :completed
|
||||||
|
ical_property :created
|
||||||
|
ical_property :description
|
||||||
|
ical_property :dtstamp, :timestamp
|
||||||
|
ical_property :dtstart, :start
|
||||||
|
ical_property :geo
|
||||||
|
ical_property :last_modified
|
||||||
|
ical_property :location
|
||||||
|
ical_property :organizer
|
||||||
|
ical_property :percent
|
||||||
|
ical_property :priority
|
||||||
|
ical_property :recurid, :recurrence_id
|
||||||
|
ical_property :seq, :sequence
|
||||||
|
ical_property :status
|
||||||
|
ical_property :summary
|
||||||
|
ical_property :uid, :user_id
|
||||||
|
ical_property :url
|
||||||
|
|
||||||
|
# Single but mutually exclusive TODO: not testing anything yet
|
||||||
|
ical_property :due
|
||||||
|
ical_property :duration
|
||||||
|
|
||||||
|
# Multi-properties
|
||||||
|
ical_multi_property :attach, :attachment, :attachments
|
||||||
|
ical_multiline_property :attendee, :attendee, :attendees
|
||||||
|
ical_multi_property :categories, :category, :categories
|
||||||
|
ical_multi_property :comment, :comment, :comments
|
||||||
|
ical_multi_property :contact, :contact, :contacts
|
||||||
|
ical_multi_property :exdate, :exception_date, :exception_dates
|
||||||
|
ical_multi_property :exrule, :exception_rule, :exception_rules
|
||||||
|
ical_multi_property :rstatus, :request_status, :request_statuses
|
||||||
|
ical_multi_property :related_to, :related_to, :related_tos
|
||||||
|
ical_multi_property :resources, :resource, :resources
|
||||||
|
ical_multi_property :rdate, :recurrence_date, :recurrence_dates
|
||||||
|
ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
super("VTODO")
|
||||||
|
|
||||||
|
sequence 0
|
||||||
|
timestamp DateTime.now
|
||||||
|
uid new_uid
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,133 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
### Add some to_ical methods to classes
|
||||||
|
|
||||||
|
# class Object
|
||||||
|
# def to_ical
|
||||||
|
# raise(NotImplementedError, "This object does not implement the to_ical method!")
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
require 'uri/generic'
|
||||||
|
|
||||||
|
class String
|
||||||
|
def to_ical
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Fixnum
|
||||||
|
def to_ical
|
||||||
|
"#{self}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Float
|
||||||
|
def to_ical
|
||||||
|
"#{self}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# From the spec: "Values in a list of values MUST be separated by a COMMA
|
||||||
|
# character (US-ASCII decimal 44)."
|
||||||
|
class Array
|
||||||
|
def to_ical
|
||||||
|
map{|elem| elem.to_ical}.join ','
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module URI
|
||||||
|
class Generic
|
||||||
|
def to_ical
|
||||||
|
"#{self}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DateTime < Date
|
||||||
|
def to_ical(utc = false)
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
# 4 digit year
|
||||||
|
s << self.year.to_s
|
||||||
|
|
||||||
|
# Double digit month
|
||||||
|
s << "0" unless self.month > 9
|
||||||
|
s << self.month.to_s
|
||||||
|
|
||||||
|
# Double digit day
|
||||||
|
s << "0" unless self.day > 9
|
||||||
|
s << self.day.to_s
|
||||||
|
|
||||||
|
s << "T"
|
||||||
|
|
||||||
|
# Double digit hour
|
||||||
|
s << "0" unless self.hour > 9
|
||||||
|
s << self.hour.to_s
|
||||||
|
|
||||||
|
# Double digit minute
|
||||||
|
s << "0" unless self.min > 9
|
||||||
|
s << self.min.to_s
|
||||||
|
|
||||||
|
# Double digit second
|
||||||
|
s << "0" unless self.sec > 9
|
||||||
|
s << self.sec.to_s
|
||||||
|
|
||||||
|
# UTC time gets a Z suffix
|
||||||
|
if utc
|
||||||
|
s << "Z"
|
||||||
|
end
|
||||||
|
|
||||||
|
s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Date
|
||||||
|
def to_ical(utc = false)
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
# 4 digit year
|
||||||
|
s << self.year.to_s
|
||||||
|
|
||||||
|
# Double digit month
|
||||||
|
s << "0" unless self.month > 9
|
||||||
|
s << self.month.to_s
|
||||||
|
|
||||||
|
# Double digit day
|
||||||
|
s << "0" unless self.day > 9
|
||||||
|
s << self.day.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Time
|
||||||
|
def to_ical(utc = false)
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
# Double digit hour
|
||||||
|
s << "0" unless self.hour > 9
|
||||||
|
s << self.hour.to_s
|
||||||
|
|
||||||
|
# Double digit minute
|
||||||
|
s << "0" unless self.min > 9
|
||||||
|
s << self.min.to_s
|
||||||
|
|
||||||
|
# Double digit second
|
||||||
|
s << "0" unless self.sec > 9
|
||||||
|
s << self.sec.to_s
|
||||||
|
|
||||||
|
# UTC time gets a Z suffix
|
||||||
|
if utc
|
||||||
|
s << "Z"
|
||||||
|
end
|
||||||
|
|
||||||
|
s
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,109 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
Copyright (C) 2005 Sam Roberts
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
module Icalendar
|
||||||
|
module DateProp
|
||||||
|
# date = date-fullyear date-month date-mday
|
||||||
|
# date-fullyear = 4 DIGIT
|
||||||
|
# date-month = 2 DIGIT
|
||||||
|
# date-mday = 2 DIGIT
|
||||||
|
DATE = '(\d\d\d\d)(\d\d)(\d\d)'
|
||||||
|
|
||||||
|
# time = time-hour [":"] time-minute [":"] time-second [time-secfrac] [time-zone]
|
||||||
|
# time-hour = 2 DIGIT
|
||||||
|
# time-minute = 2 DIGIT
|
||||||
|
# time-second = 2 DIGIT
|
||||||
|
# time-secfrac = "," 1*DIGIT
|
||||||
|
# time-zone = "Z" / time-numzone
|
||||||
|
# time-numzome = sign time-hour [":"] time-minute
|
||||||
|
# TIME = '(\d\d)(\d\d)(\d\d)(Z)?'
|
||||||
|
TIME = '(\d\d)(\d\d)(\d\d)'
|
||||||
|
|
||||||
|
# This method is called automatically when the module is mixed in.
|
||||||
|
# I guess you have to do this to mixin class methods rather than instance methods.
|
||||||
|
def self.append_features(base)
|
||||||
|
super
|
||||||
|
klass.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is made a sub-module just so it can be added as class
|
||||||
|
# methods rather than instance methods.
|
||||||
|
module ClassMethods
|
||||||
|
def date_property(dp, alias_name = nil)
|
||||||
|
dp = "#{dp}".strip.downcase
|
||||||
|
getter = dp
|
||||||
|
setter = "#{dp}="
|
||||||
|
query = "#{dp}?"
|
||||||
|
|
||||||
|
unless instance_methods.include? getter
|
||||||
|
code = <<-code
|
||||||
|
def #{getter}(*a)
|
||||||
|
if a.empty?
|
||||||
|
@properties[#{dp.upcase}]
|
||||||
|
else
|
||||||
|
self.#{dp} = a.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
module_eval code
|
||||||
|
end
|
||||||
|
|
||||||
|
unless instance_methods.include? setter
|
||||||
|
code = <<-code
|
||||||
|
def #{setter} a
|
||||||
|
@properties[#{dp.upcase}] = a
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
module_eval code
|
||||||
|
end
|
||||||
|
|
||||||
|
unless instance_methods.include? query
|
||||||
|
code = <<-code
|
||||||
|
def #{query}
|
||||||
|
@properties.has_key?(#{dp.upcase})
|
||||||
|
end
|
||||||
|
code
|
||||||
|
|
||||||
|
module_eval code
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define the getter
|
||||||
|
getter = "get#{property.to_s.capitalize}"
|
||||||
|
define_method(getter.to_sym) do
|
||||||
|
puts "inside getting..."
|
||||||
|
getDateProperty(property.to_s.upcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define the setter
|
||||||
|
setter = "set#{property.to_s.capitalize}"
|
||||||
|
define_method(setter.to_sym) do |*params|
|
||||||
|
date = params[0]
|
||||||
|
utc = params[1]
|
||||||
|
puts "inside setting..."
|
||||||
|
setDateProperty(property.to_s.upcase, date, utc)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create aliases if a name was specified
|
||||||
|
# if not aliasName.nil?
|
||||||
|
# gasym = "get#{aliasName.to_s.capitalize}".to_sym
|
||||||
|
# gsym = getter.to_sym
|
||||||
|
# alias gasym gsym
|
||||||
|
|
||||||
|
# sasym = "set#{aliasName.to_s.capitalize}".to_sym
|
||||||
|
# ssym = setter.to_sym
|
||||||
|
# alias sasym ssym
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,33 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
module Icalendar
|
||||||
|
|
||||||
|
# A property can have attributes associated with it. These "property
|
||||||
|
# parameters" contain meta-information about the property or the
|
||||||
|
# property value. Property parameters are provided to specify such
|
||||||
|
# information as the location of an alternate text representation for a
|
||||||
|
# property value, the language of a text property value, the data type
|
||||||
|
# of the property value and other attributes.
|
||||||
|
class Parameter < Icalendar::Content
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
s << "#{@name}="
|
||||||
|
if is_escapable?
|
||||||
|
s << escape(print_value())
|
||||||
|
else
|
||||||
|
s << print_value
|
||||||
|
end
|
||||||
|
|
||||||
|
s
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,375 @@
|
|||||||
|
=begin
|
||||||
|
Copyright (C) 2005 Jeff Rose
|
||||||
|
Copyright (C) 2005 Sam Roberts
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as the ruby language itself, see the file COPYING for
|
||||||
|
details.
|
||||||
|
=end
|
||||||
|
|
||||||
|
require 'date'
|
||||||
|
require 'uri'
|
||||||
|
require 'stringio'
|
||||||
|
|
||||||
|
module Icalendar
|
||||||
|
def Icalendar.parse(src, single = false)
|
||||||
|
cals = Icalendar::Parser.new(src).parse
|
||||||
|
|
||||||
|
if single
|
||||||
|
cals.first
|
||||||
|
else
|
||||||
|
cals
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Parser < Icalendar::Base
|
||||||
|
# date = date-fullyear ["-"] date-month ["-"] date-mday
|
||||||
|
# date-fullyear = 4 DIGIT
|
||||||
|
# date-month = 2 DIGIT
|
||||||
|
# date-mday = 2 DIGIT
|
||||||
|
DATE = '(\d\d\d\d)-?(\d\d)-?(\d\d)'
|
||||||
|
|
||||||
|
# time = time-hour [":"] time-minute [":"] time-second [time-secfrac] [time-zone]
|
||||||
|
# time-hour = 2 DIGIT
|
||||||
|
# time-minute = 2 DIGIT
|
||||||
|
# time-second = 2 DIGIT
|
||||||
|
# time-secfrac = "," 1*DIGIT
|
||||||
|
# time-zone = "Z" / time-numzone
|
||||||
|
# time-numzome = sign time-hour [":"] time-minute
|
||||||
|
TIME = '(\d\d):?(\d\d):?(\d\d)(\.\d+)?(Z|[-+]\d\d:?\d\d)?'
|
||||||
|
|
||||||
|
def initialize(src)
|
||||||
|
# Setup the parser method hash table
|
||||||
|
setup_parsers()
|
||||||
|
|
||||||
|
if src.respond_to?(:gets)
|
||||||
|
@file = src
|
||||||
|
elsif (not src.nil?) and src.respond_to?(:to_s)
|
||||||
|
@file = StringIO.new(src.to_s, 'r')
|
||||||
|
else
|
||||||
|
raise ArgumentError, "CalendarParser.new cannot be called with a #{src.class} type!"
|
||||||
|
end
|
||||||
|
|
||||||
|
@prev_line = @file.gets
|
||||||
|
@prev_line.chomp! unless @prev_line.nil?
|
||||||
|
|
||||||
|
@@logger.debug("New Calendar Parser: #{@file.inspect}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define next line for an IO object.
|
||||||
|
# Works for strings now with StringIO
|
||||||
|
def next_line
|
||||||
|
line = @prev_line
|
||||||
|
|
||||||
|
if line.nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Loop through until we get to a non-continuation line...
|
||||||
|
loop do
|
||||||
|
nextLine = @file.gets
|
||||||
|
@@logger.debug "new_line: #{nextLine}"
|
||||||
|
|
||||||
|
if !nextLine.nil?
|
||||||
|
nextLine.chomp!
|
||||||
|
end
|
||||||
|
|
||||||
|
# If it's a continuation line, add it to the last.
|
||||||
|
# If it's an empty line, drop it from the input.
|
||||||
|
if( nextLine =~ /^[ \t]/ )
|
||||||
|
line << nextLine[1, nextLine.size]
|
||||||
|
elsif( nextLine =~ /^$/ )
|
||||||
|
else
|
||||||
|
@prev_line = nextLine
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
line
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parse the calendar into an object representation
|
||||||
|
def parse
|
||||||
|
calendars = []
|
||||||
|
|
||||||
|
@@logger.debug "parsing..."
|
||||||
|
# Outer loop for Calendar objects
|
||||||
|
while (line = next_line)
|
||||||
|
fields = parse_line(line)
|
||||||
|
|
||||||
|
# Just iterate through until we find the beginning of a calendar object
|
||||||
|
if fields[:name] == "BEGIN" and fields[:value] == "VCALENDAR"
|
||||||
|
cal = parse_component
|
||||||
|
@@logger.debug "Added parsed calendar..."
|
||||||
|
calendars << cal
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
calendars
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Parse a single VCALENDAR object
|
||||||
|
# -- This should consist of the PRODID, VERSION, option METHOD & CALSCALE,
|
||||||
|
# and then one or more calendar components: VEVENT, VTODO, VJOURNAL,
|
||||||
|
# VFREEBUSY, VTIMEZONE
|
||||||
|
def parse_component(component = Calendar.new)
|
||||||
|
@@logger.debug "parsing new component..."
|
||||||
|
|
||||||
|
while (line = next_line)
|
||||||
|
fields = parse_line(line)
|
||||||
|
|
||||||
|
name = fields[:name].upcase
|
||||||
|
|
||||||
|
# Although properties are supposed to come before components, we should
|
||||||
|
# be able to handle them in any order...
|
||||||
|
if name == "END"
|
||||||
|
break
|
||||||
|
elsif name == "BEGIN" # New component
|
||||||
|
case(fields[:value])
|
||||||
|
when "VEVENT" # Event
|
||||||
|
component.add_component parse_component(Event.new)
|
||||||
|
when "VTODO" # Todo entry
|
||||||
|
component.add_component parse_component(Todo.new)
|
||||||
|
when "VALARM" # Alarm sub-component for event and todo
|
||||||
|
component.add_component parse_component(Alarm.new)
|
||||||
|
when "VJOURNAL" # Journal entry
|
||||||
|
component.add_component parse_component(Journal.new)
|
||||||
|
when "VFREEBUSY" # Free/Busy section
|
||||||
|
component.add_component parse_component(Freebusy.new)
|
||||||
|
when "VTIMEZONE" # Timezone specification
|
||||||
|
component.add_component parse_component(Timezone.new)
|
||||||
|
when "STANDARD" # Standard time sub-component for timezone
|
||||||
|
component.add_component parse_component(Standard.new)
|
||||||
|
when "DAYLIGHT" # Daylight time sub-component for timezone
|
||||||
|
component.add_component parse_component(Daylight.new)
|
||||||
|
else # Uknown component type, skip to matching end
|
||||||
|
until ((line = next_line) == "END:#{fields[:value]}"); end
|
||||||
|
next
|
||||||
|
end
|
||||||
|
else # If its not a component then it should be a property
|
||||||
|
params = fields[:params]
|
||||||
|
value = fields[:value]
|
||||||
|
|
||||||
|
# Lookup the property name to see if we have a string to
|
||||||
|
# object parser for this property type.
|
||||||
|
if @parsers.has_key?(name)
|
||||||
|
value = @parsers[name].call(name, params, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
name = name.downcase
|
||||||
|
|
||||||
|
# TODO: check to see if there are any more conflicts.
|
||||||
|
if name == 'class' or name == 'method'
|
||||||
|
name = "ip_" + name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Replace dashes with underscores
|
||||||
|
name = name.gsub('-', '_')
|
||||||
|
|
||||||
|
if component.multi_property?(name)
|
||||||
|
adder = "add_" + name
|
||||||
|
if component.respond_to?(adder)
|
||||||
|
component.send(adder, value, params)
|
||||||
|
else
|
||||||
|
raise(UnknownPropertyMethod, "Unknown property type: #{adder}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if component.respond_to?(name)
|
||||||
|
component.send(name, value, params)
|
||||||
|
else
|
||||||
|
raise(UnknownPropertyMethod, "Unknown property type: #{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
component
|
||||||
|
end
|
||||||
|
|
||||||
|
# 1*(ALPHA / DIGIT / "=")
|
||||||
|
NAME = '[-a-z0-9]+'
|
||||||
|
|
||||||
|
# <"> <Any character except CTLs, DQUOTE> <">
|
||||||
|
QSTR = '"[^"]*"'
|
||||||
|
|
||||||
|
# Contentline
|
||||||
|
LINE = "(#{NAME})(.*(?:#{QSTR})|(?:[^:]*))\:(.*)"
|
||||||
|
|
||||||
|
# *<Any character except CTLs, DQUOTE, ";", ":", ",">
|
||||||
|
PTEXT = '[^";:,]*'
|
||||||
|
|
||||||
|
# param-value = ptext / quoted-string
|
||||||
|
PVALUE = "#{QSTR}|#{PTEXT}"
|
||||||
|
|
||||||
|
# param = name "=" param-value *("," param-value)
|
||||||
|
PARAM = ";(#{NAME})(=?)((?:#{PVALUE})(?:,#{PVALUE})*)"
|
||||||
|
|
||||||
|
def parse_line(line)
|
||||||
|
unless line =~ %r{#{LINE}}i # Case insensitive match for a valid line
|
||||||
|
raise "Invalid line in calendar string!"
|
||||||
|
end
|
||||||
|
|
||||||
|
name = $1.upcase # The case insensitive part is upcased for easier comparison...
|
||||||
|
paramslist = $2
|
||||||
|
value = $3.gsub("\\;", ";").gsub("\\,", ",").gsub("\\n", "\n").gsub("\\\\", "\\")
|
||||||
|
|
||||||
|
# Parse the parameters
|
||||||
|
params = {}
|
||||||
|
if paramslist.size > 1
|
||||||
|
paramslist.scan( %r{#{PARAM}}i ) do
|
||||||
|
|
||||||
|
# parameter names are case-insensitive, and multi-valued
|
||||||
|
pname = $1
|
||||||
|
pvals = $3
|
||||||
|
|
||||||
|
# If their isn't an '=' sign then we need to do some custom
|
||||||
|
# business. Defaults to 'type'
|
||||||
|
if $2 == ""
|
||||||
|
pvals = $1
|
||||||
|
case $1
|
||||||
|
when /quoted-printable/i
|
||||||
|
pname = 'encoding'
|
||||||
|
|
||||||
|
when /base64/i
|
||||||
|
pname = 'encoding'
|
||||||
|
|
||||||
|
else
|
||||||
|
pname = 'type'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make entries into the params dictionary where the name
|
||||||
|
# is the key and the value is an array of values.
|
||||||
|
unless params.key? pname
|
||||||
|
params[pname] = []
|
||||||
|
end
|
||||||
|
|
||||||
|
# Save all the values into the array.
|
||||||
|
pvals.scan( %r{(#{PVALUE})} ) do
|
||||||
|
if $1.size > 0
|
||||||
|
params[pname] << $1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{:name => name, :value => value, :params => params}
|
||||||
|
end
|
||||||
|
|
||||||
|
## Following is a collection of parsing functions for various
|
||||||
|
## icalendar property value data types... First we setup
|
||||||
|
## a hash with property names pointing to methods...
|
||||||
|
def setup_parsers
|
||||||
|
@parsers = {}
|
||||||
|
|
||||||
|
# Integer properties
|
||||||
|
m = self.method(:parse_integer)
|
||||||
|
@parsers["PERCENT-COMPLETE"] = m
|
||||||
|
@parsers["PRIORITY"] = m
|
||||||
|
@parsers["REPEAT"] = m
|
||||||
|
@parsers["SEQUENCE"] = m
|
||||||
|
|
||||||
|
# Dates and Times
|
||||||
|
m = self.method(:parse_datetime)
|
||||||
|
@parsers["COMPLETED"] = m
|
||||||
|
@parsers["DTEND"] = m
|
||||||
|
@parsers["DUE"] = m
|
||||||
|
@parsers["DTSTART"] = m
|
||||||
|
@parsers["RECURRENCE-ID"] = m
|
||||||
|
@parsers["EXDATE"] = m
|
||||||
|
@parsers["RDATE"] = m
|
||||||
|
@parsers["CREATED"] = m
|
||||||
|
@parsers["DTSTAMP"] = m
|
||||||
|
@parsers["LAST-MODIFIED"] = m
|
||||||
|
|
||||||
|
# URI's
|
||||||
|
m = self.method(:parse_uri)
|
||||||
|
@parsers["TZURL"] = m
|
||||||
|
@parsers["ATTENDEE"] = m
|
||||||
|
@parsers["ORGANIZER"] = m
|
||||||
|
@parsers["URL"] = m
|
||||||
|
|
||||||
|
# This is a URI by default, and if its not a valid URI
|
||||||
|
# it will be returned as a string which works for binary data
|
||||||
|
# the other possible type.
|
||||||
|
@parsers["ATTACH"] = m
|
||||||
|
|
||||||
|
# GEO
|
||||||
|
m = self.method(:parse_geo)
|
||||||
|
@parsers["GEO"] = m
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Booleans
|
||||||
|
# NOTE: It appears that although this is a valid data type
|
||||||
|
# there aren't any properties that use it... Maybe get
|
||||||
|
# rid of this in the future.
|
||||||
|
def parse_boolean(name, params, value)
|
||||||
|
if value.upcase == "FALSE"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Dates, Date-Times & Times
|
||||||
|
# NOTE: invalid dates & times will be returned as strings...
|
||||||
|
def parse_datetime(name, params, value)
|
||||||
|
begin
|
||||||
|
DateTime.parse(value)
|
||||||
|
rescue Exception
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Durations
|
||||||
|
# TODO: Need to figure out the best way to represent durations
|
||||||
|
# so just returning string for now.
|
||||||
|
def parse_duration(name, params, value)
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
# Floats
|
||||||
|
# NOTE: returns 0.0 if it can't parse the value
|
||||||
|
def parse_float(name, params, value)
|
||||||
|
value.to_f
|
||||||
|
end
|
||||||
|
|
||||||
|
# Integers
|
||||||
|
# NOTE: returns 0 if it can't parse the value
|
||||||
|
def parse_integer(name, params, value)
|
||||||
|
value.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# Periods
|
||||||
|
# TODO: Got to figure out how to represent periods also...
|
||||||
|
def parse_period(name, params, value)
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calendar Address's & URI's
|
||||||
|
# NOTE: invalid URI's will be returned as strings...
|
||||||
|
def parse_uri(name, params, value)
|
||||||
|
begin
|
||||||
|
URI.parse(value)
|
||||||
|
rescue Exception
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Geographical location (GEO)
|
||||||
|
# NOTE: returns an array with two floats (long & lat)
|
||||||
|
# if the parsing fails return the string
|
||||||
|
def parse_geo(name, params, value)
|
||||||
|
strloc = value.split(';')
|
||||||
|
if strloc.size != 2
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
Geo.new(strloc[0].to_f, strloc[1].to_f)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,32 @@
|
|||||||
|
# A set of methods to help create meta-programming gizmos.
|
||||||
|
class Object
|
||||||
|
# The metaclass is the singleton behind every object.
|
||||||
|
def metaclass
|
||||||
|
class << self
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Evaluates the block in the context of the metaclass
|
||||||
|
def meta_eval &blk
|
||||||
|
metaclass.instance_eval &blk
|
||||||
|
end
|
||||||
|
|
||||||
|
# Acts like an include except it adds the module's methods
|
||||||
|
# to the metaclass so they act like class methods.
|
||||||
|
def meta_include mod
|
||||||
|
meta_eval do
|
||||||
|
include mod
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Adds methods to a metaclass
|
||||||
|
def meta_def name, &blk
|
||||||
|
meta_eval { define_method name, &blk }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines an instance method within a class
|
||||||
|
def class_def name, &blk
|
||||||
|
class_eval { define_method name, &blk }
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,71 @@
|
|||||||
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
class TestCalendar < Test::Unit::TestCase
|
||||||
|
include Icalendar
|
||||||
|
# Generate a calendar using the raw api, and then spit it out
|
||||||
|
# as a string. Parse the string and make sure everything matches up.
|
||||||
|
def test_raw_generation
|
||||||
|
# Create a fresh calendar
|
||||||
|
cal = Calendar.new
|
||||||
|
|
||||||
|
cal.calscale = "GREGORIAN"
|
||||||
|
cal.version = "3.2"
|
||||||
|
cal.prodid = "test-prodid"
|
||||||
|
|
||||||
|
# Now generate the string and then parse it so we can verify
|
||||||
|
# that everything was set, generated and parsed correctly.
|
||||||
|
calString = cal.to_ical
|
||||||
|
|
||||||
|
cals = Parser.new(calString).parse
|
||||||
|
|
||||||
|
cal2 = cals.first
|
||||||
|
assert_equal("GREGORIAN", cal2.calscale)
|
||||||
|
assert_equal("3.2", cal2.version)
|
||||||
|
assert_equal("test-prodid", cal2.prodid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_creation
|
||||||
|
cal = Calendar.new
|
||||||
|
cal.event do
|
||||||
|
self.dtend = "19970903T190000Z"
|
||||||
|
self.summary = "This is my summary"
|
||||||
|
end
|
||||||
|
|
||||||
|
event = cal.event
|
||||||
|
event.dtend "19970903T190000Z", {:TZID => "Europe/Copenhagen"}
|
||||||
|
event.summary "This is my summary"
|
||||||
|
|
||||||
|
ev = cal.events.each do |ev|
|
||||||
|
assert_equal("19970903T190000Z", ev.dtend)
|
||||||
|
assert_equal("This is my summary", ev.summary)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_find
|
||||||
|
cal = Calendar.new
|
||||||
|
|
||||||
|
# add some events so we actually have to search
|
||||||
|
10.times do
|
||||||
|
cal.event
|
||||||
|
cal.todo
|
||||||
|
cal.journal
|
||||||
|
cal.freebusy
|
||||||
|
end
|
||||||
|
event = cal.events[5]
|
||||||
|
assert_equal(event, cal.find_event(event.uid))
|
||||||
|
|
||||||
|
todo = cal.todos[5]
|
||||||
|
assert_equal(todo, cal.find_todo(todo.uid))
|
||||||
|
|
||||||
|
journal = cal.journals[5]
|
||||||
|
assert_equal(journal, cal.find_journal(journal.uid))
|
||||||
|
|
||||||
|
freebusy = cal.freebusys[5]
|
||||||
|
assert_equal(freebusy, cal.find_freebusy(freebusy.uid))
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,44 @@
|
|||||||
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
# Define a test event
|
||||||
|
testEvent = <<EOS
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:19970901T130000Z-123401@host.com
|
||||||
|
DTSTAMP:19970901T1300Z
|
||||||
|
DTSTART:19970903T163000Z
|
||||||
|
DTEND:19970903T190000Z
|
||||||
|
SUMMARY:Annual Employee Review
|
||||||
|
CLASS:PRIVATE
|
||||||
|
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
||||||
|
END:VEVENT
|
||||||
|
EOS
|
||||||
|
|
||||||
|
class TestEvent < Test::Unit::TestCase
|
||||||
|
|
||||||
|
# Create a calendar with an event for each test.
|
||||||
|
def setup
|
||||||
|
@cal = Icalendar::Calendar.new
|
||||||
|
@event = Icalendar::Event.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new
|
||||||
|
assert(@event)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Properties that can only occur once per event
|
||||||
|
def test_single_properties
|
||||||
|
@event.ip_class = "PRIVATE"
|
||||||
|
|
||||||
|
@cal.add_event(@event)
|
||||||
|
|
||||||
|
cals = Icalendar::Parser.new(@cal.to_ical).parse
|
||||||
|
cal2 = cals.first
|
||||||
|
event2 = cal2.events.first
|
||||||
|
|
||||||
|
assert_equal("PRIVATE", event2.ip_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -0,0 +1,13 @@
|
|||||||
|
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||||
|
|
||||||
|
class TestTodo < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@cal = Icalendar::Calendar.new
|
||||||
|
@todo = Icalendar::Todo.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new
|
||||||
|
assert(@todo)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,66 @@
|
|||||||
|
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||||
|
|
||||||
|
require 'date'
|
||||||
|
require 'test/unit'
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
class TestComponent < Test::Unit::TestCase
|
||||||
|
|
||||||
|
# Create a calendar with an event for each test.
|
||||||
|
def setup
|
||||||
|
@cal = Icalendar::Calendar.new
|
||||||
|
@event = Icalendar::Event.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_remove_component
|
||||||
|
@cal.add_component(@event)
|
||||||
|
assert_equal(1, @cal.events.size)
|
||||||
|
@cal.remove_component(@event)
|
||||||
|
assert_equal(0, @cal.events.size)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ical_property
|
||||||
|
# No alias but it does have a prop_name
|
||||||
|
assert_equal(false, @event.ip_class?)
|
||||||
|
@event.ip_class = "PRIVATE"
|
||||||
|
assert_equal(true, @event.ip_class?)
|
||||||
|
assert_equal("PRIVATE", @event.ip_class)
|
||||||
|
|
||||||
|
# Check that both dtend and its alias start work correctly
|
||||||
|
date = DateTime.new(2005, 02, 05, 23, 24, 25)
|
||||||
|
@event.dtend = date
|
||||||
|
assert_equal(date.year, @event.dtend.year)
|
||||||
|
|
||||||
|
date2 = DateTime.new(2005, 02, 05, 23, 24, 26)
|
||||||
|
@event.end = date2
|
||||||
|
assert_equal(date2.year, @event.end.year)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ical_multi_property
|
||||||
|
# Query
|
||||||
|
assert_equal(false, @event.comments?)
|
||||||
|
@event.comments = []
|
||||||
|
assert_equal(true, @event.comments?)
|
||||||
|
|
||||||
|
# Should return an empty array, rather than nil
|
||||||
|
assert_equal(0, @event.comments.size)
|
||||||
|
|
||||||
|
# Add and remove
|
||||||
|
@event.add_comment "c1"
|
||||||
|
@event.add_comment "c2"
|
||||||
|
assert_equal(2, @event.comments.size)
|
||||||
|
assert_equal(["c1","c2"], @event.comments)
|
||||||
|
@event.remove_comment "c1"
|
||||||
|
assert_equal(["c2"], @event.comments)
|
||||||
|
|
||||||
|
# Set & get whole array
|
||||||
|
foo = ["as", "df"]
|
||||||
|
@event.comments = foo
|
||||||
|
assert_equal(foo, @event.comments)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_x_property
|
||||||
|
@event.x_foobar = "my-custom-property"
|
||||||
|
assert_equal("my-custom-property", @event.x_foobar)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,97 @@
|
|||||||
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
class TestConversions < Test::Unit::TestCase
|
||||||
|
include Icalendar
|
||||||
|
|
||||||
|
RESULT = <<EOS
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
PRODID:iCalendar-Ruby
|
||||||
|
BEGIN:VEVENT
|
||||||
|
LAST-MODIFIED:19960817T133000
|
||||||
|
SEQUENCE:2
|
||||||
|
ORGANIZER:mailto:joe@example.com?subject=Ruby
|
||||||
|
UID:foobar
|
||||||
|
X-TIME-OF-DAY:111736
|
||||||
|
CATEGORIES:foo\\,bar\\,baz
|
||||||
|
GEO:46.01\\;8.57
|
||||||
|
DESCRIPTION:desc
|
||||||
|
DTSTART:20060720
|
||||||
|
DTSTAMP:20060720T174052
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
EOS
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@cal = Calendar.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_to_ical_conversions
|
||||||
|
@cal.event do
|
||||||
|
# String
|
||||||
|
description "desc"
|
||||||
|
|
||||||
|
# Fixnum
|
||||||
|
sequence 2
|
||||||
|
|
||||||
|
# Float by way of Geo class
|
||||||
|
geo(Geo.new(46.01, 8.57))
|
||||||
|
|
||||||
|
# Array
|
||||||
|
categories ["foo", "bar"]
|
||||||
|
add_category "baz"
|
||||||
|
|
||||||
|
# Last Modified
|
||||||
|
last_modified DateTime.parse("1996-08-17T13:30:00")
|
||||||
|
|
||||||
|
# URI
|
||||||
|
organizer(URI::MailTo.build(['joe@example.com', 'subject=Ruby']))
|
||||||
|
|
||||||
|
# Date
|
||||||
|
start Date.parse("2006-07-20")
|
||||||
|
|
||||||
|
# DateTime
|
||||||
|
timestamp DateTime.parse("2006-07-20T17:40:52+0200")
|
||||||
|
|
||||||
|
# Time
|
||||||
|
x_time_of_day Time.at(123456)
|
||||||
|
|
||||||
|
uid "foobar"
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal(RESULT.gsub("\n", "\r\n"), @cal.to_ical)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_to_ical_folding
|
||||||
|
@cal.x_wr_calname = 'Test Long Description'
|
||||||
|
|
||||||
|
@cal.event do
|
||||||
|
url 'http://test.com/events/644'
|
||||||
|
dtend DateTime.parse('20061215T180000')
|
||||||
|
dtstart DateTime.parse('20061215T160000')
|
||||||
|
timestamp DateTime.parse('20061215T114034')
|
||||||
|
seq 1001
|
||||||
|
uid 'foobar'
|
||||||
|
summary 'DigiWorld 2006'
|
||||||
|
|
||||||
|
description "FULL DETAILS:\nhttp://test.com/events/570\n\n" +
|
||||||
|
"Cary Brothers walks the same musical ground as Pete Yorn, Nick Drake, " +
|
||||||
|
"Jeff Buckley and others; crafting emotional melodies, with strong vocals " +
|
||||||
|
"and thoughtful lyrics. Brett Dennen has "that thing." " +
|
||||||
|
"Inspired fans describe it: "lush shimmering vocals, an intricately " +
|
||||||
|
"groovin' guitar style, a lyrical beauty rare in a young songwriter," +
|
||||||
|
"" and "this soulful blend of everything that feels good." " +
|
||||||
|
"Rising up around him is music; transcending genres, genders and generations."
|
||||||
|
end
|
||||||
|
|
||||||
|
folded = File.read(File.join(File.dirname(__FILE__), 'fixtures/folding.ics')).gsub("\n", "\r\n")
|
||||||
|
assert_equal(folded, @cal.to_ical)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -0,0 +1,23 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
PRODID:iCalendar-Ruby
|
||||||
|
X-WR-CALNAME:Test Long Description
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SEQUENCE:1001
|
||||||
|
DTEND:20061215T180000
|
||||||
|
URL:http://test.com/events/644
|
||||||
|
UID:foobar
|
||||||
|
DESCRIPTION:FULL DETAILS:\nhttp://test.com/events/570\n\nCary Brothers walk
|
||||||
|
s the same musical ground as Pete Yorn\, Nick Drake\, Jeff Buckley and othe
|
||||||
|
rs\; crafting emotional melodies\, with strong vocals and thoughtful lyrics
|
||||||
|
. Brett Dennen has "\;that thing."\; Inspired fans describe it: &qu
|
||||||
|
ot\;lush shimmering vocals\, an intricately groovin'\; guitar style\, a
|
||||||
|
lyrical beauty rare in a young songwriter\,"\; and "\;this soulful
|
||||||
|
blend of everything that feels good."\; Rising up around him is music\;
|
||||||
|
transcending genres\, genders and generations.
|
||||||
|
SUMMARY:DigiWorld 2006
|
||||||
|
DTSTART:20061215T160000
|
||||||
|
DTSTAMP:20061215T114034
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@ -0,0 +1,46 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
X-WR-CALNAME:Life
|
||||||
|
PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
|
||||||
|
X-WR-RELCALID:69FB0467-7E07-4DE7-901D-B70242B3DB25
|
||||||
|
X-WR-TIMEZONE:America/Chicago
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
METHOD:PUBLISH
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:America/Chicago
|
||||||
|
LAST-MODIFIED:20050825T194346Z
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:20050403T080000
|
||||||
|
TZOFFSETTO:-0500
|
||||||
|
TZOFFSETFROM:+0000
|
||||||
|
TZNAME:CDT
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20051030T020000
|
||||||
|
TZOFFSETTO:-0600
|
||||||
|
TZOFFSETFROM:-0500
|
||||||
|
TZNAME:CST
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:bsuidfortestabc123
|
||||||
|
ORGANIZER:mailto:joebob@random.net
|
||||||
|
ATTACH:http://bush.sucks.org/impeach/him.rhtml
|
||||||
|
ATTACH:http://corporations-dominate.existence.net/why.rhtml
|
||||||
|
LAST-MODIFIED:20050825T194346Z
|
||||||
|
SUMMARY:This is a really long summary
|
||||||
|
to test the method of unfolding lines
|
||||||
|
so I'm just going to ma
|
||||||
|
ke it
|
||||||
|
a whol
|
||||||
|
e
|
||||||
|
bunch of lines.
|
||||||
|
CLASS:PRIVATE
|
||||||
|
PRIORITY:2
|
||||||
|
GEO:37.386013;-122.0829322
|
||||||
|
DTSTART;TZID=US-Mountain:20050120T170000
|
||||||
|
DTEND:20050120T184500
|
||||||
|
DTSTAMP:20050118T211523Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
|
@ -0,0 +1,119 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION
|
||||||
|
:2.0
|
||||||
|
PRODID
|
||||||
|
:-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID
|
||||||
|
:a08863de-1dd1-11b2-9445-d6248402dd19
|
||||||
|
SUMMARY
|
||||||
|
:Hybrid Embedded Systems. This is a really long summary
|
||||||
|
to test the method of unfolding lines
|
||||||
|
so I'm just going to ma
|
||||||
|
ke it
|
||||||
|
a whole
|
||||||
|
bunch of lines.
|
||||||
|
CATEGORIES
|
||||||
|
:Personal
|
||||||
|
STATUS
|
||||||
|
:CONFIRMED
|
||||||
|
CLASS
|
||||||
|
:PRIVATE
|
||||||
|
RRULE
|
||||||
|
:FREQ=WEEKLY;INTERVAL=1;BYDAY=TH
|
||||||
|
DTSTART
|
||||||
|
:20050120T170000
|
||||||
|
DTEND
|
||||||
|
:20050120T184500
|
||||||
|
DTSTAMP
|
||||||
|
:20050118T211523Z
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID
|
||||||
|
:6226e2f6-1dd2-11b2-9ad7-9b8d2d642229
|
||||||
|
SUMMARY
|
||||||
|
:Databases
|
||||||
|
LOCATION
|
||||||
|
:ECCS 1b12
|
||||||
|
CATEGORIES
|
||||||
|
:Personal
|
||||||
|
STATUS
|
||||||
|
:CONFIRMED
|
||||||
|
CLASS
|
||||||
|
:PRIVATE
|
||||||
|
X-MOZILLA-ALARM-DEFAULT-LENGTH
|
||||||
|
:30
|
||||||
|
X-MOZILLA-LASTALARMACK
|
||||||
|
:20050118T141054
|
||||||
|
RRULE
|
||||||
|
:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE
|
||||||
|
DTSTART
|
||||||
|
:20050117T173000
|
||||||
|
DTEND
|
||||||
|
:20050117T184500
|
||||||
|
DTSTAMP
|
||||||
|
:20050118T210648Z
|
||||||
|
LAST-MODIFIED
|
||||||
|
:20050118T211954Z
|
||||||
|
BEGIN:VALARM
|
||||||
|
TRIGGER
|
||||||
|
;VALUE=DURATION
|
||||||
|
:-PT30M
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID
|
||||||
|
:d8f4468a-1dd1-11b2-9e0a-bbe95143dbed
|
||||||
|
SUMMARY
|
||||||
|
:Software Radios
|
||||||
|
LOCATION
|
||||||
|
:ECCR 1b06
|
||||||
|
CATEGORIES
|
||||||
|
:Personal
|
||||||
|
STATUS
|
||||||
|
:CONFIRMED
|
||||||
|
CLASS
|
||||||
|
:PRIVATE
|
||||||
|
X-MOZILLA-ALARM-DEFAULT-LENGTH
|
||||||
|
:30
|
||||||
|
X-MOZILLA-LASTALARMACK
|
||||||
|
:20050118T141351
|
||||||
|
RRULE
|
||||||
|
:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE
|
||||||
|
DTSTART
|
||||||
|
:20050117T100000
|
||||||
|
DTEND
|
||||||
|
:20050117T111500
|
||||||
|
DTSTAMP
|
||||||
|
:20050118T211304Z
|
||||||
|
LAST-MODIFIED
|
||||||
|
:20050118T212016Z
|
||||||
|
BEGIN:VALARM
|
||||||
|
TRIGGER
|
||||||
|
;VALUE=DURATION
|
||||||
|
:-PT30M
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID
|
||||||
|
:9acd4cc8-1dd1-11b2-a65e-b276ad1df3b9
|
||||||
|
SUMMARY
|
||||||
|
:Israel & Palestine peace accord talk...
|
||||||
|
LOCATION
|
||||||
|
:Chem. 140
|
||||||
|
CATEGORIES
|
||||||
|
:Personal
|
||||||
|
STATUS
|
||||||
|
:CONFIRMED
|
||||||
|
CLASS
|
||||||
|
:PRIVATE
|
||||||
|
DTSTART
|
||||||
|
:20050118T193000
|
||||||
|
DTEND
|
||||||
|
:20050118T203000
|
||||||
|
DTSTAMP
|
||||||
|
:20050118T212943Z
|
||||||
|
LAST-MODIFIED
|
||||||
|
:20050118T213355Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@ -0,0 +1,23 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:bsprodidfortestabc123
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:bsuidfortestabc123
|
||||||
|
ORGANIZER:mailto:joebob@random.net
|
||||||
|
ATTACH:http://bush.sucks.org/impeach/him.rhtml
|
||||||
|
ATTACH:http://corporations-dominate.existence.net/why.rhtml
|
||||||
|
SUMMARY:This is a really long summary
|
||||||
|
to test the method of unfolding lines\,
|
||||||
|
so I'm just going to ma
|
||||||
|
ke it
|
||||||
|
a whol
|
||||||
|
e
|
||||||
|
bunch of lines.
|
||||||
|
CLASS:PRIVATE
|
||||||
|
PRIORITY:2
|
||||||
|
GEO:37.386013;-122.0829322
|
||||||
|
DTSTART;TZID=US-Mountain:20050120T170000
|
||||||
|
DTEND:20050120T184500
|
||||||
|
DTSTAMP:20050118T211523Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# NOTE: you must have installed ruby-breakpoint in order to use this script.
|
||||||
|
# Grab it using gem with "gem install ruby-breakpoint --remote" or download
|
||||||
|
# from the website (http://ruby-breakpoint.rubyforge.org/) then run setup.rb
|
||||||
|
|
||||||
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
|
require 'breakpoint'
|
||||||
|
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
cal = Icalendar::Parser.new(File.new("life.ics")).parse
|
||||||
|
#cal = Icalendar::Calendar.new
|
||||||
|
|
||||||
|
breakpoint
|
@ -0,0 +1,29 @@
|
|||||||
|
# Test out property parameter functionality
|
||||||
|
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||||
|
|
||||||
|
require 'date'
|
||||||
|
require 'test/unit'
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
class TestComponent < Test::Unit::TestCase
|
||||||
|
|
||||||
|
# Create a calendar with an event for each test.
|
||||||
|
def setup
|
||||||
|
@cal = Icalendar::Calendar.new
|
||||||
|
@event = Icalendar::Event.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_property_parameters
|
||||||
|
params = {"ALTREP" =>['"http://my.language.net"'], "LANGUAGE" => ["SPANISH"]}
|
||||||
|
@event.summary("This is a test summary.", params)
|
||||||
|
|
||||||
|
assert_equal params, @event.summary.ical_params
|
||||||
|
|
||||||
|
@cal.add_event @event
|
||||||
|
cal_str = @cal.to_ical
|
||||||
|
|
||||||
|
cals = Icalendar::Parser.new(cal_str).parse
|
||||||
|
event = cals.first.events.first
|
||||||
|
assert_equal params, event.summary.ical_params
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,84 @@
|
|||||||
|
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
# This is a test class for the calendar parser.
|
||||||
|
class TestIcalendarParser < Test::Unit::TestCase
|
||||||
|
|
||||||
|
TEST_CAL = File.join(File.dirname(__FILE__), 'fixtures', 'single_event.ics')
|
||||||
|
|
||||||
|
# First make sure that we can run the parser and get back objects.
|
||||||
|
def test_new
|
||||||
|
# Make sure we don't take invalid object types.
|
||||||
|
assert_raise(ArgumentError) { Icalendar::Parser.new(nil) }
|
||||||
|
|
||||||
|
# Make sure we get an object back from parsing a file
|
||||||
|
calFile = File.open(TEST_CAL)
|
||||||
|
cals = Icalendar::Parser.new(calFile).parse
|
||||||
|
assert(cals)
|
||||||
|
calFile.close
|
||||||
|
|
||||||
|
# Make sure we get an object back from parsing a string
|
||||||
|
calString = File.open(TEST_CAL).read
|
||||||
|
cals = Icalendar::Parser.new(calString).parse
|
||||||
|
assert(cals)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now go through and make sure the object is correct using the
|
||||||
|
# dynamically generated raw interfaces.
|
||||||
|
def test_zzfile_parse
|
||||||
|
calFile = File.open(TEST_CAL)
|
||||||
|
cals = Icalendar.parse(calFile)
|
||||||
|
calFile.close
|
||||||
|
do_asserts(cals)
|
||||||
|
|
||||||
|
Icalendar::Base.quiet
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_string_parse
|
||||||
|
# Make sure we get an object back from parsing a string
|
||||||
|
calString = File.open(TEST_CAL).read
|
||||||
|
cals = Icalendar::Parser.new(calString).parse
|
||||||
|
do_asserts(cals)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Just a helper method so we don't have to repeat the same tests.
|
||||||
|
def do_asserts(cals)
|
||||||
|
# Should just get one calendar back.
|
||||||
|
assert_equal(1, cals.size)
|
||||||
|
|
||||||
|
cal = cals.first
|
||||||
|
|
||||||
|
# Calendar properties
|
||||||
|
assert_equal("2.0", cal.version)
|
||||||
|
assert_equal("bsprodidfortestabc123", cal.prodid)
|
||||||
|
|
||||||
|
# Now the event
|
||||||
|
assert_equal(1, cal.events.size)
|
||||||
|
|
||||||
|
event = cal.events.first
|
||||||
|
assert_equal("bsuidfortestabc123", event.uid)
|
||||||
|
|
||||||
|
summary = "This is a really long summary to test the method of unfolding lines, so I'm just going to make it a whole bunch of lines."
|
||||||
|
|
||||||
|
assert_equal(summary, event.summary)
|
||||||
|
|
||||||
|
start = DateTime.parse("20050120T170000")
|
||||||
|
daend = DateTime.parse("20050120T184500")
|
||||||
|
stamp = DateTime.parse("20050118T211523Z")
|
||||||
|
assert_equal(start, event.dtstart)
|
||||||
|
assert_equal(daend, event.dtend)
|
||||||
|
assert_equal(stamp, event.dtstamp)
|
||||||
|
|
||||||
|
organizer = URI.parse("mailto:joebob@random.net")
|
||||||
|
assert_equal(organizer, event.organizer)
|
||||||
|
|
||||||
|
ats = event.attachments
|
||||||
|
assert_equal(2, ats.size)
|
||||||
|
attachment = URI.parse("http://bush.sucks.org/impeach/him.rhtml")
|
||||||
|
assert_equal(attachment, ats[0])
|
||||||
|
attachment = URI.parse("http://corporations-dominate.existence.net/why.rhtml")
|
||||||
|
assert_equal(attachment, ats[1])
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/ruby
|
||||||
|
|
||||||
|
# NOTE: you must have installed ruby-breakpoint in order to use this script.
|
||||||
|
# Grab it using gem with "gem install ruby-breakpoint --remote" or download
|
||||||
|
# from the website (http://ruby-breakpoint.rubyforge.org/) then run setup.rb
|
||||||
|
|
||||||
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||||
|
|
||||||
|
require 'breakpoint'
|
||||||
|
require 'icalendar'
|
||||||
|
|
||||||
|
cals = Icalendar::Parser.new(File.new(ARGV[0])).parse
|
||||||
|
puts "Parsed #{cals.size} calendars"
|
||||||
|
|
||||||
|
cal = cals.first
|
||||||
|
puts "First calendar has:"
|
||||||
|
puts "#{cal.events.size} events"
|
||||||
|
puts "#{cal.todos.size} todos"
|
||||||
|
puts "#{cal.journals.size} journals"
|
||||||
|
|
||||||
|
test = File.new("rw.ics", "w")
|
||||||
|
test.write(cal.to_ical)
|
||||||
|
test.close
|
Reference in new issue