ri_cal 0.5.0

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 (130) hide show
  1. data/History.txt +45 -0
  2. data/Manifest.txt +129 -0
  3. data/README.txt +394 -0
  4. data/Rakefile +31 -0
  5. data/bin/ri_cal +8 -0
  6. data/component_attributes/alarm.yml +10 -0
  7. data/component_attributes/calendar.yml +4 -0
  8. data/component_attributes/component_property_defs.yml +180 -0
  9. data/component_attributes/event.yml +45 -0
  10. data/component_attributes/freebusy.yml +16 -0
  11. data/component_attributes/journal.yml +35 -0
  12. data/component_attributes/timezone.yml +3 -0
  13. data/component_attributes/timezone_period.yml +11 -0
  14. data/component_attributes/todo.yml +46 -0
  15. data/copyrights.txt +1 -0
  16. data/docs/draft-ietf-calsify-2446bis-08.txt +7280 -0
  17. data/docs/draft-ietf-calsify-rfc2445bis-09.txt +10416 -0
  18. data/docs/incrementers.txt +7 -0
  19. data/docs/rfc2445.pdf +0 -0
  20. data/lib/ri_cal.rb +144 -0
  21. data/lib/ri_cal/component.rb +247 -0
  22. data/lib/ri_cal/component/alarm.rb +21 -0
  23. data/lib/ri_cal/component/calendar.rb +219 -0
  24. data/lib/ri_cal/component/event.rb +60 -0
  25. data/lib/ri_cal/component/freebusy.rb +18 -0
  26. data/lib/ri_cal/component/journal.rb +30 -0
  27. data/lib/ri_cal/component/t_z_info_timezone.rb +123 -0
  28. data/lib/ri_cal/component/timezone.rb +196 -0
  29. data/lib/ri_cal/component/timezone/daylight_period.rb +25 -0
  30. data/lib/ri_cal/component/timezone/standard_period.rb +23 -0
  31. data/lib/ri_cal/component/timezone/timezone_period.rb +53 -0
  32. data/lib/ri_cal/component/todo.rb +43 -0
  33. data/lib/ri_cal/core_extensions.rb +6 -0
  34. data/lib/ri_cal/core_extensions/array.rb +7 -0
  35. data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
  36. data/lib/ri_cal/core_extensions/date.rb +13 -0
  37. data/lib/ri_cal/core_extensions/date/conversions.rb +61 -0
  38. data/lib/ri_cal/core_extensions/date_time.rb +15 -0
  39. data/lib/ri_cal/core_extensions/date_time/conversions.rb +50 -0
  40. data/lib/ri_cal/core_extensions/object.rb +8 -0
  41. data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
  42. data/lib/ri_cal/core_extensions/string.rb +8 -0
  43. data/lib/ri_cal/core_extensions/string/conversions.rb +63 -0
  44. data/lib/ri_cal/core_extensions/time.rb +13 -0
  45. data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
  46. data/lib/ri_cal/core_extensions/time/conversions.rb +61 -0
  47. data/lib/ri_cal/core_extensions/time/tzid_access.rb +50 -0
  48. data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +88 -0
  49. data/lib/ri_cal/floating_timezone.rb +32 -0
  50. data/lib/ri_cal/invalid_property_value.rb +8 -0
  51. data/lib/ri_cal/invalid_timezone_identifer.rb +20 -0
  52. data/lib/ri_cal/occurrence_enumerator.rb +206 -0
  53. data/lib/ri_cal/occurrence_period.rb +17 -0
  54. data/lib/ri_cal/parser.rb +138 -0
  55. data/lib/ri_cal/properties/alarm.rb +390 -0
  56. data/lib/ri_cal/properties/calendar.rb +164 -0
  57. data/lib/ri_cal/properties/event.rb +1526 -0
  58. data/lib/ri_cal/properties/freebusy.rb +594 -0
  59. data/lib/ri_cal/properties/journal.rb +1240 -0
  60. data/lib/ri_cal/properties/timezone.rb +151 -0
  61. data/lib/ri_cal/properties/timezone_period.rb +416 -0
  62. data/lib/ri_cal/properties/todo.rb +1562 -0
  63. data/lib/ri_cal/property_value.rb +149 -0
  64. data/lib/ri_cal/property_value/array.rb +27 -0
  65. data/lib/ri_cal/property_value/cal_address.rb +11 -0
  66. data/lib/ri_cal/property_value/date.rb +175 -0
  67. data/lib/ri_cal/property_value/date_time.rb +335 -0
  68. data/lib/ri_cal/property_value/date_time/additive_methods.rb +44 -0
  69. data/lib/ri_cal/property_value/date_time/time_machine.rb +181 -0
  70. data/lib/ri_cal/property_value/date_time/timezone_support.rb +96 -0
  71. data/lib/ri_cal/property_value/duration.rb +110 -0
  72. data/lib/ri_cal/property_value/geo.rb +11 -0
  73. data/lib/ri_cal/property_value/integer.rb +12 -0
  74. data/lib/ri_cal/property_value/occurrence_list.rb +144 -0
  75. data/lib/ri_cal/property_value/period.rb +82 -0
  76. data/lib/ri_cal/property_value/recurrence_rule.rb +145 -0
  77. data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +97 -0
  78. data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +79 -0
  79. data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +148 -0
  80. data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +53 -0
  81. data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +31 -0
  82. data/lib/ri_cal/property_value/recurrence_rule/occurence_incrementer.rb +793 -0
  83. data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +131 -0
  84. data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +60 -0
  85. data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +33 -0
  86. data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +49 -0
  87. data/lib/ri_cal/property_value/recurrence_rule/validations.rb +125 -0
  88. data/lib/ri_cal/property_value/text.rb +40 -0
  89. data/lib/ri_cal/property_value/uri.rb +11 -0
  90. data/lib/ri_cal/property_value/utc_offset.rb +33 -0
  91. data/lib/ri_cal/required_timezones.rb +55 -0
  92. data/ri_cal.gemspec +49 -0
  93. data/sample_ical_files/from_ical_dot_app/test1.ics +38 -0
  94. data/script/console +10 -0
  95. data/script/destroy +14 -0
  96. data/script/generate +14 -0
  97. data/script/txt2html +71 -0
  98. data/spec/ri_cal/component/alarm_spec.rb +12 -0
  99. data/spec/ri_cal/component/calendar_spec.rb +54 -0
  100. data/spec/ri_cal/component/event_spec.rb +601 -0
  101. data/spec/ri_cal/component/freebusy_spec.rb +12 -0
  102. data/spec/ri_cal/component/journal_spec.rb +37 -0
  103. data/spec/ri_cal/component/t_z_info_timezone_spec.rb +36 -0
  104. data/spec/ri_cal/component/timezone_spec.rb +218 -0
  105. data/spec/ri_cal/component/todo_spec.rb +112 -0
  106. data/spec/ri_cal/component_spec.rb +224 -0
  107. data/spec/ri_cal/core_extensions/string/conversions_spec.rb +78 -0
  108. data/spec/ri_cal/core_extensions/time/calculations_spec.rb +188 -0
  109. data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +45 -0
  110. data/spec/ri_cal/occurrence_enumerator_spec.rb +573 -0
  111. data/spec/ri_cal/parser_spec.rb +303 -0
  112. data/spec/ri_cal/property_value/date_spec.rb +53 -0
  113. data/spec/ri_cal/property_value/date_time_spec.rb +383 -0
  114. data/spec/ri_cal/property_value/duration_spec.rb +126 -0
  115. data/spec/ri_cal/property_value/occurrence_list_spec.rb +72 -0
  116. data/spec/ri_cal/property_value/period_spec.rb +49 -0
  117. data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +21 -0
  118. data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1814 -0
  119. data/spec/ri_cal/property_value/text_spec.rb +25 -0
  120. data/spec/ri_cal/property_value/utc_offset_spec.rb +48 -0
  121. data/spec/ri_cal/property_value_spec.rb +125 -0
  122. data/spec/ri_cal/required_timezones_spec.rb +67 -0
  123. data/spec/ri_cal_spec.rb +53 -0
  124. data/spec/spec.opts +4 -0
  125. data/spec/spec_helper.rb +46 -0
  126. data/tasks/gem_loader/load_active_support.rb +3 -0
  127. data/tasks/gem_loader/load_tzinfo_gem.rb +2 -0
  128. data/tasks/ri_cal.rake +410 -0
  129. data/tasks/spec.rake +50 -0
  130. metadata +221 -0
