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,53 @@
|
|
1
|
+
module Icalendar
|
2
|
+
|
3
|
+
class Timezone < Component
|
4
|
+
module TzProperties
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
required_property :dtstart, Icalendar::Values::DateTime
|
8
|
+
required_property :tzoffsetfrom, Icalendar::Values::UtcOffset
|
9
|
+
required_property :tzoffsetto, Icalendar::Values::UtcOffset
|
10
|
+
|
11
|
+
optional_property :rrule, Icalendar::Values::Recur, true
|
12
|
+
optional_property :comment
|
13
|
+
optional_property :rdate, Icalendar::Values::DateTime
|
14
|
+
optional_property :tzname
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
class Daylight < Component
|
19
|
+
include TzProperties
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super 'daylight', 'DAYLIGHT'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
class Standard < Component
|
26
|
+
include TzProperties
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
super 'standard', 'STANDARD'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
required_property :tzid
|
35
|
+
|
36
|
+
optional_single_property :last_modified, Icalendar::Values::DateTime
|
37
|
+
optional_single_property :tzurl, Icalendar::Values::Uri
|
38
|
+
|
39
|
+
component :daylight, false, Icalendar::Timezone::Daylight
|
40
|
+
component :standard, false, Icalendar::Timezone::Standard
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
super 'timezone'
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid?(strict = false)
|
47
|
+
daylights.empty? && standards.empty? and return false
|
48
|
+
daylights.all? { |d| d.valid? strict } or return false
|
49
|
+
standards.all? { |s| s.valid? strict } or return false
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Icalendar
|
2
|
+
|
3
|
+
class Todo < Component
|
4
|
+
required_property :dtstamp, Icalendar::Values::DateTime
|
5
|
+
required_property :uid
|
6
|
+
# dtstart only required if duration is specified
|
7
|
+
required_property :dtstart, Icalendar::Values::DateTime,
|
8
|
+
->(todo, dtstart) { !(!todo.duration.nil? && dtstart.nil?) }
|
9
|
+
|
10
|
+
optional_single_property :due, Icalendar::Values::DateTime
|
11
|
+
optional_single_property :duration, Icalendar::Values::Duration
|
12
|
+
mutually_exclusive_properties :due, :duration
|
13
|
+
|
14
|
+
optional_single_property :ip_class
|
15
|
+
optional_single_property :completed, Icalendar::Values::DateTime
|
16
|
+
optional_single_property :created, Icalendar::Values::DateTime
|
17
|
+
optional_single_property :description
|
18
|
+
optional_single_property :geo, Icalendar::Values::Float
|
19
|
+
optional_single_property :last_modified, Icalendar::Values::DateTime
|
20
|
+
optional_single_property :location
|
21
|
+
optional_single_property :organizer, Icalendar::Values::CalAddress
|
22
|
+
optional_single_property :percent_complete, Icalendar::Values::Integer
|
23
|
+
optional_single_property :priority, Icalendar::Values::Integer
|
24
|
+
optional_single_property :recurrence_id, Icalendar::Values::DateTime
|
25
|
+
optional_single_property :sequence, Icalendar::Values::Integer
|
26
|
+
optional_single_property :status
|
27
|
+
optional_single_property :summary
|
28
|
+
optional_single_property :url, Icalendar::Values::Uri
|
29
|
+
|
30
|
+
optional_property :rrule, Icalendar::Values::Recur, true
|
31
|
+
optional_property :attach, Icalendar::Values::Uri
|
32
|
+
optional_property :attendee, Icalendar::Values::CalAddress
|
33
|
+
optional_property :categories
|
34
|
+
optional_property :comment
|
35
|
+
optional_property :contact
|
36
|
+
optional_property :exdate, Icalendar::Values::DateTime
|
37
|
+
optional_property :request_status
|
38
|
+
optional_property :related_to
|
39
|
+
optional_property :resources
|
40
|
+
optional_property :rdate, Icalendar::Values::DateTime
|
41
|
+
|
42
|
+
component :alarm, false
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
super 'todo'
|
46
|
+
self.dtstamp = Icalendar::Values::DateTime.new Time.now.utc, 'tzid' => 'UTC'
|
47
|
+
self.uid = new_uid
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/icalendar/tzinfo.rb
CHANGED
@@ -38,22 +38,22 @@
|
|
38
38
|
# seems to work fine (tested in Mozilla Thunderbird + Lightning).
|
39
39
|
# Future goal would be making this better.
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
|
42
|
+
require 'tzinfo'
|
43
43
|
|
44
44
|
module TZInfo
|
45
45
|
class Timezone
|
46
46
|
def ical_timezone(date, dst = Timezone.default_dst)
|
47
47
|
period = period_for_local(date, dst)
|
48
48
|
timezone = Icalendar::Timezone.new
|
49
|
-
timezone.
|
49
|
+
timezone.tzid = identifier
|
50
50
|
if period.start_transition.nil?
|
51
|
-
timezone.
|
51
|
+
timezone.add_component period.single
|
52
52
|
elsif period.end_transition.nil?
|
53
|
-
timezone.
|
53
|
+
timezone.add_component period.dst? ? period.daylight : period.standard
|
54
54
|
else
|
55
|
-
timezone.
|
56
|
-
timezone.
|
55
|
+
timezone.add_component period.daylight
|
56
|
+
timezone.add_component period.standard
|
57
57
|
end
|
58
58
|
timezone
|
59
59
|
end
|
@@ -73,7 +73,7 @@ module TZInfo
|
|
73
73
|
# this is somewhat of a hack, but seems to work ok
|
74
74
|
[sprintf(
|
75
75
|
'FREQ=YEARLY;BYMONTH=%d;BYDAY=%d%s',
|
76
|
-
start.month,
|
76
|
+
start.month,
|
77
77
|
((start.day - 1)/ 7).to_i + 1,
|
78
78
|
start.strftime('%a').upcase[0,2]
|
79
79
|
)]
|
@@ -93,46 +93,46 @@ module TZInfo
|
|
93
93
|
|
94
94
|
class TimezonePeriod
|
95
95
|
def daylight
|
96
|
-
Icalendar::Daylight.new.tap do |day|
|
96
|
+
Icalendar::Timezone::Daylight.new.tap do |day|
|
97
97
|
if dst?
|
98
|
-
day.
|
99
|
-
day.
|
100
|
-
day.
|
98
|
+
day.tzname = abbreviation.to_s
|
99
|
+
day.tzoffsetfrom = start_transition.offset_from
|
100
|
+
day.tzoffsetto = start_transition.offset_to
|
101
101
|
day.dtstart = start_transition.dtstart
|
102
|
-
day.
|
102
|
+
day.rrule = start_transition.rrule unless end_transition.nil?
|
103
103
|
else
|
104
|
-
day.
|
105
|
-
day.
|
106
|
-
day.
|
104
|
+
day.tzname = abbreviation.to_s.sub("ST","DT")
|
105
|
+
day.tzoffsetfrom = end_transition.offset_from
|
106
|
+
day.tzoffsetto = end_transition.offset_to
|
107
107
|
day.dtstart = end_transition.dtstart
|
108
|
-
day.
|
108
|
+
day.rrule = end_transition.rrule
|
109
109
|
end
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
113
113
|
def standard
|
114
|
-
Icalendar::Standard.new.tap do |std|
|
114
|
+
Icalendar::Timezone::Standard.new.tap do |std|
|
115
115
|
if dst?
|
116
|
-
std.
|
117
|
-
std.
|
118
|
-
std.
|
116
|
+
std.tzname = abbreviation.to_s.sub('DT', 'ST')
|
117
|
+
std.tzoffsetfrom = end_transition.offset_from
|
118
|
+
std.tzoffsetto = end_transition.offset_to
|
119
119
|
std.dtstart = end_transition.dtstart
|
120
|
-
std.
|
120
|
+
std.rrule = end_transition.rrule
|
121
121
|
else
|
122
|
-
std.
|
123
|
-
std.
|
124
|
-
std.
|
122
|
+
std.tzname = abbreviation.to_s
|
123
|
+
std.tzoffsetfrom = start_transition.offset_from
|
124
|
+
std.tzoffsetto = start_transition.offset_to
|
125
125
|
std.dtstart = start_transition.dtstart
|
126
|
-
std.
|
126
|
+
std.rrule = start_transition.rrule unless end_transition.nil?
|
127
127
|
end
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
131
131
|
def single
|
132
|
-
Icalendar::Standard.new.tap do |std|
|
133
|
-
std.
|
134
|
-
std.
|
135
|
-
std.
|
132
|
+
Icalendar::Timezone::Standard.new.tap do |std|
|
133
|
+
std.tzname = abbreviation.to_s
|
134
|
+
std.tzoffsetfrom = offset.ical_offset
|
135
|
+
std.tzoffsetto = offset.ical_offset
|
136
136
|
std.dtstart = DateTime.new(1970).strftime '%Y%m%dT%H%M%S'
|
137
137
|
end
|
138
138
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
|
5
|
+
class Value < ::SimpleDelegator
|
6
|
+
|
7
|
+
attr_accessor :ical_params
|
8
|
+
|
9
|
+
def initialize(value, params = {})
|
10
|
+
@ical_params = params.dup
|
11
|
+
super value
|
12
|
+
end
|
13
|
+
|
14
|
+
def ical_param(key, value)
|
15
|
+
@ical_params[key.to_s] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def value
|
19
|
+
__getobj__
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_ical(default_type)
|
23
|
+
ical_param 'value', self.class.value_type if needs_value_type?(default_type)
|
24
|
+
"#{params_ical}:#{value_ical}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def params_ical
|
28
|
+
unless ical_params.empty?
|
29
|
+
";#{ical_params.map { |name, value| param_ical name, value }.join ';'}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.value_type
|
34
|
+
name.gsub(/\A.*::/, '').gsub(/(?<!\A)[A-Z]/, '-\0').upcase
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def needs_value_type?(default_type)
|
40
|
+
self.class != default_type
|
41
|
+
end
|
42
|
+
|
43
|
+
def param_ical(name, param_value)
|
44
|
+
if param_value.is_a? Array
|
45
|
+
param_value = param_value.map { |v| escape_param_value v }.join ','
|
46
|
+
else
|
47
|
+
param_value = escape_param_value param_value
|
48
|
+
end
|
49
|
+
"#{name.to_s.gsub('_', '-').upcase}=#{param_value}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def escape_param_value(value)
|
53
|
+
v = value.gsub '"', "'"
|
54
|
+
v =~ /[;:,]/ ? %("#{v}") : v
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
# helper; not actual iCalendar value type
|
62
|
+
require_relative 'values/array'
|
63
|
+
|
64
|
+
# iCalendar value types
|
65
|
+
require_relative 'values/binary'
|
66
|
+
require_relative 'values/boolean'
|
67
|
+
require_relative 'values/date'
|
68
|
+
require_relative 'values/date_time'
|
69
|
+
require_relative 'values/duration'
|
70
|
+
require_relative 'values/float'
|
71
|
+
require_relative 'values/integer'
|
72
|
+
require_relative 'values/period'
|
73
|
+
require_relative 'values/recur'
|
74
|
+
require_relative 'values/text'
|
75
|
+
require_relative 'values/time'
|
76
|
+
require_relative 'values/uri'
|
77
|
+
require_relative 'values/utc_offset'
|
78
|
+
|
79
|
+
# further refine above classes
|
80
|
+
require_relative 'values/cal_address'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Icalendar
|
2
|
+
module Values
|
3
|
+
|
4
|
+
class Array < Value
|
5
|
+
|
6
|
+
def initialize(value, klass, params = {}, options = {})
|
7
|
+
@value_delimiter = options[:delimiter] || ','
|
8
|
+
mapped = if value.is_a? ::Array
|
9
|
+
value.map { |v| klass.new v, params }
|
10
|
+
else
|
11
|
+
[klass.new(value, params)]
|
12
|
+
end
|
13
|
+
super mapped, params
|
14
|
+
end
|
15
|
+
|
16
|
+
def params_ical
|
17
|
+
value.each do |v|
|
18
|
+
ical_params.merge! v.ical_params
|
19
|
+
end
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def value_ical
|
24
|
+
value.map do |v|
|
25
|
+
v.value_ical
|
26
|
+
end.join @value_delimiter
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid?
|
30
|
+
klass = value.first.class
|
31
|
+
!value.all? { |v| v.class == klass }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def needs_value_type?(default_type)
|
37
|
+
value.first.class != default_type
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
|
6
|
+
class Binary < Value
|
7
|
+
|
8
|
+
def params_ical
|
9
|
+
ical_param :value, 'BINARY'
|
10
|
+
ical_param :encoding, 'BASE64'
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_ical
|
15
|
+
if base64?
|
16
|
+
value
|
17
|
+
else
|
18
|
+
Base64.strict_encode64 value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def base64?
|
25
|
+
value.is_a?(String) &&
|
26
|
+
value =~ /\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)\z/
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
|
6
|
+
class Date < Value
|
7
|
+
FORMAT = '%Y%m%d'
|
8
|
+
|
9
|
+
def initialize(value, params = {})
|
10
|
+
if value.is_a? String
|
11
|
+
super ::Date.strptime(value, FORMAT), params
|
12
|
+
elsif value.respond_to? :to_date
|
13
|
+
super value.to_date, params
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def value_ical
|
20
|
+
value.strftime FORMAT
|
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 DateTime < Value
|
8
|
+
include TimeWithZone
|
9
|
+
|
10
|
+
FORMAT = '%Y%m%dT%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), params
|
16
|
+
elsif value.respond_to? :to_datetime
|
17
|
+
super value.to_datetime, 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
|