icalendar 1.5.4 → 2.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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