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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -1
  3. data/.rspec +2 -0
  4. data/.travis.yml +1 -2
  5. data/History.txt +2 -7
  6. data/README.md +82 -107
  7. data/Rakefile +6 -7
  8. data/icalendar.gemspec +10 -9
  9. data/lib/icalendar.rb +17 -33
  10. data/lib/icalendar/alarm.rb +35 -0
  11. data/lib/icalendar/calendar.rb +17 -100
  12. data/lib/icalendar/component.rb +41 -403
  13. data/lib/icalendar/event.rb +51 -0
  14. data/lib/icalendar/freebusy.rb +27 -0
  15. data/lib/icalendar/has_components.rb +83 -0
  16. data/lib/icalendar/has_properties.rb +156 -0
  17. data/lib/icalendar/journal.rb +39 -0
  18. data/lib/icalendar/parser.rb +75 -403
  19. data/lib/icalendar/timezone.rb +53 -0
  20. data/lib/icalendar/todo.rb +52 -0
  21. data/lib/icalendar/tzinfo.rb +30 -30
  22. data/lib/icalendar/value.rb +80 -0
  23. data/lib/icalendar/values/array.rb +43 -0
  24. data/lib/icalendar/values/binary.rb +31 -0
  25. data/lib/icalendar/values/boolean.rb +17 -0
  26. data/lib/icalendar/values/cal_address.rb +8 -0
  27. data/lib/icalendar/values/date.rb +26 -0
  28. data/lib/icalendar/values/date_time.rb +34 -0
  29. data/lib/icalendar/values/duration.rb +48 -0
  30. data/lib/icalendar/values/float.rb +17 -0
  31. data/lib/icalendar/values/integer.rb +17 -0
  32. data/lib/icalendar/values/period.rb +46 -0
  33. data/lib/icalendar/values/recur.rb +63 -0
  34. data/lib/icalendar/values/text.rb +26 -0
  35. data/lib/icalendar/values/time.rb +34 -0
  36. data/lib/icalendar/values/time_with_zone.rb +31 -0
  37. data/lib/icalendar/values/uri.rb +19 -0
  38. data/lib/icalendar/values/utc_offset.rb +39 -0
  39. data/lib/icalendar/version.rb +5 -0
  40. data/spec/alarm_spec.rb +108 -0
  41. data/spec/calendar_spec.rb +167 -0
  42. data/spec/event_spec.rb +108 -0
  43. data/{test/fixtures/folding.ics → spec/fixtures/nondefault_values.ics} +2 -2
  44. data/{test → spec}/fixtures/single_event.ics +11 -14
  45. data/spec/fixtures/timezone.ics +35 -0
  46. data/spec/freebusy_spec.rb +7 -0
  47. data/spec/journal_spec.rb +7 -0
  48. data/spec/parser_spec.rb +26 -0
  49. data/spec/roundtrip_spec.rb +40 -0
  50. data/spec/spec_helper.rb +25 -0
  51. data/spec/timezone_spec.rb +31 -0
  52. data/spec/todo_spec.rb +24 -0
  53. data/spec/tzinfo_spec.rb +85 -0
  54. data/spec/values/date_time_spec.rb +80 -0
  55. data/spec/values/duration_spec.rb +67 -0
  56. data/spec/values/period_spec.rb +47 -0
  57. data/spec/values/recur_spec.rb +47 -0
  58. data/spec/values/text_spec.rb +72 -0
  59. data/spec/values/utc_offset_spec.rb +41 -0
  60. metadata +129 -88
  61. data/GPL +0 -340
  62. data/examples/create_cal.rb +0 -45
  63. data/examples/parse_cal.rb +0 -20
  64. data/examples/single_event.ics +0 -18
  65. data/lib/hash_attrs.rb +0 -34
  66. data/lib/icalendar/base.rb +0 -47
  67. data/lib/icalendar/component/alarm.rb +0 -47
  68. data/lib/icalendar/component/event.rb +0 -131
  69. data/lib/icalendar/component/freebusy.rb +0 -38
  70. data/lib/icalendar/component/journal.rb +0 -60
  71. data/lib/icalendar/component/timezone.rb +0 -91
  72. data/lib/icalendar/component/todo.rb +0 -64
  73. data/lib/icalendar/conversions.rb +0 -107
  74. data/lib/icalendar/helpers.rb +0 -109
  75. data/lib/icalendar/parameter.rb +0 -33
  76. data/lib/icalendar/rrule.rb +0 -133
  77. data/lib/meta.rb +0 -32
  78. data/script/console +0 -10
  79. data/script/recur1.ics +0 -38
  80. data/script/tryit.rb +0 -13
  81. data/test/component/test_event.rb +0 -253
  82. data/test/component/test_timezone.rb +0 -74
  83. data/test/component/test_todo.rb +0 -31
  84. data/test/fixtures/life.ics +0 -46
  85. data/test/fixtures/nonstandard.ics +0 -25
  86. data/test/fixtures/simplecal.ics +0 -119
  87. data/test/interactive.rb +0 -17
  88. data/test/read_write.rb +0 -23
  89. data/test/test_calendar.rb +0 -167
  90. data/test/test_component.rb +0 -102
  91. data/test/test_conversions.rb +0 -104
  92. data/test/test_helper.rb +0 -7
  93. data/test/test_parameter.rb +0 -91
  94. data/test/test_parser.rb +0 -100
  95. data/test/test_tzinfo.rb +0 -83
  96. data/website/index.html +0 -70
  97. data/website/index.txt +0 -38
  98. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  99. data/website/stylesheets/screen.css +0 -159
  100. 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