@@ -0,0 +1,7 @@
1
+ Seconds - Freq="SECONDLY", BYSECOND
2
+ Minutes - Freq="MINUTELY", BYMINUTE
3
+ Hours - Freq="HOURLY", BYHOUR
4
+ Days - Freq="DAILY", BYDAY, BYMONTHDAY, BYYEARDAY
5
+ Weeks - Freq="WEEKLY", BYWEEKNO
6
+ Months - Freq="MONTHLY", BYMONTH
7
+ Years - Freq="YEARLY"
Binary file
@@ -0,0 +1,144 @@
1
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
2
+ #
3
+ # The RiCal module provides the outermost namespace, along with several convenience methods for parsing
4
+ # and building calendars and calendar components.
5
+ module RiCal
6
+
7
+ my_dir = File.dirname(__FILE__)
8
+
9
+ autoload :Component, "#{my_dir}/ri_cal/component.rb"
10
+ autoload :TimezonePeriod, "#{my_dir}/ri_cal/properties/timezone_period.rb"
11
+ autoload :OccurrenceEnumerator, "#{my_dir}/ri_cal/occurrence_enumerator.rb"
12
+
13
+ # :stopdoc:
14
+ VERSION = '0.5.0'
15
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
16
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
17
+
18
+ # Returns the version string for the library.
19
+ #
20
+ def self.version
21
+ VERSION
22
+ end
23
+
24
+ # Returns the library path for the module. If any arguments are given,
25
+ # they will be joined to the end of the libray path using
26
+ # <tt>File.join</tt>.
27
+ #
28
+ def self.libpath( *args )
29
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
30
+ end
31
+
32
+ # Returns the lpath for the module. If any arguments are given,
33
+ # they will be joined to the end of the path using
34
+ # <tt>File.join</tt>.
35
+ #
36
+ def self.path( *args )
37
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
38
+ end
39
+
40
+ # Utility method used to rquire all files ending in .rb that lie in the
41
+ # directory below this file that has the same name as the filename passed
42
+ # in. Optionally, a specific _directory_ name can be passed in such that
43
+ # the _filename_ does not have to be equivalent to the directory.
44
+ #
45
+ def self.require_all_libs_relative_to( fname, dir = nil )
46
+ dir ||= ::File.basename(fname, '.*')
47
+ search_me = ::File.expand_path(::File.join(::File.dirname(fname), dir, '**', '*.rb'))
48
+ Dir.glob(search_me).sort.each {|rb|
49
+ require rb}
50
+ end
51
+
52
+ # :startdoc:
53
+
54
+ # Parse an io stream and return an array of iCalendar entities.
55
+ # Normally this will be an array of RiCal::Component::Calendar instances
56
+ def self.parse(io)
57
+ Parser.new(io).parse
58
+ end
59
+
60
+ # Parse a string and return an array of iCalendar entities.
61
+ # see RiCal.parse
62
+ def self.parse_string(string)
63
+ parse(StringIO.new(string))
64
+ end
65
+
66
+ def self.debug # :nodoc:
67
+ @debug
68
+ end
69
+
70
+ def self.debug=(val) # :nodoc:
71
+ @debug = val
72
+ end
73
+
74
+ # return a new Alarm event or todo component. If a block is provided it will will be executed in
75
+ # the context of a builder object which can be used to initialize the properties of the
76
+ # new Alarm.
77
+ def self.Alarm(&init_block)
78
+ Component::Alarm.new(&init_block)
79
+ end
80
+
81
+ # return a new Calendar. If a block is provided it will will be executed in
82
+ # the context of a builder object which can be used to initialize the properties and components of the
83
+ # new calendar.
84
+ def self.Calendar(&init_block)
85
+ Component::Calendar.new(&init_block)
86
+ end
87
+
88
+ # return a new Event calendar component. If a block is provided it will will be executed in
89
+ # the context of a builder object which can be used to initialize the properties and alarms of the
90
+ # new Event.
91
+ def self.Event(&init_block)
92
+ Component::Event.new(&init_block)
93
+ end
94
+
95
+ # return a new Freebusy calendar component. If a block is provided it will will be executed in
96
+ # the context of a builder object which can be used to initialize the properties and components of the
97
+ # new Freebusy.
98
+ def self.Freebusy(&init_block)
99
+ Component::Freebusy.new(&init_block)
100
+ end
101
+
102
+ # return a new Journal calendar component. If a block is provided it will will be executed in
103
+ # the context of a builder object which can be used to initialize the properties and components of the
104
+ # new Event.
105
+ def self.Journal(&init_block)
106
+ Component::Journal.new(&init_block)
107
+ end
108
+
109
+ # return a new Timezone calendar component. If a block is provided it will will be executed in
110
+ # the context of a builder object which can be used to initialize the properties and timezone periods of the
111
+ # new Timezone.
112
+ def self.Timezone(&init_block)
113
+ Component::Timezone.new(&init_block)
114
+ end
115
+
116
+ # return a new TimezonePeriod timezone component. If a block is provided it will will be executed in
117
+ # the context of a builder object which can be used to initialize the properties of the
118
+ # new TimezonePeriod.
119
+ def self.TimezonePeriod(&init_block)
120
+ Component::TimezonePeriod.new(&init_block)
121
+ end
122
+
123
+ if Object.const_defined?(:ActiveSupport)
124
+ as = Object.const_get(:ActiveSupport)
125
+ if as.const_defined?(:TimeWithZone)
126
+ time_with_zone = as.const_get(:TimeWithZone)
127
+ end
128
+ end
129
+
130
+ # TimeWithZone will be set to ActiveSupport::TimeWithZone if the activesupport gem is loaded
131
+ # otherwise it will be nil
132
+ TimeWithZone = time_with_zone
133
+
134
+ # return a new Todo calendar component. If a block is provided it will will be executed in
135
+ # the context of a builder object which can be used to initialize the properties and alarms of the
136
+ # new Todo.
137
+ def self.Todo(&init_block)
138
+ Component::Todo.new(&init_block)
139
+ end
140
+ end # module RiCal
141
+
142
+ RiCal.require_all_libs_relative_to(__FILE__)
143
+
144
+ # EOF
@@ -0,0 +1,247 @@
1
+ module RiCal
2
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
3
+ #
4
+ class Component #:nodoc:
5
+ class ComponentBuilder #:nodoc:
6
+ def initialize(component)
7
+ @component = component
8
+ end
9
+
10
+ def method_missing(selector, *args, &init_block) #:nodoc:
11
+ if(sub_comp_class = @component.subcomponent_class[selector])
12
+ if init_block
13
+ sub_comp = sub_comp_class.new(@component)
14
+ if init_block.arity == 1
15
+ yield ComponentBuilder.new(sub_comp)
16
+ else
17
+ ComponentBuilder.new(sub_comp).instance_eval(&init_block)
18
+ end
19
+ self.add_subcomponent(sub_comp)
20
+ end
21
+ else
22
+ sel = selector.to_s
23
+ sel = "#{sel}=" unless /(^(add_)|(remove_))|(=$)/ =~ sel
24
+ if @component.respond_to?(sel)
25
+ @component.send(sel, *args)
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ autoload :Timezone, "#{File.dirname(__FILE__)}/component/timezone.rb"
34
+
35
+ attr_accessor :imported #:nodoc:
36
+
37
+ def initialize(parent=nil, &init_block) #:nodoc:
38
+ @parent = parent
39
+ if block_given?
40
+ if init_block.arity == 1
41
+ init_block.call(ComponentBuilder.new(self))
42
+ else
43
+ ComponentBuilder.new(self).instance_eval(&init_block)
44
+ end
45
+ end
46
+ end
47
+
48
+ def default_tzid #:nodoc:
49
+ if @parent
50
+ @parent.default_tzid
51
+ else
52
+ PropertyValue::DateTime.default_tzid
53
+ end
54
+ end
55
+
56
+ def find_timezone(identifier) #:nodoc:
57
+ if @parent
58
+ @parent.find_timezone(identifier)
59
+ else
60
+ begin
61
+ Calendar::TZInfoWrapper.new(TZInfo::Timezone.get(identifier), self)
62
+ rescue ::TZInfo::InvalidTimezoneIdentifier => ex
63
+ raise RiCal::InvalidTimezoneIdentifier.invalid_tzinfo_identifier(identifier)
64
+ end
65
+ end
66
+ end
67
+
68
+ def tz_info_source?
69
+ if @parent
70
+ @parent.tz_info_source?
71
+ else
72
+ true
73
+ end
74
+ end
75
+
76
+ def time_zone_for(ruby_object) #:nodoc:
77
+ @parent.time_zone_for(ruby_object) #:nodoc:
78
+ end
79
+
80
+ def subcomponent_class #:nodoc:
81
+ {}
82
+ end
83
+
84
+ def self.from_parser(parser, parent) #:nodoc:
85
+ entity = self.new(parent)
86
+ entity.imported = true
87
+ line = parser.next_separated_line
88
+ while parser.still_in(entity_name, line)
89
+ entity.process_line(parser, line)
90
+ line = parser.next_separated_line
91
+ end
92
+ entity
93
+ end
94
+
95
+ def self.parse(io) #:nodoc:
96
+ Parser.new(io).parse
97
+ end
98
+
99
+ def imported? #:nodoc:
100
+ imported
101
+ end
102
+
103
+ def self.parse_string(string) #:nodoc:
104
+ parse(StringIO.new(string))
105
+ end
106
+
107
+ def subcomponents #:nodoc:
108
+ @subcomponents ||= Hash.new {|h, k| h[k] = []}
109
+ end
110
+
111
+ def entity_name #:nodoc:
112
+ self.class.entity_name
113
+ end
114
+
115
+ # return an array of Alarm components within this component :nodoc:
116
+ # Alarms may be contained within Events, and Todos
117
+ def alarms
118
+ subcomponents["VALARM"]
119
+ end
120
+
121
+ def add_subcomponent(component) #:nodoc:
122
+ subcomponents[component.entity_name] << component
123
+ end
124
+
125
+ def parse_subcomponent(parser, line) #:nodoc:
126
+ subcomponents[line[:value]] << parser.parse_one(line, self)
127
+ end
128
+
129
+ def process_line(parser, line) #:nodoc:
130
+ if line[:name] == "BEGIN"
131
+ parse_subcomponent(parser, line)
132
+ else
133
+ setter = self.class.property_parser[line[:name]]
134
+ if setter
135
+ send(setter, line)
136
+ else
137
+ self.add_x_property(line[:name], PropertyValue::Text.new(self, line))
138
+ end
139
+ end
140
+ end
141
+
142
+ # return a hash of any extended properties, (i.e. those with a property name starting with "X-"
143
+ # representing an extension to the RFC 2445 specification)
144
+ def x_properties
145
+ @x_properties ||= {}
146
+ end
147
+
148
+ # Add a n extended property
149
+ def add_x_property(name, prop)
150
+ x_properties[name] = prop
151
+ end
152
+
153
+ def method_missing(selector, *args, &b) #:nodoc:
154
+ xprop_candidate = selector.to_s
155
+ if (match = /^x_(.+)(=?)$/.match(xprop_candidate))
156
+ if match[2] == "="
157
+ add_x_property("x_#{match[1]}", *args)
158
+ else
159
+ x_properties[xprop_candidate]
160
+ end
161
+ else
162
+ super
163
+ end
164
+ end
165
+
166
+ # Predicate to determine if the component is valid according to RFC 2445
167
+ def valid?
168
+ !mutual_exclusion_violation
169
+ end
170
+
171
+ def initialize_copy(original) #:nodoc:
172
+ end
173
+
174
+ def prop_string(prop_name, *properties) #:nodoc:
175
+ properties = properties.flatten.compact
176
+ if properties && !properties.empty?
177
+ properties.map {|prop| "#{prop_name}#{prop.to_s}"}.join("\n")
178
+ else
179
+ nil
180
+ end
181
+ end
182
+
183
+ def add_property_date_times_to(required_timezones, property) #:nodoc:
184
+ if property
185
+ if Array === property
186
+ property.each do |prop|
187
+ prop.add_date_times_to(required_timezones)
188
+ end
189
+ else
190
+ property.add_date_times_to(required_timezones)
191
+ end
192
+ end
193
+ end
194
+
195
+ def export_prop_to(export_stream, name, prop) #:nodoc:
196
+ if prop
197
+ string = prop_string(name, prop)
198
+ export_stream.puts(string) if string
199
+ end
200
+ end
201
+
202
+ def export_x_properties_to(export_stream) #:nodoc:
203
+ x_properties.each do |name, prop|
204
+ export_stream.puts("#{name}:#{prop}")
205
+ end
206
+ end
207
+
208
+ def export_subcomponent_to(export_stream, subcomponent) #:nodoc:
209
+ subcomponent.each do |component|
210
+ component.export_to(export_stream)
211
+ end
212
+ end
213
+
214
+ # return a string containing the rfc2445 format of the component
215
+ def to_s
216
+ io = StringIO.new
217
+ export_to(io)
218
+ io.string
219
+ end
220
+
221
+ # Export this component to an export stream
222
+ def export_to(export_stream)
223
+ export_stream.puts("BEGIN:#{entity_name}")
224
+ export_properties_to(export_stream)
225
+ export_x_properties_to(export_stream)
226
+ subcomponents.values do |sub|
227
+ export_subcomponent_to(export_subcomponent_to, sub)
228
+ end
229
+ export_stream.puts("END:#{entity_name}")
230
+ end
231
+
232
+ # Export this single component as an iCalendar component containing only this component and
233
+ # any required additional components (i.e. VTIMEZONES referenced from this component)
234
+ # if stream is nil (the default) then this method will return a string,
235
+ # otherwise stream should be an IO to which the iCalendar file contents will be written
236
+ def export(stream=nil)
237
+ wrapper_calendar = Calendar.new
238
+ wrapper_calendar.add_subcomponent(self)
239
+ wrapper_calendar.export(stream)
240
+ end
241
+ end
242
+ end
243
+
244
+ Dir[File.dirname(__FILE__) + "/component/*.rb"].sort.each do |path|
245
+ filename = File.basename(path)
246
+ require path
247
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. properties alarm.rb])
2
+
3
+ module RiCal
4
+
5
+ class Component
6
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
7
+ #
8
+ # An Alarm component groups properties defining a reminder or alarm associated with an event or to-do
9
+ # TODO: The Alarm component has complex cardinality restrictions depending on the value of the action property
10
+ # i.e. audio, display, email, and proc alarms, this is currently not checked or enforced
11
+ #
12
+ # to see the property accessing methods for this class see the RiCal::Properties::Alarm module
13
+ class Alarm < Component
14
+ include RiCal::Properties::Alarm
15
+
16
+ def self.entity_name #:nodoc:
17
+ "VALARM"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,219 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. properties calendar.rb])
2
+
3
+ module RiCal
4
+ class Component
5
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
6
+ #
7
+ # to see the property accessing methods for this class see the RiCal::Properties::Calendar module
8
+ class Calendar < Component
9
+ include RiCal::Properties::Calendar
10
+ attr_reader :tz_source #:nodoc:
11
+
12
+ def initialize(parent=nil, &init_block) #:nodoc:
13
+ @tz_source = 'TZINFO' # Until otherwise told
14
+ super
15
+ end
16
+
17
+ def self.entity_name #:nodoc:
18
+ "VCALENDAR"
19
+ end
20
+
21
+ def tz_info_source? #:nodoc:
22
+ @tz_source == 'TZINFO'
23
+ end
24
+
25
+ def required_timezones # :nodoc:
26
+ @required_timezones ||= RequiredTimezones.new
27
+ end
28
+
29
+ def subcomponent_class # :nodoc:
30
+ {
31
+ :event => Event,
32
+ :todo => Todo,
33
+ :journal => Journal,
34
+ :freebusy => Freebusy,
35
+ :timezone => Timezone,
36
+ }
37
+ end
38
+
39
+ def export_properties_to(export_stream) # :nodoc:
40
+ prodid_property.params["X-RICAL-TZSOURCE"] = @tz_source
41
+ export_prop_to(export_stream, "PRODID", prodid_property)
42
+ export_prop_to(export_stream, "CALSCALE", calscale_property)
43
+ export_prop_to(export_stream, "VERSION", version_property)
44
+ export_prop_to(export_stream, "METHOD", method_property)
45
+ end
46
+
47
+ def prodid_property_from_string(line) # :nodoc:
48
+ result = super
49
+ @tz_source = prodid_property.params["X-RICAL-TZSOURCE"]
50
+ result
51
+ end
52
+
53
+ # Return the default time zone identifier for this calendar
54
+ def default_tzid
55
+ @default_tzid || PropertyValue::DateTime.default_tzid
56
+ end
57
+
58
+ # Set the default time zone identifier for this calendar
59
+ # To set the default to floating times use a value of :floating
60
+ def default_tzid=(value)
61
+ @default_tzid=value
62
+ end
63
+
64
+ # return an array of event components contained within this Calendar
65
+ def events
66
+ subcomponents["VEVENT"]
67
+ end
68
+
69
+ # add an event to the calendar
70
+ def add_subcomponent(component)
71
+ super(component)
72
+ component.add_date_times_to(required_timezones) if tz_info_source?
73
+ end
74
+
75
+ # return an array of todo components contained within this Calendar
76
+ def todos
77
+ subcomponents["VTODO"]
78
+ end
79
+
80
+ # return an array of journal components contained within this Calendar
81
+ def journals
82
+ subcomponents["VJOURNAL"]
83
+ end
84
+
85
+ # return an array of freebusy components contained within this Calendar
86
+ def freebusys
87
+ subcomponents["VFREEBUSY"]
88
+ end
89
+
90
+ class TimezoneID #:nodoc:
91
+ attr_reader :identifier, :calendar
92
+ def initialize(identifier, calendar)
93
+ self.identifier, self.calendar = identifier, calendar
94
+ end
95
+
96
+ def tzinfo_timezone
97
+ nil
98
+ end
99
+
100
+ def resolved
101
+ calendar.find_timezone(identifier)
102
+ end
103
+
104
+ def local_to_utc(local)
105
+ resolved.local_to_utc(date_time_prop)
106
+ end
107
+ end
108
+
109
+ # return an array of timezone components contained within this calendar
110
+ def timezones
111
+ subcomponents["VTIMEZONE"]
112
+ end
113
+
114
+ class TZInfoWrapper #:nodoc:
115
+ attr_reader :tzinfo, :calendar #:nodoc:
116
+ def initialize(tzinfo, calendar) #:nodoc:
117
+ @tzinfo = tzinfo
118
+ @calendar = calendar
119
+ end
120
+
121
+ def identifier #:nodoc:
122
+ tzinfo.identifier
123
+ end
124
+
125
+ def local_date_time(ruby_time, tzid) #:nodoc:
126
+ RiCal::PropertyValue::DateTime.new(calendar, :value => ruby_time.strftime("%Y%m%dT%H%M%S"), :params => {'TZID' => tzid})
127
+ end
128
+
129
+ def utc_date_time(ruby_time) #:nodoc
130
+ RiCal::PropertyValue::DateTime.new(calendar, :value => ruby_time.strftime("%Y%m%dT%H%M%SZ"))
131
+ end
132
+
133
+ def local_to_utc(utc) #:nodoc:
134
+ utc_date_time(tzinfo.local_to_utc(utc.to_ri_cal_ruby_value))
135
+ end
136
+
137
+ def utc_to_local(local) #:nodoc:
138
+ local_date_time(tzinfo.utc_to_local(local.to_ri_cal_ruby_value), tzinfo.identifier)
139
+ end
140
+
141
+
142
+ def rational_utc_offset(local)
143
+ Rational(tzinfo.period_for_local(local, true).utc_total_offset, 3600) / 24
144
+ end
145
+
146
+ end
147
+
148
+ def find_timezone(identifier) #:nodoc:
149
+ if tz_info_source?
150
+ begin
151
+ TZInfoWrapper.new(TZInfo::Timezone.get(identifier), self)
152
+ rescue ::TZInfo::InvalidTimezoneIdentifier => ex
153
+ raise RiCal::InvalidTimezoneIdentifier.invalid_tzinfo_identifier(identifier)
154
+ end
155
+ else
156
+ result = timezones.find {|tz| tz.tzid == identifier}
157
+ raise RiCal::InvalidTimezoneIdentifier.not_found_in_calendar(identifier) unless result
158
+ result
159
+ end
160
+ end
161
+
162
+ def export_required_timezones(export_stream) # :nodoc:
163
+ required_timezones.export_to(export_stream)
164
+ end
165
+
166
+ class FoldingStream #:nodoc:
167
+ attr_reader :stream #:nodoc:
168
+ def initialize(stream) #:nodoc:
169
+ @stream = stream || StringIO.new
170
+ end
171
+
172
+ def string #:nodoc:
173
+ stream.string
174
+ end
175
+
176
+ def fold(string) #:nodoc:
177
+ stream.puts(string[0,73])
178
+ string = string[73..-1]
179
+ while string
180
+ stream.puts " #{string[0, 72]}"
181
+ string = string[72..-1]
182
+ end
183
+ end
184
+
185
+ def puts(*strings) #:nodoc:
186
+ strings.each do |string|
187
+ string.split("\n").each do |line|
188
+ fold(line)
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ # Export this calendar as an iCalendar file.
195
+ # if to is nil (the default) then this method will return a string,
196
+ # otherwise to should be an IO to which the iCalendar file contents will be written
197
+ def export(to=nil)
198
+ export_stream = FoldingStream.new(to)
199
+ export_stream.puts("BEGIN:VCALENDAR")
200
+ #TODO: right now I'm assuming that all timezones are internal what happens when we export
201
+ # an imported calendar.
202
+ export_properties_to(export_stream)
203
+ export_x_properties_to(export_stream)
204
+ export_required_timezones(export_stream)
205
+ export_subcomponent_to(export_stream, events)
206
+ export_subcomponent_to(export_stream, todos)
207
+ export_subcomponent_to(export_stream, journals)
208
+ export_subcomponent_to(export_stream, freebusys)
209
+ export_stream.puts("END:VCALENDAR")
210
+ if to
211
+ nil
212
+ else
213
+ export_stream.string
214
+ end
215
+ end
216
+
217
+ end
218
+ end
219
+ end