icalendar 1.5.4 → 2.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -1
- data/.rspec +2 -0
- data/.travis.yml +1 -2
- data/History.txt +2 -7
- data/README.md +82 -107
- data/Rakefile +6 -7
- data/icalendar.gemspec +10 -9
- data/lib/icalendar.rb +17 -33
- data/lib/icalendar/alarm.rb +35 -0
- data/lib/icalendar/calendar.rb +17 -100
- data/lib/icalendar/component.rb +41 -403
- data/lib/icalendar/event.rb +51 -0
- data/lib/icalendar/freebusy.rb +27 -0
- data/lib/icalendar/has_components.rb +83 -0
- data/lib/icalendar/has_properties.rb +156 -0
- data/lib/icalendar/journal.rb +39 -0
- data/lib/icalendar/parser.rb +75 -403
- data/lib/icalendar/timezone.rb +53 -0
- data/lib/icalendar/todo.rb +52 -0
- data/lib/icalendar/tzinfo.rb +30 -30
- data/lib/icalendar/value.rb +80 -0
- data/lib/icalendar/values/array.rb +43 -0
- data/lib/icalendar/values/binary.rb +31 -0
- data/lib/icalendar/values/boolean.rb +17 -0
- data/lib/icalendar/values/cal_address.rb +8 -0
- data/lib/icalendar/values/date.rb +26 -0
- data/lib/icalendar/values/date_time.rb +34 -0
- data/lib/icalendar/values/duration.rb +48 -0
- data/lib/icalendar/values/float.rb +17 -0
- data/lib/icalendar/values/integer.rb +17 -0
- data/lib/icalendar/values/period.rb +46 -0
- data/lib/icalendar/values/recur.rb +63 -0
- data/lib/icalendar/values/text.rb +26 -0
- data/lib/icalendar/values/time.rb +34 -0
- data/lib/icalendar/values/time_with_zone.rb +31 -0
- data/lib/icalendar/values/uri.rb +19 -0
- data/lib/icalendar/values/utc_offset.rb +39 -0
- data/lib/icalendar/version.rb +5 -0
- data/spec/alarm_spec.rb +108 -0
- data/spec/calendar_spec.rb +167 -0
- data/spec/event_spec.rb +108 -0
- data/{test/fixtures/folding.ics → spec/fixtures/nondefault_values.ics} +2 -2
- data/{test → spec}/fixtures/single_event.ics +11 -14
- data/spec/fixtures/timezone.ics +35 -0
- data/spec/freebusy_spec.rb +7 -0
- data/spec/journal_spec.rb +7 -0
- data/spec/parser_spec.rb +26 -0
- data/spec/roundtrip_spec.rb +40 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/timezone_spec.rb +31 -0
- data/spec/todo_spec.rb +24 -0
- data/spec/tzinfo_spec.rb +85 -0
- data/spec/values/date_time_spec.rb +80 -0
- data/spec/values/duration_spec.rb +67 -0
- data/spec/values/period_spec.rb +47 -0
- data/spec/values/recur_spec.rb +47 -0
- data/spec/values/text_spec.rb +72 -0
- data/spec/values/utc_offset_spec.rb +41 -0
- metadata +129 -88
- data/GPL +0 -340
- data/examples/create_cal.rb +0 -45
- data/examples/parse_cal.rb +0 -20
- data/examples/single_event.ics +0 -18
- data/lib/hash_attrs.rb +0 -34
- data/lib/icalendar/base.rb +0 -47
- data/lib/icalendar/component/alarm.rb +0 -47
- data/lib/icalendar/component/event.rb +0 -131
- data/lib/icalendar/component/freebusy.rb +0 -38
- data/lib/icalendar/component/journal.rb +0 -60
- data/lib/icalendar/component/timezone.rb +0 -91
- data/lib/icalendar/component/todo.rb +0 -64
- data/lib/icalendar/conversions.rb +0 -107
- data/lib/icalendar/helpers.rb +0 -109
- data/lib/icalendar/parameter.rb +0 -33
- data/lib/icalendar/rrule.rb +0 -133
- data/lib/meta.rb +0 -32
- data/script/console +0 -10
- data/script/recur1.ics +0 -38
- data/script/tryit.rb +0 -13
- data/test/component/test_event.rb +0 -253
- data/test/component/test_timezone.rb +0 -74
- data/test/component/test_todo.rb +0 -31
- data/test/fixtures/life.ics +0 -46
- data/test/fixtures/nonstandard.ics +0 -25
- data/test/fixtures/simplecal.ics +0 -119
- data/test/interactive.rb +0 -17
- data/test/read_write.rb +0 -23
- data/test/test_calendar.rb +0 -167
- data/test/test_component.rb +0 -102
- data/test/test_conversions.rb +0 -104
- data/test/test_helper.rb +0 -7
- data/test/test_parameter.rb +0 -91
- data/test/test_parser.rb +0 -100
- data/test/test_tzinfo.rb +0 -83
- data/website/index.html +0 -70
- data/website/index.txt +0 -38
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -159
- data/website/template.html.erb +0 -50
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
|
6
|
+
class Duration < Value
|
7
|
+
|
8
|
+
def initialize(value, params = {})
|
9
|
+
super OpenStruct.new(parse_fields value), params
|
10
|
+
end
|
11
|
+
|
12
|
+
def past?
|
13
|
+
value.past
|
14
|
+
end
|
15
|
+
|
16
|
+
def value_ical
|
17
|
+
return "#{'-' if past?}P#{weeks}W" if weeks > 0
|
18
|
+
builder = []
|
19
|
+
builder << '-' if past?
|
20
|
+
builder << 'P'
|
21
|
+
builder << "#{days}D" if days > 0
|
22
|
+
builder << 'T' if time?
|
23
|
+
builder << "#{hours}H" if hours > 0
|
24
|
+
builder << "#{minutes}M" if minutes > 0
|
25
|
+
builder << "#{seconds}S" if seconds > 0
|
26
|
+
builder.join
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def time?
|
32
|
+
hours > 0 || minutes > 0 || seconds > 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_fields(value)
|
36
|
+
{
|
37
|
+
past: (value =~ /\A([+-])P/ ? $1 == '-' : false),
|
38
|
+
weeks: (value =~ /(\d+)W/ ? $1.to_i : 0),
|
39
|
+
days: (value =~ /(\d+)D/ ? $1.to_i : 0),
|
40
|
+
hours: (value =~ /(\d+)H/ ? $1.to_i : 0),
|
41
|
+
minutes: (value =~ /(\d+)M/ ? $1.to_i : 0),
|
42
|
+
seconds: (value =~ /(\d+)S/ ? $1.to_i : 0)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Icalendar
|
2
|
+
module Values
|
3
|
+
|
4
|
+
class Period < Value
|
5
|
+
|
6
|
+
def initialize(value, params = {})
|
7
|
+
parts = value.split '/'
|
8
|
+
period_start = Icalendar::Values::DateTime.new parts.first
|
9
|
+
if parts.last =~ /\A[+-]?P.+\z/
|
10
|
+
period_end = Icalendar::Values::Duration.new parts.last
|
11
|
+
else
|
12
|
+
period_end = Icalendar::Values::DateTime.new parts.last
|
13
|
+
end
|
14
|
+
super [period_start, period_end], params
|
15
|
+
end
|
16
|
+
|
17
|
+
def value_ical
|
18
|
+
value.map { |v| v.value_ical }.join '/'
|
19
|
+
end
|
20
|
+
|
21
|
+
def period_start
|
22
|
+
first
|
23
|
+
end
|
24
|
+
|
25
|
+
def period_start=(v)
|
26
|
+
value[0] = v.is_a?(Icalendar::Values::DateTime) ? v : Icalendar::Values::DateTime.new(v)
|
27
|
+
end
|
28
|
+
|
29
|
+
def explicit_end
|
30
|
+
last.is_a?(Icalendar::Values::DateTime) ? last : nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def explicit_end=(v)
|
34
|
+
value[1] = v.is_a?(Icalendar::Values::DateTime) ? v : Icalendar::Values::DateTime.new(v)
|
35
|
+
end
|
36
|
+
|
37
|
+
def duration
|
38
|
+
last.is_a?(Icalendar::Values::Duration) ? last : nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def duration=(v)
|
42
|
+
value[1] = v.is_a?(Icalendar::Values::Duration) ? v : Icalendar::Values::Duration.new(v)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
|
6
|
+
class Recur < Value
|
7
|
+
NUM_LIST = '\d{1,2}(?:,\d{1,2})*'
|
8
|
+
DAYNAME = 'SU|MO|TU|WE|TH|FR|SA'
|
9
|
+
WEEKDAY = "(?:[+-]?\\d{1,2})?(?:#{DAYNAME})"
|
10
|
+
MONTHDAY = '[+-]?\d{1,2}'
|
11
|
+
YEARDAY = '[+-]?\d{1,3}'
|
12
|
+
|
13
|
+
def initialize(value, params = {})
|
14
|
+
super OpenStruct.new(parse_fields value), params
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
return false if frequency.nil?
|
19
|
+
return false if !self.until.nil? && !count.nil?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def value_ical
|
24
|
+
builder = ["FREQ=#{frequency}"]
|
25
|
+
builder << "UNTIL=#{self.until}" unless self.until.nil?
|
26
|
+
builder << "COUNT=#{count}" unless count.nil?
|
27
|
+
builder << "INTERVAL=#{interval}" unless interval.nil?
|
28
|
+
builder << "BYSECOND=#{by_second.join ','}" unless by_second.nil?
|
29
|
+
builder << "BYMINUTE=#{by_minute.join ','}" unless by_minute.nil?
|
30
|
+
builder << "BYHOUR=#{by_hour.join ','}" unless by_hour.nil?
|
31
|
+
builder << "BYDAY=#{by_day.join ','}" unless by_day.nil?
|
32
|
+
builder << "BYMONTHDAY=#{by_month_day.join ','}" unless by_month_day.nil?
|
33
|
+
builder << "BYYEARDAY=#{by_year_day.join ','}" unless by_year_day.nil?
|
34
|
+
builder << "BYWEEKNO=#{by_week_number.join ','}" unless by_week_number.nil?
|
35
|
+
builder << "BYMONTH=#{by_month.join ','}" unless by_month.nil?
|
36
|
+
builder << "BYSETPOS=#{by_set_position.join ','}" unless by_set_position.nil?
|
37
|
+
builder << "WKST=#{week_start}" unless week_start.nil?
|
38
|
+
builder.join ';'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def parse_fields(value)
|
44
|
+
{
|
45
|
+
frequency: (value =~ /FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/i ? $1.upcase : nil),
|
46
|
+
until: (value =~ /UNTIL=([^;]*)/i ? $1 : nil),
|
47
|
+
count: (value =~ /COUNT=(\d+)/i ? $1.to_i : nil),
|
48
|
+
interval: (value =~ /INTERVAL=(\d+)/i ? $1.to_i : nil),
|
49
|
+
by_second: (value =~ /BYSECOND=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
|
50
|
+
by_minute: (value =~ /BYMINUTE=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
|
51
|
+
by_hour: (value =~ /BYHOUR=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
|
52
|
+
by_day: (value =~ /BYDAY=(#{WEEKDAY}(?:,#{WEEKDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
|
53
|
+
by_month_day: (value =~ /BYMONTHDAY=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
|
54
|
+
by_year_day: (value =~ /BYYEARDAY=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
|
55
|
+
by_week_number: (value =~ /BYWEEKNO=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
|
56
|
+
by_month: (value =~ /BYMONTH=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
|
57
|
+
by_set_position: (value =~ /BYSETPOS=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
|
58
|
+
week_start: (value =~ /WKST=(#{DAYNAME})/i ? $1.upcase : nil)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Icalendar
|
2
|
+
module Values
|
3
|
+
|
4
|
+
class Text < Value
|
5
|
+
|
6
|
+
def initialize(value, params = {})
|
7
|
+
value = value.gsub '\n', "\n"
|
8
|
+
value.gsub! '\,', ','
|
9
|
+
value.gsub! '\;', ';'
|
10
|
+
value.gsub!('\\\\') { '\\' }
|
11
|
+
super value, params
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_ical
|
15
|
+
value.dup.tap do |v|
|
16
|
+
v.gsub!('\\') { '\\\\' }
|
17
|
+
v.gsub! ';', '\;'
|
18
|
+
v.gsub! ',', '\,'
|
19
|
+
v.gsub! /\r?\n/, '\n'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'date'
|
2
|
+
require_relative 'time_with_zone'
|
3
|
+
|
4
|
+
module Icalendar
|
5
|
+
module Values
|
6
|
+
|
7
|
+
class Time < Value
|
8
|
+
include TimeWithZone
|
9
|
+
|
10
|
+
FORMAT = '%H%M%S'
|
11
|
+
|
12
|
+
def initialize(value, params = {})
|
13
|
+
if value.is_a? String
|
14
|
+
params['tzid'] = 'UTC' if value.end_with? 'Z'
|
15
|
+
super ::DateTime.strptime(value, FORMAT).to_time, params
|
16
|
+
elsif value.respond_to? :to_time
|
17
|
+
super value.to_time, params
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def value_ical
|
24
|
+
if tz_utc
|
25
|
+
"#{strftime FORMAT}Z"
|
26
|
+
else
|
27
|
+
strftime FORMAT
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_support/time'
|
3
|
+
rescue LoadError
|
4
|
+
$stderr.puts "Info: No TimeWithZone support"
|
5
|
+
end
|
6
|
+
|
7
|
+
module Icalendar
|
8
|
+
module Values
|
9
|
+
module TimeWithZone
|
10
|
+
attr_reader :tz_utc
|
11
|
+
|
12
|
+
def initialize(value, params = {})
|
13
|
+
@tz_utc = params['tzid'] == 'UTC'
|
14
|
+
|
15
|
+
if defined?(ActiveSupport) && !params['tzid'].nil?
|
16
|
+
tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid']
|
17
|
+
zone = ActiveSupport::TimeZone[tzid]
|
18
|
+
value = ActiveSupport::TimeWithZone.new nil, zone, value unless zone.nil?
|
19
|
+
super value, params
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def params_ical
|
26
|
+
ical_params.delete 'tzid' if tz_utc
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
|
6
|
+
class Uri < Value
|
7
|
+
|
8
|
+
def initialize(value, params = {})
|
9
|
+
parsed = URI.parse value rescue value
|
10
|
+
super parsed, params
|
11
|
+
end
|
12
|
+
|
13
|
+
def value_ical
|
14
|
+
value.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
|
6
|
+
class UtcOffset < Value
|
7
|
+
|
8
|
+
def initialize(value, params = {})
|
9
|
+
value = OpenStruct.new parse_fields(value)
|
10
|
+
super value, params
|
11
|
+
end
|
12
|
+
|
13
|
+
def behind?
|
14
|
+
return false if zero_offset?
|
15
|
+
value.behind
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_ical
|
19
|
+
"#{behind? ? '-' : '+'}#{'%02d' % hours}#{'%02d' % minutes}#{'%02d' % seconds if seconds > 0}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def zero_offset?
|
25
|
+
hours == 0 && minutes == 0 && seconds == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_fields(value)
|
29
|
+
md = /\A(?<behind>[+-])(?<hours>\d{2})(?<minutes>\d{2})(?<seconds>\d{2})?\z/.match value
|
30
|
+
{
|
31
|
+
behind: (md[:behind] == '-'),
|
32
|
+
hours: md[:hours].to_i,
|
33
|
+
minutes: md[:minutes].to_i,
|
34
|
+
seconds: md[:seconds].to_i
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/alarm_spec.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Icalendar::Alarm do
|
4
|
+
|
5
|
+
# currently no behavior in Alarm not tested other places
|
6
|
+
describe '#valid?' do
|
7
|
+
subject do
|
8
|
+
described_class.new.tap do |a|
|
9
|
+
a.action = 'AUDIO'
|
10
|
+
a.trigger = Time.now.utc.to_datetime
|
11
|
+
end
|
12
|
+
end
|
13
|
+
context 'neither duration or repeat is set' do
|
14
|
+
it { should be_valid }
|
15
|
+
end
|
16
|
+
context 'both duration and repeat are set' do
|
17
|
+
before(:each) do
|
18
|
+
subject.duration = 'PT15M'
|
19
|
+
subject.repeat = 4
|
20
|
+
end
|
21
|
+
it { should be_valid }
|
22
|
+
end
|
23
|
+
context 'only duration is set' do
|
24
|
+
before(:each) { subject.duration = 'PT15M' }
|
25
|
+
it { should_not be_valid }
|
26
|
+
end
|
27
|
+
context 'only repeat is set' do
|
28
|
+
before(:each) { subject.repeat = 4 }
|
29
|
+
it { should_not be_valid }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'display action' do
|
33
|
+
before(:each) { subject.action = 'DISPLAY' }
|
34
|
+
it 'requires description' do
|
35
|
+
subject.should_not be_valid
|
36
|
+
subject.description = 'Display Text'
|
37
|
+
subject.should be_valid
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'email action' do
|
42
|
+
before(:each) { subject.action = 'EMAIL' }
|
43
|
+
context 'requires subject and body' do
|
44
|
+
before(:each) { subject.attendee = ['mailto:test@email.com'] }
|
45
|
+
it 'requires description' do
|
46
|
+
subject.summary = 'Email subject'
|
47
|
+
subject.should_not be_valid
|
48
|
+
subject.description = 'Email Body'
|
49
|
+
subject.should be_valid
|
50
|
+
end
|
51
|
+
it 'requires summary' do
|
52
|
+
subject.description = 'Email body'
|
53
|
+
subject.should_not be_valid
|
54
|
+
subject.summary = 'Email subject'
|
55
|
+
subject.should be_valid
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context 'attendees are required' do
|
59
|
+
before(:each) do
|
60
|
+
subject.summary = 'subject'
|
61
|
+
subject.description = 'body'
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'must be present' do
|
65
|
+
subject.attendee = nil
|
66
|
+
subject.should_not be_valid
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'can be single' do
|
70
|
+
subject.attendee << 'mailto:test@email.com'
|
71
|
+
subject.should be_valid
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'can be multi' do
|
75
|
+
subject.attendee << 'mailto:test@email.com'
|
76
|
+
subject.attendee << 'mailto:email@test.com'
|
77
|
+
subject.should be_valid
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'strict validations check parent' do
|
83
|
+
subject do
|
84
|
+
described_class.new.tap do |a|
|
85
|
+
a.action = 'AUDIO'
|
86
|
+
a.trigger = Time.now.utc.to_datetime
|
87
|
+
end
|
88
|
+
end
|
89
|
+
specify { subject.valid?(true).should be_true }
|
90
|
+
context 'with parent' do
|
91
|
+
before(:each) { subject.parent = parent }
|
92
|
+
context 'event' do
|
93
|
+
let(:parent) { Icalendar::Event.new }
|
94
|
+
specify { subject.valid?(true).should be_true }
|
95
|
+
end
|
96
|
+
context 'todo' do
|
97
|
+
let(:parent) { Icalendar::Todo.new }
|
98
|
+
specify { subject.valid?(true).should be_true }
|
99
|
+
end
|
100
|
+
context 'journal' do
|
101
|
+
let(:parent) { Icalendar::Journal.new }
|
102
|
+
specify { subject.valid?(true).should be_false }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|