Gem: icalendar

master
Alinson S. Xavier 18 years ago
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,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

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

@ -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>
&nbsp;<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%">&nbsp;</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">&nbsp;</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 &quot;that thing.&quot; " +
"Inspired fans describe it: &quot;lush shimmering vocals, an intricately " +
"groovin&#39; guitar style, a lyrical beauty rare in a young songwriter," +
"&quot; and &quot;this soulful blend of everything that feels good.&quot; " +
"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 &quot\;that thing.&quot\; Inspired fans describe it: &qu
ot\;lush shimmering vocals\, an intricately groovin&#39\; guitar style\, a
lyrical beauty rare in a young songwriter\,&quot\; and &quot\;this soulful
blend of everything that feels good.&quot\; 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