@@ -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
- # require "rubygems"
42
- # require "tzinfo"
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.timezone_id = identifier
49
+ timezone.tzid = identifier
50
50
  if period.start_transition.nil?
51
- timezone.add period.single
51
+ timezone.add_component period.single
52
52
  elsif period.end_transition.nil?
53
- timezone.add period.dst? ? period.daylight : period.standard
53
+ timezone.add_component period.dst? ? period.daylight : period.standard
54
54
  else
55
- timezone.add period.daylight
56
- timezone.add period.standard
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.timezone_name = abbreviation.to_s
99
- day.timezone_offset_from = start_transition.offset_from
100
- day.timezone_offset_to = start_transition.offset_to
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.recurrence_rules = start_transition.rrule unless end_transition.nil?
102
+ day.rrule = start_transition.rrule unless end_transition.nil?
103
103
  else
104
- day.timezone_name = abbreviation.to_s.sub("ST","DT")
105
- day.timezone_offset_from = end_transition.offset_from
106
- day.timezone_offset_to = end_transition.offset_to
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.recurrence_rules = end_transition.rrule
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.timezone_name = abbreviation.to_s.sub('DT', 'ST')
117
- std.timezone_offset_from = end_transition.offset_from
118
- std.timezone_offset_to = end_transition.offset_to
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.recurrence_rules = end_transition.rrule
120
+ std.rrule = end_transition.rrule
121
121
  else
122
- std.timezone_name = abbreviation.to_s
123
- std.timezone_offset_from = start_transition.offset_from
124
- std.timezone_offset_to = start_transition.offset_to
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.recurrence_rules = start_transition.rrule unless end_transition.nil?
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.timezone_name = abbreviation.to_s
134
- std.timezone_offset_from = offset.ical_offset
135
- std.timezone_offset_to = offset.ical_offset
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,17 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ class Boolean < Value
5
+
6
+ def initialize(value, params = {})
7
+ super value.to_s.downcase == 'true', params
8
+ end
9
+
10
+ def value_ical
11
+ value ? 'TRUE' : 'FALSE'
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ class CalAddress < Uri
5
+ end
6
+
7
+ end
8
+ 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