icalendar 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +125 -0
- data/Rakefile +3 -3
- data/docs/api/STUB +0 -0
- data/lib/icalendar.rb +2 -0
- data/lib/icalendar/calendar.rb +10 -1
- data/lib/icalendar/component.rb +12 -8
- data/lib/icalendar/component/alarm.rb +1 -1
- data/lib/icalendar/component/event.rb +7 -1
- data/lib/icalendar/component/timezone.rb +21 -3
- data/lib/icalendar/conversions.rb +13 -2
- data/lib/icalendar/parser.rb +118 -3
- data/lib/icalendar/tzinfo.rb +121 -0
- data/test/component/event_test.rb +212 -0
- data/test/component/timezone_test.rb +67 -0
- data/test/conversions_test.rb +2 -2
- data/test/coverage/STUB +0 -0
- metadata +71 -60
data/README
CHANGED
@@ -75,6 +75,131 @@ Now for some examples:
|
|
75
75
|
# network port, database etc.
|
76
76
|
cal_string = cal.to_ical
|
77
77
|
puts cal_string
|
78
|
+
|
79
|
+
## ALARMS
|
80
|
+
|
81
|
+
## Within an event, you can create e-mail notification alarms like this...
|
82
|
+
|
83
|
+
cal.event.do
|
84
|
+
# ...other event properties
|
85
|
+
alarm do
|
86
|
+
action "EMAIL"
|
87
|
+
description "This is an event reminder" # email body (required)
|
88
|
+
summary "Alarm notification" # email subject (required)
|
89
|
+
attendees %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required)
|
90
|
+
add_attendee "mailto:me-three@my-domain.com"
|
91
|
+
remove_attendee "mailto:me@my-domain.com"
|
92
|
+
trigger "-PT15M" # 15 minutes before
|
93
|
+
add_attach "ftp://host.com/novo-procs/felizano.exe", {"FMTTYPE" => "application/binary"} # email attachments (optional)
|
94
|
+
end
|
95
|
+
|
96
|
+
alarm do
|
97
|
+
action "DISPLAY" # This line isn't necessary, it's the default
|
98
|
+
summary "Alarm notification"
|
99
|
+
trigger "-P1DT0H0M0S" # 1 day before
|
100
|
+
end
|
101
|
+
|
102
|
+
alarm do
|
103
|
+
action "AUDIO"
|
104
|
+
trigger "-PT15M"
|
105
|
+
add_attach "Basso", {"VALUE" => ["URI"]} # only one attach allowed (optional)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Output
|
110
|
+
|
111
|
+
# BEGIN:VALARM
|
112
|
+
# ACTION:EMAIL
|
113
|
+
# ATTACH;FMTTYPE=application/binary:ftp://host.com/novo-procs/felizano.exe
|
114
|
+
# TRIGGER:-PT15M
|
115
|
+
# SUMMARY:Alarm notification
|
116
|
+
# DESCRIPTION:This is an event reminder
|
117
|
+
# ATTENDEE:mailto:me-too@my-domain.com
|
118
|
+
# ATTENDEE:mailto:me-three@my-domain.com
|
119
|
+
# END:VALARM
|
120
|
+
#
|
121
|
+
# BEGIN:VALARM
|
122
|
+
# ACTION:DISPLAY
|
123
|
+
# TRIGGER:-P1DT0H0M0S
|
124
|
+
# SUMMARY:Alarm notification
|
125
|
+
# END:VALARM
|
126
|
+
#
|
127
|
+
# BEGIN:VALARM
|
128
|
+
# ACTION:AUDIO
|
129
|
+
# ATTACH;VALUE=URI:Basso
|
130
|
+
# TRIGGER:-PT15M
|
131
|
+
# END:VALARM
|
132
|
+
|
133
|
+
## Timezones
|
134
|
+
|
135
|
+
# Create a timezone definition (previous convention)
|
136
|
+
cal = Calendar.new
|
137
|
+
timezone = Icalendar::Timezone.new
|
138
|
+
daylight = Icalendar::Daylight.new
|
139
|
+
standard = Icalendar::Standard.new
|
140
|
+
|
141
|
+
timezone.timezone_id = "America/Chicago"
|
142
|
+
|
143
|
+
daylight.timezone_offset_from = "-0600"
|
144
|
+
daylight.timezone_offset_to = "-0500"
|
145
|
+
daylight.timezone_name = "CDT"
|
146
|
+
daylight.dtstart = "19700308TO20000"
|
147
|
+
daylight.recurrence_rules = ["FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"]
|
148
|
+
|
149
|
+
standard.timezone_offset_from = "-0500"
|
150
|
+
standard.timezone_offset_to = "-0600"
|
151
|
+
standard.timezone_name = "CST"
|
152
|
+
standard.dtstart = "19701101T020000"
|
153
|
+
standard.recurrence_rules = ["YEARLY;BYMONTH=11;BYDAY=1SU"]
|
154
|
+
|
155
|
+
timezone.add(daylight)
|
156
|
+
timezone.add(standard)
|
157
|
+
cal.add(timezone)
|
158
|
+
|
159
|
+
# Now, you can make timezones like this
|
160
|
+
cal = Calendar.new
|
161
|
+
cal.timezone do
|
162
|
+
timezone_id "America/Chicago"
|
163
|
+
|
164
|
+
daylight do
|
165
|
+
timezone_offset_from "-0600"
|
166
|
+
timezone_offset_to "-0500"
|
167
|
+
timezone_name "CDT"
|
168
|
+
dtstart "19700308TO20000"
|
169
|
+
add_recurrence_rule "FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"
|
170
|
+
end
|
171
|
+
|
172
|
+
standard do
|
173
|
+
timezone_offset_from "-0500"
|
174
|
+
timezone_offset_to "-0600"
|
175
|
+
timezone_name "CST"
|
176
|
+
dtstart "19701101T020000"
|
177
|
+
add_recurrence_rule "YEARLY;BYMONTH=11;BYDAY=1SU"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Both conventions output
|
182
|
+
|
183
|
+
# BEGIN:VTIMEZONE
|
184
|
+
# TZID:America/Chicago
|
185
|
+
# BEGIN:DAYLIGHT
|
186
|
+
# TZOFFSETFROM:-0600
|
187
|
+
# TZOFFSETTO:-0500
|
188
|
+
# TZNAME:CDT
|
189
|
+
# DTSTART:19700308T020000
|
190
|
+
# RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
|
191
|
+
# END:DAYLIGHT
|
192
|
+
# BEGIN:STANDARD
|
193
|
+
# TZOFFSETFROM:-0500
|
194
|
+
# TZOFFSETTO:-0600
|
195
|
+
# TZNAME:CST
|
196
|
+
# DTSTART:19701101T020000
|
197
|
+
# RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
|
198
|
+
# END:STANDARD
|
199
|
+
# END:VTIMEZONE
|
200
|
+
|
201
|
+
== Unicode
|
202
|
+
Add `$KCODE = 'u'` to make icalender work correctly with Utf8 texts
|
78
203
|
|
79
204
|
== Parsing iCalendars:
|
80
205
|
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake/rdoctask'
|
|
5
5
|
require 'rake/clean'
|
6
6
|
require 'rake/contrib/sshpublisher'
|
7
7
|
|
8
|
-
PKG_VERSION = "1.0
|
8
|
+
PKG_VERSION = "1.1.0"
|
9
9
|
|
10
10
|
$VERBOSE = nil
|
11
11
|
TEST_CHANGES_SINCE = Time.now - 600 # Recent tests = changed in last 10 minutes
|
@@ -74,8 +74,8 @@ spec = Gem::Specification.new do |s|
|
|
74
74
|
s.extra_rdoc_files = ["README", "COPYING", "GPL"]
|
75
75
|
s.rdoc_options.concat ['--main', 'README']
|
76
76
|
|
77
|
-
s.author = "
|
78
|
-
s.email = "
|
77
|
+
s.author = "Sean Dague"
|
78
|
+
s.email = "sean@dague.net"
|
79
79
|
end
|
80
80
|
|
81
81
|
Rake::GemPackageTask.new(spec) do |pkg|
|
data/docs/api/STUB
ADDED
File without changes
|
data/lib/icalendar.rb
CHANGED
data/lib/icalendar/calendar.rb
CHANGED
@@ -27,9 +27,18 @@ module Icalendar
|
|
27
27
|
|
28
28
|
def event(&block)
|
29
29
|
e = Event.new
|
30
|
+
# Note: I'm not sure this is the best way to pass this down, but it works
|
31
|
+
e.tzid = self.timezones[0].tzid if self.timezones.length > 0
|
32
|
+
|
30
33
|
self.add_component e
|
31
34
|
|
32
|
-
|
35
|
+
if block
|
36
|
+
e.instance_eval &block
|
37
|
+
if e.tzid
|
38
|
+
e.dtstart.ical_params = { "TZID" => e.tzid }
|
39
|
+
e.dtend.ical_params = { "TZID" => e.tzid }
|
40
|
+
end
|
41
|
+
end
|
33
42
|
|
34
43
|
e
|
35
44
|
end
|
data/lib/icalendar/component.rb
CHANGED
@@ -145,25 +145,29 @@ module Icalendar
|
|
145
145
|
|
146
146
|
# Property value
|
147
147
|
value = ":#{val.to_ical}"
|
148
|
-
|
149
|
-
s << escaped.slice!(0, MAX_LINE_LENGTH) << "\r\n " while escaped.size > MAX_LINE_LENGTH
|
150
|
-
s << escaped << "\r\n"
|
151
|
-
s.gsub!(/ *$/, '')
|
148
|
+
add_sliced_text(s,prelude+escape_chars(value))
|
152
149
|
else
|
153
150
|
prelude = "#{key.gsub(/_/, '-').upcase}"
|
154
151
|
val.each do |v|
|
155
152
|
params = print_parameters(v)
|
156
153
|
value = ":#{v.to_ical}"
|
157
|
-
|
158
|
-
s << escaped.slice!(0, MAX_LINE_LENGTH) << "\r\n " while escaped.size > MAX_LINE_LENGTH
|
159
|
-
s << escaped << "\r\n"
|
160
|
-
s.gsub!(/ *$/, '')
|
154
|
+
add_sliced_text(s,prelude + params + escape_chars(value))
|
161
155
|
end
|
162
156
|
end
|
163
157
|
end
|
164
158
|
s
|
165
159
|
end
|
166
160
|
|
161
|
+
def escape_chars(value)
|
162
|
+
value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
|
163
|
+
end
|
164
|
+
|
165
|
+
def add_sliced_text(add_to,escaped)
|
166
|
+
escaped = escaped.split('') # split is unicdoe-aware when `$KCODE = 'u'`
|
167
|
+
add_to << escaped.slice!(0,MAX_LINE_LENGTH).to_s << "\r\n " while escaped.length != 0 # shift(MAX_LINE_LENGTH) does not work with ruby 1.8.6
|
168
|
+
add_to.gsub!(/ *$/, '')
|
169
|
+
end
|
170
|
+
|
167
171
|
# Print the parameters for a specific property
|
168
172
|
def print_parameters(val)
|
169
173
|
s = ""
|
@@ -31,7 +31,7 @@ module Icalendar
|
|
31
31
|
|
32
32
|
# Multi properties
|
33
33
|
ical_multiline_property :attendee, :attendee, :attendees
|
34
|
-
|
34
|
+
ical_multiline_property :attach, :attachment, :attachments
|
35
35
|
|
36
36
|
def initialize()
|
37
37
|
super("VALARM")
|
@@ -27,6 +27,9 @@ module Icalendar
|
|
27
27
|
# Complete description of the calendar component
|
28
28
|
ical_property :description
|
29
29
|
|
30
|
+
# Specifies the timezone for the event
|
31
|
+
attr_accessor :tzid
|
32
|
+
|
30
33
|
# Specifies date-time when calendar component begins
|
31
34
|
ical_property :dtstart, :start
|
32
35
|
|
@@ -117,7 +120,10 @@ module Icalendar
|
|
117
120
|
|
118
121
|
a
|
119
122
|
end
|
120
|
-
|
123
|
+
|
124
|
+
def occurrences_starting(time)
|
125
|
+
recurrence_rules.first.occurrences_of_event_starting(self, time)
|
126
|
+
end
|
121
127
|
|
122
128
|
end
|
123
129
|
end
|
@@ -50,19 +50,37 @@ module Icalendar
|
|
50
50
|
# of components.
|
51
51
|
def to_ical
|
52
52
|
print_component do
|
53
|
-
|
53
|
+
s = ""
|
54
54
|
@components.each_value do |comp|
|
55
55
|
s << comp.to_ical
|
56
56
|
end
|
57
|
-
|
57
|
+
s
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
|
62
62
|
def initialize(name = "VTIMEZONE")
|
63
63
|
super(name)
|
64
64
|
end
|
65
|
+
|
66
|
+
# Allow block syntax for declaration of standard and daylight components of timezone
|
67
|
+
def standard(&block)
|
68
|
+
e = Standard.new
|
69
|
+
self.add_component e
|
65
70
|
|
71
|
+
e.instance_eval &block if block
|
72
|
+
|
73
|
+
e
|
74
|
+
end
|
75
|
+
|
76
|
+
def daylight(&block)
|
77
|
+
e = Daylight.new
|
78
|
+
self.add_component e
|
79
|
+
|
80
|
+
e.instance_eval &block if block
|
81
|
+
|
82
|
+
e
|
83
|
+
end
|
66
84
|
end
|
67
85
|
|
68
86
|
# A Standard component is a sub-component of the Timezone component which
|
@@ -16,6 +16,12 @@ require 'date'
|
|
16
16
|
# end
|
17
17
|
# end
|
18
18
|
|
19
|
+
module Icalendar
|
20
|
+
module TzidSupport
|
21
|
+
attr_accessor :icalendar_tzid
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
19
25
|
require 'uri/generic'
|
20
26
|
|
21
27
|
class String
|
@@ -53,7 +59,10 @@ module URI
|
|
53
59
|
end
|
54
60
|
|
55
61
|
class DateTime < Date
|
56
|
-
|
62
|
+
attr_accessor :ical_params
|
63
|
+
include Icalendar::TzidSupport
|
64
|
+
|
65
|
+
def to_ical
|
57
66
|
s = ""
|
58
67
|
|
59
68
|
# 4 digit year
|
@@ -82,7 +91,7 @@ class DateTime < Date
|
|
82
91
|
s << self.sec.to_s
|
83
92
|
|
84
93
|
# UTC time gets a Z suffix
|
85
|
-
if
|
94
|
+
if icalendar_tzid == "UTC"
|
86
95
|
s << "Z"
|
87
96
|
end
|
88
97
|
|
@@ -91,6 +100,7 @@ class DateTime < Date
|
|
91
100
|
end
|
92
101
|
|
93
102
|
class Date
|
103
|
+
attr_accessor :ical_params
|
94
104
|
def to_ical(utc = false)
|
95
105
|
s = ""
|
96
106
|
|
@@ -108,6 +118,7 @@ class Date
|
|
108
118
|
end
|
109
119
|
|
110
120
|
class Time
|
121
|
+
attr_accessor :ical_params
|
111
122
|
def to_ical(utc = false)
|
112
123
|
s = ""
|
113
124
|
|
data/lib/icalendar/parser.rb
CHANGED
@@ -12,6 +12,101 @@ require 'uri'
|
|
12
12
|
require 'stringio'
|
13
13
|
|
14
14
|
module Icalendar
|
15
|
+
class RRule
|
16
|
+
|
17
|
+
class Weekday
|
18
|
+
def initialize(day, position)
|
19
|
+
@day, @position = day, position
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"#{@position}#{@day}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(name, params, value, parser)
|
28
|
+
frequency_match = value.match(/FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/)
|
29
|
+
raise Icalendar::InvalidPropertyValue.new("FREQ must be specified for RRULE values") unless frequency_match
|
30
|
+
@frequency = frequency_match[1]
|
31
|
+
@until = parse_date_val("UNTIL", value)
|
32
|
+
@count = parse_int_val("COUNT", value)
|
33
|
+
raise Icalendar::InvalidPropertyValue.new("UNTIL and COUNT must not both be specified for RRULE values") if [@until, @count].compact.length > 1
|
34
|
+
@interval = parse_int_val("INTERVAL", value)
|
35
|
+
@by_list = {:bysecond => parse_int_list("BYSECOND", value)}
|
36
|
+
@by_list[:byminute] = parse_int_list("BYMINUTE",value)
|
37
|
+
@by_list[:byhour] = parse_int_list("BYHOUR", value)
|
38
|
+
@by_list[:byday] = parse_weekday_list("BYDAY", value)
|
39
|
+
@by_list[:bymonthday] = parse_int_list("BYMONTHDAY", value)
|
40
|
+
@by_list[:byyearday] = parse_int_list("BYYEARDAY", value)
|
41
|
+
@by_list[:byweekno] = parse_int_list("BYWEEKNO", value)
|
42
|
+
@by_list[:bymonth] = parse_int_list("BYMONTH", value)
|
43
|
+
@by_list[:bysetpos] = parse_int_list("BYSETPOS", value)
|
44
|
+
@wkst = parse_wkstart(value)
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_ical
|
48
|
+
result = ["FREQ=#{@frequency}"]
|
49
|
+
result << ";UNTIL=#{@until.to_ical}" if @until
|
50
|
+
result << ";COUNT=#{@count}" if @count
|
51
|
+
result << ";INTERVAL=#{@interval}" if @interval
|
52
|
+
@by_list.each do |key, value|
|
53
|
+
result << ";#{key.to_s.upcase}=#{value}" if value
|
54
|
+
end
|
55
|
+
result << ";WKST=#{@wkst}" if @wkst
|
56
|
+
result.join
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_date_val(name, string)
|
60
|
+
match = string.match(/;#{name}=(.*?)(;|$)/)
|
61
|
+
match ? DateTime.parse(match[1]) : nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_int_val(name, string)
|
65
|
+
match = string.match(/;#{name}=(\d+)(;|$)/)
|
66
|
+
match ? match[1].to_i : nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_int_list(name, string)
|
70
|
+
match = string.match(/;#{name}=([+-]?.*?)(;|$)/)
|
71
|
+
if match
|
72
|
+
match[1].split(",").map {|int| int.to_i}
|
73
|
+
else
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_weekday_list(name, string)
|
79
|
+
match = string.match(/;#{name}=(.*?)(;|$)/)
|
80
|
+
if match
|
81
|
+
match[1].split(",").map {|weekday|
|
82
|
+
wd_match = weekday.match(/([+-]?\d*)(SU|MO|TU|WE|TH|FR|SA)/)
|
83
|
+
Weekday.new(wd_match[2], wd_match[1])
|
84
|
+
}
|
85
|
+
else
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_wkstart(string)
|
91
|
+
match = string.match(/;WKSTART=(SU|MO|TU|WE|TH|FR|SA)(;|$)/)
|
92
|
+
if match
|
93
|
+
%w{SU MO TU WE TH FR SA}.index(match[1])
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# TODO: Incomplete
|
100
|
+
def occurrences_of_event_starting(event, datetime)
|
101
|
+
initial_start = event.dtstart
|
102
|
+
(0...@count).map {|day_offset|
|
103
|
+
occurrence = event.clone
|
104
|
+
occurrence.dtstart = initial_start + day_offset
|
105
|
+
occurrence.clone
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
15
110
|
def Icalendar.parse(src, single = false)
|
16
111
|
cals = Icalendar::Parser.new(src).parse
|
17
112
|
|
@@ -153,6 +248,7 @@ module Icalendar
|
|
153
248
|
|
154
249
|
# Lookup the property name to see if we have a string to
|
155
250
|
# object parser for this property type.
|
251
|
+
orig_value = value
|
156
252
|
if @parsers.has_key?(name)
|
157
253
|
value = @parsers[name].call(name, params, value)
|
158
254
|
end
|
@@ -223,7 +319,7 @@ module Icalendar
|
|
223
319
|
pname = $1
|
224
320
|
pvals = $3
|
225
321
|
|
226
|
-
# If
|
322
|
+
# If there isn't an '=' sign then we need to do some custom
|
227
323
|
# business. Defaults to 'type'
|
228
324
|
if $2 == ""
|
229
325
|
pvals = $1
|
@@ -298,6 +394,11 @@ module Icalendar
|
|
298
394
|
# GEO
|
299
395
|
m = self.method(:parse_geo)
|
300
396
|
@parsers["GEO"] = m
|
397
|
+
|
398
|
+
#RECUR
|
399
|
+
m = self.method(:parse_recur)
|
400
|
+
@parsers["RRULE"] = m
|
401
|
+
@parsers["EXRULE"] = m
|
301
402
|
|
302
403
|
end
|
303
404
|
|
@@ -317,11 +418,25 @@ module Icalendar
|
|
317
418
|
# NOTE: invalid dates & times will be returned as strings...
|
318
419
|
def parse_datetime(name, params, value)
|
319
420
|
begin
|
320
|
-
|
421
|
+
if params["VALUE"] && params["VALUE"].first == "DATE"
|
422
|
+
result = Date.parse(value)
|
423
|
+
else
|
424
|
+
result = DateTime.parse(value)
|
425
|
+
if /Z$/ =~ value
|
426
|
+
timezone = "UTC"
|
427
|
+
else
|
428
|
+
timezone = params["TZID"].first if params["TZID"]
|
429
|
+
end
|
430
|
+
result.icalendar_tzid = timezone
|
431
|
+
end
|
432
|
+
result
|
321
433
|
rescue Exception
|
322
434
|
value
|
323
435
|
end
|
324
|
-
|
436
|
+
end
|
437
|
+
|
438
|
+
def parse_recur(name, params, value)
|
439
|
+
::Icalendar::RRule.new(name, params, value, self)
|
325
440
|
end
|
326
441
|
|
327
442
|
# Durations
|
@@ -0,0 +1,121 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sean Dague
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
# The following adds a bunch of mixins to the tzinfo class, with the
|
10
|
+
# intent on making it very easy to load in tzinfo data for generating
|
11
|
+
# ical events. With this you can do the following:
|
12
|
+
#
|
13
|
+
# require "icalendar/tzinfo"
|
14
|
+
#
|
15
|
+
# estart = DateTime.new(2008, 12, 29, 8, 0, 0)
|
16
|
+
# eend = DateTime.new(2008, 12, 29, 11, 0, 0)
|
17
|
+
# tstring = "America/Chicago"
|
18
|
+
#
|
19
|
+
# tz = TZInfo::Timezone.get(tstring)
|
20
|
+
# cal = Calendar.new
|
21
|
+
# # the mixins now generate all the timezone info for the date in question
|
22
|
+
# timezone = tz.ical_timezone(estart)
|
23
|
+
# cal.add(timezone)
|
24
|
+
#
|
25
|
+
# cal.event do
|
26
|
+
# dtstart estart
|
27
|
+
# dtend eend
|
28
|
+
# summary "Meeting with the man."
|
29
|
+
# description "Have a long lunch meeting and decide nothing..."
|
30
|
+
# klass "PRIVATE"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# puts cal.to_ical
|
34
|
+
#
|
35
|
+
# The recurance rule calculations are hacky, and only start at the
|
36
|
+
# beginning of the current dst transition. I doubt this works for non
|
37
|
+
# dst areas yet. However, for a standard dst flipping zone, this
|
38
|
+
# seems to work fine (tested in Mozilla Thunderbird + Lightning).
|
39
|
+
# Future goal would be making this better.
|
40
|
+
|
41
|
+
# require "rubygems"
|
42
|
+
# require "tzinfo"
|
43
|
+
|
44
|
+
module TZInfo
|
45
|
+
class Timezone
|
46
|
+
def ical_timezone(date)
|
47
|
+
period = period_for_local(date)
|
48
|
+
timezone = Icalendar::Timezone.new
|
49
|
+
timezone.timezone_id = identifier
|
50
|
+
timezone.add(period.daylight)
|
51
|
+
timezone.add(period.standard)
|
52
|
+
return timezone
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class TimezoneTransitionInfo
|
57
|
+
def offset_from
|
58
|
+
a = previous_offset.utc_total_offset
|
59
|
+
sprintf("%2.2d%2.2d", (a / 3600).to_i, ((a / 60) % 60).to_i)
|
60
|
+
end
|
61
|
+
|
62
|
+
def offset_to
|
63
|
+
a = offset.utc_total_offset
|
64
|
+
sprintf("%2.2d%2.2d", (a / 3600).to_i, ((a / 60) % 60).to_i)
|
65
|
+
end
|
66
|
+
|
67
|
+
def rrule
|
68
|
+
start = local_start.to_datetime
|
69
|
+
# this is somewhat of a hack, but seems to work ok
|
70
|
+
[sprintf(
|
71
|
+
"FREQ=YEARLY;BYMONTH=%d;BYDAY=%d%s",
|
72
|
+
start.month,
|
73
|
+
((start.day - 1)/ 7).to_i + 1,
|
74
|
+
start.strftime("%a").upcase[0,2]
|
75
|
+
)]
|
76
|
+
end
|
77
|
+
|
78
|
+
def dtstart
|
79
|
+
local_start.to_datetime.strftime("%Y%m%dT%H%M%S")
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
class TimezonePeriod
|
85
|
+
def daylight
|
86
|
+
day = Icalendar::Daylight.new
|
87
|
+
if dst?
|
88
|
+
day.timezone_name = abbreviation.to_s
|
89
|
+
day.timezone_offset_from = start_transition.offset_from
|
90
|
+
day.timezone_offset_to = start_transition.offset_to
|
91
|
+
day.dtstart = start_transition.dtstart
|
92
|
+
day.recurrence_rules = start_transition.rrule
|
93
|
+
else
|
94
|
+
day.timezone_name = abbreviation.to_s.sub("ST","DT")
|
95
|
+
day.timezone_offset_from = end_transition.offset_from
|
96
|
+
day.timezone_offset_to = end_transition.offset_to
|
97
|
+
day.dtstart = end_transition.dtstart
|
98
|
+
day.recurrence_rules = end_transition.rrule
|
99
|
+
end
|
100
|
+
return day
|
101
|
+
end
|
102
|
+
|
103
|
+
def standard
|
104
|
+
std = Icalendar::Standard.new
|
105
|
+
if dst?
|
106
|
+
std.timezone_name = abbreviation.to_s.sub("DT","ST")
|
107
|
+
std.timezone_offset_from = end_transition.offset_from
|
108
|
+
std.timezone_offset_to = end_transition.offset_to
|
109
|
+
std.dtstart = end_transition.dtstart
|
110
|
+
std.recurrence_rules = end_transition.rrule
|
111
|
+
else
|
112
|
+
std.timezone_name = abbreviation.to_s
|
113
|
+
std.timezone_offset_from = start_transition.offset_from
|
114
|
+
std.timezone_offset_to = start_transition.offset_to
|
115
|
+
std.dtstart = start_transition.dtstart
|
116
|
+
std.recurrence_rules = start_transition.rrule
|
117
|
+
end
|
118
|
+
return std
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -3,6 +3,14 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
3
3
|
require 'test/unit'
|
4
4
|
require 'icalendar'
|
5
5
|
|
6
|
+
unless defined? 1.days
|
7
|
+
class Integer
|
8
|
+
def days
|
9
|
+
self * 60 * 60 * 24
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
6
14
|
# Define a test event
|
7
15
|
testEvent = <<EOS
|
8
16
|
BEGIN:VEVENT
|
@@ -40,5 +48,209 @@ class TestEvent < Test::Unit::TestCase
|
|
40
48
|
|
41
49
|
assert_equal("PRIVATE", event2.ip_class)
|
42
50
|
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class TestEventWithSpecifiedTimezone < Test::Unit::TestCase
|
54
|
+
|
55
|
+
def setup
|
56
|
+
src = <<EOS
|
57
|
+
BEGIN:VCALENDAR
|
58
|
+
METHOD:PUBLISH
|
59
|
+
CALSCALE:GREGORIAN
|
60
|
+
VERSION:2.0
|
61
|
+
BEGIN:VEVENT
|
62
|
+
UID:19970901T130000Z-123401@host.com
|
63
|
+
DTSTAMP:19970901T1300Z
|
64
|
+
DTSTART;TZID=America/Chicago:19970903T163000
|
65
|
+
DTEND;TZID=America/Chicago:19970903T190000
|
66
|
+
SUMMARY:Annual Employee Review
|
67
|
+
CLASS:PRIVATE
|
68
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
69
|
+
END:VEVENT
|
70
|
+
END:VCALENDAR
|
71
|
+
EOS
|
72
|
+
@calendar = Icalendar.parse(src).first
|
73
|
+
@event = @calendar.events.first
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_event_is_parsed
|
77
|
+
assert_not_nil(@event)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_dtstart_should_understand_icalendar_tzid
|
81
|
+
assert_respond_to(@event.dtstart, :icalendar_tzid)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_dtstart_tzid_should_be_correct
|
85
|
+
puts "#{@event.dtstart.icalendar_tzid} #{@event.dtstart}"
|
86
|
+
assert_equal("America/Chicago",@event.dtstart.icalendar_tzid)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_dtend_tzid_should_be_correct
|
90
|
+
assert_equal("America/Chicago",@event.dtend.icalendar_tzid)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class TestEventWithZuluTimezone < Test::Unit::TestCase
|
96
|
+
|
97
|
+
def setup
|
98
|
+
src = <<EOS
|
99
|
+
BEGIN:VCALENDAR
|
100
|
+
METHOD:PUBLISH
|
101
|
+
CALSCALE:GREGORIAN
|
102
|
+
VERSION:2.0
|
103
|
+
BEGIN:VEVENT
|
104
|
+
UID:19970901T130000Z-123401@host.com
|
105
|
+
DTSTAMP:19970901T1300Z
|
106
|
+
DTSTART:19970903T163000Z
|
107
|
+
DTEND:19970903T190000Z
|
108
|
+
SUMMARY:Annual Employee Review
|
109
|
+
CLASS:PRIVATE
|
110
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
111
|
+
END:VEVENT
|
112
|
+
END:VCALENDAR
|
113
|
+
EOS
|
114
|
+
@calendar = Icalendar.parse(src).first
|
115
|
+
@event = @calendar.events.first
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_event_is_parsed
|
119
|
+
assert_not_nil(@event)
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_dtstart_tzid_should_be_correct
|
123
|
+
puts "#{@event.dtstart.icalendar_tzid} #{@event.dtstart}"
|
124
|
+
assert_equal("UTC",@event.dtstart.icalendar_tzid)
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_dtend_tzid_should_be_correct
|
128
|
+
assert_equal("UTC",@event.dtend.icalendar_tzid)
|
129
|
+
end
|
43
130
|
|
44
131
|
end
|
132
|
+
|
133
|
+
class TestEventWithFloatingTimezone < Test::Unit::TestCase
|
134
|
+
|
135
|
+
def setup
|
136
|
+
src = <<EOS
|
137
|
+
BEGIN:VCALENDAR
|
138
|
+
METHOD:PUBLISH
|
139
|
+
CALSCALE:GREGORIAN
|
140
|
+
VERSION:2.0
|
141
|
+
BEGIN:VEVENT
|
142
|
+
UID:19970901T130000Z-123401@host.com
|
143
|
+
DTSTAMP:19970901T1300Z
|
144
|
+
DTSTART:19970903T163000
|
145
|
+
DTEND:19970903T190000
|
146
|
+
SUMMARY:Annual Employee Review
|
147
|
+
CLASS:PRIVATE
|
148
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
149
|
+
END:VEVENT
|
150
|
+
END:VCALENDAR
|
151
|
+
EOS
|
152
|
+
@calendar = Icalendar.parse(src).first
|
153
|
+
@event = @calendar.events.first
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_event_is_parsed
|
157
|
+
assert_not_nil(@event)
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_dtstart_tzid_should_be_nil
|
161
|
+
puts "#{@event.dtstart.icalendar_tzid.inspect} #{@event.dtstart}"
|
162
|
+
assert_nil(@event.dtstart.icalendar_tzid)
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_dtend_tzid_should_be_nil
|
166
|
+
assert_nil(@event.dtend.icalendar_tzid)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
class TestAllDayEventWithoutTime < Test::Unit::TestCase
|
172
|
+
|
173
|
+
def setup
|
174
|
+
src = <<EOS
|
175
|
+
BEGIN:VCALENDAR
|
176
|
+
VERSION:2.0
|
177
|
+
X-WR-CALNAME:New Event
|
178
|
+
PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
|
179
|
+
X-WR-RELCALID:3A016BE7-8932-4456-8ABD-C8F7EEC5963A
|
180
|
+
X-WR-TIMEZONE:Europe/London
|
181
|
+
CALSCALE:GREGORIAN
|
182
|
+
METHOD:PUBLISH
|
183
|
+
BEGIN:VEVENT
|
184
|
+
DTSTART;VALUE=DATE:20090110
|
185
|
+
DTEND;VALUE=DATE:20090111
|
186
|
+
SUMMARY:New Event
|
187
|
+
UID:3829F33C-F601-49AC-A3A5-C3AC4A6A3483
|
188
|
+
SEQUENCE:4
|
189
|
+
DTSTAMP:20090109T184719Z
|
190
|
+
END:VEVENT
|
191
|
+
END:VCALENDAR
|
192
|
+
EOS
|
193
|
+
@calendar = Icalendar.parse(src).first
|
194
|
+
@event = @calendar.events.first
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_event_is_parsed
|
198
|
+
assert_not_nil(@event)
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_dtstart_set_correctly
|
202
|
+
assert_equal("20090110", @event.dtstart.to_ical)
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
class TestRecurringEventWithCount < Test::Unit::TestCase
|
208
|
+
# DTSTART;TZID=US-Eastern:19970902T090000
|
209
|
+
# RRULE:FREQ=DAILY;COUNT=10
|
210
|
+
# ==> (1997 9:00 AM EDT)September 2-11
|
211
|
+
|
212
|
+
def setup
|
213
|
+
src = <<EOS
|
214
|
+
BEGIN:VCALENDAR
|
215
|
+
METHOD:PUBLISH
|
216
|
+
CALSCALE:GREGORIAN
|
217
|
+
VERSION:2.0
|
218
|
+
BEGIN:VEVENT
|
219
|
+
UID:19970901T130000Z-123401@host.com
|
220
|
+
DTSTAMP:19970901T1300Z
|
221
|
+
DTSTART:19970902T090000Z
|
222
|
+
DTEND:19970902T100000Z
|
223
|
+
RRULE:FREQ=DAILY;COUNT=10
|
224
|
+
SUMMARY:Annual Employee Review
|
225
|
+
CLASS:PRIVATE
|
226
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
227
|
+
END:VEVENT
|
228
|
+
END:VCALENDAR
|
229
|
+
EOS
|
230
|
+
@calendar = Icalendar.parse(src).first
|
231
|
+
@event = @calendar.events.first
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_event_is_parsed
|
235
|
+
assert_not_nil(@event)
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_recurrence_rules_should_return_a_recurrence_rule_array
|
239
|
+
assert_equal 1, @event.recurrence_rules.length
|
240
|
+
assert_kind_of(Icalendar::RRule, @event.recurrence_rules.first)
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_occurrences_after_with_start_before_start_at_should_return_count_occurrences
|
244
|
+
assert_equal 10, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).length
|
245
|
+
end
|
246
|
+
|
247
|
+
# def test_occurrences_after_with_start_before_start_at_should_return_an_event_with_the_dtstart_as_the_first_event
|
248
|
+
# assert_equal @event.dtstart.to_s, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).first.dtstart.to_s
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# def test_occurrences_after_with_start_before_start_at_should_return_events_with_the_correct_dtstart_values
|
252
|
+
# expected = (0..9).map {|delta| (@event.dtstart + delta).to_s}
|
253
|
+
# assert_equal expected, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).map {|occurence| occurence.dtstart.to_s}
|
254
|
+
# end
|
255
|
+
end
|
256
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'icalendar'
|
5
|
+
|
6
|
+
class TestTimezone < Test::Unit::TestCase
|
7
|
+
|
8
|
+
# Create a calendar with an event for each test.
|
9
|
+
def setup
|
10
|
+
@cal = Icalendar::Calendar.new
|
11
|
+
# Define a test timezone
|
12
|
+
@testTimezone = %Q(BEGIN:VTIMEZONE\r\nTZID:America/Chicago\r\nBEGIN:STANDARD\r\nTZOFFSETTO:-0600\r\nRRULE:FREQ=YEARLY\\;BYMONTH=11\\;BYDAY=1SU\r\nTZOFFSETFROM:-0500\r\nDTSTART:19701101T020000\r\nTZNAME:CST\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:-0500\r\nRRULE:FREQ=YEARLY\\;BYMONTH=3\\;BYDAY=2SU\r\nTZOFFSETFROM:-0600\r\nDTSTART:19700308TO20000\r\nTZNAME:CDT\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\n)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_new
|
16
|
+
@tz = Icalendar::Timezone.new
|
17
|
+
assert(@tz)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_raw_generation
|
21
|
+
timezone = Icalendar::Timezone.new
|
22
|
+
daylight = Icalendar::Daylight.new
|
23
|
+
standard = Icalendar::Standard.new
|
24
|
+
|
25
|
+
timezone.timezone_id = "America/Chicago"
|
26
|
+
|
27
|
+
daylight.timezone_offset_from = "-0600"
|
28
|
+
daylight.timezone_offset_to = "-0500"
|
29
|
+
daylight.timezone_name = "CDT"
|
30
|
+
daylight.dtstart = "19700308TO20000"
|
31
|
+
daylight.recurrence_rules = ["FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"]
|
32
|
+
|
33
|
+
standard.timezone_offset_from = "-0500"
|
34
|
+
standard.timezone_offset_to = "-0600"
|
35
|
+
standard.timezone_name = "CST"
|
36
|
+
standard.dtstart = "19701101T020000"
|
37
|
+
standard.recurrence_rules = ["FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"]
|
38
|
+
|
39
|
+
timezone.add(standard)
|
40
|
+
timezone.add(daylight)
|
41
|
+
@cal.add(timezone)
|
42
|
+
assert_equal(@testTimezone, @cal.timezones.first.to_ical)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_block_creation
|
46
|
+
@cal.timezone do
|
47
|
+
timezone_id "America/Chicago"
|
48
|
+
|
49
|
+
daylight do
|
50
|
+
timezone_offset_from "-0600"
|
51
|
+
timezone_offset_to "-0500"
|
52
|
+
timezone_name "CDT"
|
53
|
+
dtstart "19700308TO20000"
|
54
|
+
add_recurrence_rule "FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"
|
55
|
+
end
|
56
|
+
|
57
|
+
standard do
|
58
|
+
timezone_offset_from "-0500"
|
59
|
+
timezone_offset_to "-0600"
|
60
|
+
timezone_name "CST"
|
61
|
+
dtstart "19701101T020000"
|
62
|
+
add_recurrence_rule "FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
assert_equal(@testTimezone, @cal.timezones.first.to_ical)
|
66
|
+
end
|
67
|
+
end
|
data/test/conversions_test.rb
CHANGED
@@ -18,7 +18,7 @@ LAST-MODIFIED:19960817T133000
|
|
18
18
|
SEQUENCE:2
|
19
19
|
ORGANIZER:mailto:joe@example.com?subject=Ruby
|
20
20
|
UID:foobar
|
21
|
-
X-TIME-OF-DAY:
|
21
|
+
X-TIME-OF-DAY:101736
|
22
22
|
CATEGORIES:foo\\,bar\\,baz
|
23
23
|
GEO:46.01\\;8.57
|
24
24
|
DESCRIPTION:desc
|
@@ -60,7 +60,7 @@ EOS
|
|
60
60
|
timestamp DateTime.parse("2006-07-20T17:40:52+0200")
|
61
61
|
|
62
62
|
# Time
|
63
|
-
x_time_of_day Time.at(123456)
|
63
|
+
x_time_of_day Time.at(123456).utc
|
64
64
|
|
65
65
|
uid "foobar"
|
66
66
|
end
|
data/test/coverage/STUB
ADDED
File without changes
|
metadata
CHANGED
@@ -1,98 +1,109 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: icalendar
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.0
|
7
|
-
date: 2007-11-25 00:00:00 +01:00
|
8
|
-
summary: A ruby implementation of the iCalendar specification (RFC-2445).
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: rosejn@gmail.com
|
12
|
-
homepage: http://icalendar.rubyforge.org/
|
13
|
-
rubyforge_project:
|
14
|
-
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.
|
15
|
-
autorequire: icalendar
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
version: 1.1.0
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
|
-
-
|
7
|
+
- Sean Dague
|
8
|
+
autorequire: icalendar
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-29 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
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.
|
17
|
+
email: sean@dague.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- COPYING
|
25
|
+
- GPL
|
31
26
|
files:
|
32
|
-
- test/
|
33
|
-
- test/component/event_test.rb
|
34
|
-
- test/component/todo_test.rb
|
27
|
+
- test/calendar_test.rb
|
35
28
|
- test/parameter_test.rb
|
36
29
|
- test/interactive.rb
|
37
|
-
- test/
|
30
|
+
- test/conversions_test.rb
|
38
31
|
- test/component_test.rb
|
32
|
+
- test/parser_test.rb
|
33
|
+
- test/read_write.rb
|
39
34
|
- test/fixtures
|
40
|
-
- test/fixtures/simplecal.ics
|
41
|
-
- test/fixtures/folding.ics
|
42
35
|
- test/fixtures/single_event.ics
|
36
|
+
- test/fixtures/folding.ics
|
37
|
+
- test/fixtures/simplecal.ics
|
43
38
|
- test/fixtures/life.ics
|
44
|
-
- test/
|
45
|
-
- test/
|
46
|
-
- test/
|
47
|
-
- test/
|
39
|
+
- test/component
|
40
|
+
- test/component/timezone_test.rb
|
41
|
+
- test/component/todo_test.rb
|
42
|
+
- test/component/event_test.rb
|
43
|
+
- test/coverage
|
44
|
+
- test/coverage/STUB
|
48
45
|
- lib/icalendar
|
46
|
+
- lib/icalendar/parameter.rb
|
47
|
+
- lib/icalendar/component.rb
|
48
|
+
- lib/icalendar/base.rb
|
49
|
+
- lib/icalendar/parser.rb
|
50
|
+
- lib/icalendar/tzinfo.rb
|
51
|
+
- lib/icalendar/calendar.rb
|
49
52
|
- lib/icalendar/component
|
50
53
|
- lib/icalendar/component/alarm.rb
|
54
|
+
- lib/icalendar/component/todo.rb
|
51
55
|
- lib/icalendar/component/event.rb
|
52
|
-
- lib/icalendar/component/freebusy.rb
|
53
56
|
- lib/icalendar/component/journal.rb
|
54
57
|
- lib/icalendar/component/timezone.rb
|
55
|
-
- lib/icalendar/component/
|
58
|
+
- lib/icalendar/component/freebusy.rb
|
56
59
|
- lib/icalendar/conversions.rb
|
57
|
-
- lib/icalendar/parameter.rb
|
58
|
-
- lib/icalendar/component.rb
|
59
60
|
- lib/icalendar/helpers.rb
|
60
|
-
- lib/icalendar/parser.rb
|
61
|
-
- lib/icalendar/calendar.rb
|
62
|
-
- lib/icalendar/base.rb
|
63
|
-
- lib/hash_attrs.rb
|
64
|
-
- lib/icalendar.rb
|
65
61
|
- lib/meta.rb
|
66
|
-
-
|
62
|
+
- lib/icalendar.rb
|
63
|
+
- lib/hash_attrs.rb
|
67
64
|
- docs/rfcs
|
65
|
+
- docs/rfcs/rfc2446.pdf
|
66
|
+
- docs/rfcs/rfc2426.pdf
|
68
67
|
- docs/rfcs/itip_notes.txt
|
68
|
+
- docs/rfcs/rfc2447.pdf
|
69
69
|
- docs/rfcs/rfc2425.pdf
|
70
|
-
- docs/rfcs/rfc2426.pdf
|
71
70
|
- docs/rfcs/rfc2445.pdf
|
72
|
-
- docs/rfcs/rfc2446.pdf
|
73
|
-
- docs/rfcs/rfc2447.pdf
|
74
71
|
- docs/rfcs/rfc3283.txt
|
72
|
+
- docs/api
|
73
|
+
- docs/api/STUB
|
75
74
|
- examples/single_event.ics
|
76
|
-
- examples/create_cal.rb
|
77
75
|
- examples/parse_cal.rb
|
76
|
+
- examples/create_cal.rb
|
78
77
|
- Rakefile
|
79
78
|
- README
|
80
79
|
- COPYING
|
81
80
|
- GPL
|
82
|
-
|
83
|
-
|
81
|
+
has_rdoc: true
|
82
|
+
homepage: http://icalendar.rubyforge.org/
|
83
|
+
post_install_message:
|
84
84
|
rdoc_options:
|
85
85
|
- --main
|
86
86
|
- README
|
87
|
-
|
88
|
-
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: "0"
|
94
|
+
version:
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: "0"
|
100
|
+
version:
|
95
101
|
requirements: []
|
96
102
|
|
97
|
-
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.2.0
|
105
|
+
signing_key:
|
106
|
+
specification_version: 2
|
107
|
+
summary: A ruby implementation of the iCalendar specification (RFC-2445).
|
108
|
+
test_files: []
|
98
109
|
|