ri_cal 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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