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,149 @@
1
+ module RiCal
2
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
3
+ #
4
+ # PropertyValue provides common implementation of various RFC 2445 property value types
5
+ class PropertyValue
6
+
7
+ attr_writer :params, :value #:nodoc:
8
+ attr_reader :timezone_finder #:nodoc:
9
+ def initialize(timezone_finder, options={}) # :nodoc:
10
+ @timezone_finder = timezone_finder
11
+ validate_value(options)
12
+ ({:params => {}}).merge(options).each do |attribute, val|
13
+ unless attribute == :name
14
+ setter = :"#{attribute.to_s.downcase}="
15
+ send(setter, val)
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.if_valid_string(timezone_finder, string) #:nodoc:
21
+ if valid_string?(string)
22
+ new(timezone_finder, :value => string)
23
+ else
24
+ nil
25
+ end
26
+ end
27
+
28
+ def validate_value(options) #:nodoc:
29
+ val = options[:value]
30
+ raise "Invalid property value #{val.inspect}" if val.kind_of?(String) && /^;/.match(val)
31
+ end
32
+
33
+ # return a hash containing the parameters and values, if any
34
+ def params
35
+ @params ||= {}
36
+ end
37
+
38
+ def to_options_hash #:nodoc:
39
+ options_hash = {:value => value}
40
+ options_hash[:params] = params unless params.empty?
41
+ end
42
+
43
+ def self.date_or_date_time(timezone_finder, separated_line) # :nodoc:
44
+ match = separated_line[:value].match(/(\d\d\d\d)(\d\d)(\d\d)((T?)((\d\d)(\d\d)(\d\d))(Z?))?/)
45
+ raise Exception.new("Invalid date") unless match
46
+ if match[5] == "T" # date-time
47
+ time = Time.utc(match[1].to_i, match[2].to_i, match[3].to_i, match[7].to_i, match[8].to_i, match[9].to_i)
48
+ parms = (separated_line[:params] ||{}).dup
49
+ if match[10] == "Z"
50
+ raise Exception.new("Invalid time, cannot combine Zulu with timezone reference") if parms[:tzid]
51
+ parms['TZID'] = "UTC"
52
+ end
53
+ PropertyValue::DateTime.new(timezone_finder, separated_line.merge(:params => parms))
54
+ else
55
+ PropertyValue::Date.new(timezone_finder, separated_line)
56
+ end
57
+ end
58
+
59
+ def self.date_or_date_time_or_period(timezone_finder, separated_line) #:nodoc:
60
+ if separated_line[:value].include?("/")
61
+ PropertyValue::Period.new(timezone_finder, separated_line)
62
+ else
63
+ date_or_date_time(timezone_finder, separated_line)
64
+ end
65
+ end
66
+
67
+ # def self.from_string(string) # :nodoc:
68
+ # new(nil, :value => string)
69
+ # end
70
+
71
+ def self.convert(timezone_finder, value) #:nodoc:
72
+ new(timezone_finder, :value => value)
73
+ end
74
+
75
+ # Determine if another object is equivalent to the receiver.
76
+ def ==(o)
77
+ if o.class == self.class
78
+ equality_value == o.equality_value
79
+ else
80
+ super
81
+ end
82
+ end
83
+
84
+ # Return the string value
85
+ def value
86
+ @value
87
+ end
88
+
89
+ def equality_value #:nodoc:
90
+ value
91
+ end
92
+
93
+ def visible_params # :nodoc:
94
+ params
95
+ end
96
+
97
+ def parms_string #:nodoc:
98
+ if (vp = visible_params) && !vp.empty?
99
+ # We only sort for testability reasons
100
+ vp.keys.sort.map {|key| ";#{key}=#{vp[key]}"}.join
101
+ else
102
+ ""
103
+ end
104
+ end
105
+
106
+ # Return a string representing the receiver in RFC 2445 format
107
+ def to_s #:nodoc:
108
+ "#{parms_string}:#{value}"
109
+ end
110
+
111
+ # return the ruby value
112
+ def ruby_value
113
+ self.value
114
+ end
115
+
116
+ def to_ri_cal_property_value #:nodoc:
117
+ self
118
+ end
119
+
120
+ def find_timezone(timezone_identifier) #:nodoc:
121
+ if timezone_finder
122
+ timezone_finder.find_timezone(timezone_identifier)
123
+ else
124
+ raise "Unable to find timezone with tzid #{timezone_identifier}"
125
+ end
126
+ end
127
+
128
+ def default_tzid #:nodoc:
129
+ if timezone_finder
130
+ timezone_finder.default_tzid
131
+ else
132
+ PropertyValue::DateTime.default_tzid
133
+ end
134
+ end
135
+
136
+ def tz_info_source? #:nodoc:
137
+ if timezone_finder
138
+ timezone_finder.tz_info_source?
139
+ else
140
+ true
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ Dir[File.dirname(__FILE__) + "/property_value/*.rb"].sort.each do |path|
147
+ filename = File.basename(path)
148
+ require path
149
+ end
@@ -0,0 +1,27 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
4
+ #
5
+ class Array < PropertyValue # :nodoc:
6
+
7
+ def value=(val)
8
+ case val
9
+ when String
10
+ @value = val.split(",")
11
+ else
12
+ @value = val
13
+ end
14
+ end
15
+
16
+ def value
17
+ @value.join(",")
18
+ end
19
+
20
+ def self.convert(timezone_finder, ruby_object) # :nodoc:
21
+ self.new(timezone_finder, :value => ruby_object)
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,11 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
4
+ #
5
+ # RiCal::PropertyValue::CalAddress represents an icalendar CalAddress property value
6
+ # which is defined in
7
+ # RFC 2445 section 4.3.3 p 34
8
+ class CalAddress < PropertyValue
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,175 @@
1
+ require 'date'
2
+ module RiCal
3
+ class PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ # RiCal::PropertyValue::CalAddress represents an icalendar Date property value
7
+ # which is defined in
8
+ # RFC 2445 section 4.3.4 p 34
9
+ class Date < PropertyValue
10
+
11
+ def self.valid_string?(string) #:nodoc:
12
+ string =~ /^\d{8}$/
13
+ end
14
+
15
+ # Returns the value of the reciever as an RFC 2445 iCalendar string
16
+ def value
17
+ if @date_time_value
18
+ @date_time_value.strftime("%Y%m%d")
19
+ else
20
+ nil
21
+ end
22
+ end
23
+
24
+ # Set the value of the property to val
25
+ #
26
+ # val may be either:
27
+ #
28
+ # * A string which can be parsed as a DateTime
29
+ # * A Time instance
30
+ # * A Date instance
31
+ # * A DateTime instance
32
+ def value=(val)
33
+ case val
34
+ when nil
35
+ @date_time_value = nil
36
+ when String
37
+ @date_time_value = ::DateTime.parse(::DateTime.parse(val).strftime("%Y%m%d"))
38
+ when ::Time, ::Date, ::DateTime
39
+ @date_time_value = ::DateTime.parse(val.strftime("%Y%m%d"))
40
+ end
41
+ end
42
+
43
+ # Nop to allow occurrence list to try to set it
44
+ def tzid=(val)#:nodoc:
45
+ end
46
+
47
+ def tzid #:nodoc:
48
+ nil
49
+ end
50
+
51
+ def visible_params #:nodoc:
52
+ {"VALUE" => "DATE"}.merge(params)
53
+ end
54
+
55
+ # Returns the year (including the century)
56
+ def year
57
+ @date_time_value.year
58
+ end
59
+
60
+ # Returns the month of the year (1..12)
61
+ def month
62
+ @date_time_value.month
63
+ end
64
+
65
+ # Returns the day of the month
66
+ def day
67
+ @date_time_value.day
68
+ end
69
+
70
+ # Returns the ruby representation a ::Date
71
+ def ruby_value
72
+ ::Date.parse(@date_time_value.strftime("%Y%m%d"))
73
+ end
74
+
75
+ alias_method :to_ri_cal_ruby_value, :ruby_value
76
+
77
+ # Return an instance of RiCal::PropertyValue::DateTime representing the start of this date
78
+ def to_ri_cal_date_time_value
79
+ PropertyValue::DateTime.new(timezone_finder, :value => @date_time_value)
80
+ end
81
+
82
+ # Return this date property
83
+ def to_ri_cal_date_value(timezone_finder = nil)
84
+ self
85
+ end
86
+
87
+ # Return the "Natural' property value for the date_property, in this case the date property itself."
88
+ def to_ri_cal_date_or_date_time_value
89
+ self
90
+ end
91
+
92
+ def compute_change(d, options) #:nodoc:
93
+ ::Date.civil((options[:year] || d.year), (options[:month] || d.month), (options[:day] || d.day))
94
+ end
95
+
96
+ def compute_advance(d, options) #:nodoc:
97
+ d = d >> options[:years] * 12 if options[:years]
98
+ d = d >> options[:months] if options[:months]
99
+ d = d + options[:weeks] * 7 if options[:weeks]
100
+ d = d + options[:days] if options[:days]
101
+ compute_change(@date_time_value, :year => d.year, :month => d.month, :day => d.day)
102
+ end
103
+
104
+ def advance(options) #:nodoc:
105
+ PropertyValue::Date.new(timezone_finder, :value => compute_advance(@date_time_value, options), :params =>(params ? params.dup : nil) )
106
+ end
107
+
108
+ def change(options) #:nodoc:
109
+ PropertyValue::Date.new(timezone_finder,:value => compute_change(@date_time_value, options), :params => (params ? params.dup : nil) )
110
+ end
111
+
112
+ def add_date_times_to(required_timezones) #:nodoc:
113
+ # Do nothing since dates don't have a timezone
114
+ end
115
+
116
+ # Return the difference between the receiver and other
117
+ #
118
+ # The parameter other should be either a RiCal::PropertyValue::Duration or a RiCal::PropertyValue::DateTime
119
+ #
120
+ # If other is a Duration, the result will be a DateTime, if it is a DateTime the result will be a Duration
121
+ def -(other)
122
+ other.subtract_from_date_time_value(to_ri_cal_date_time_value)
123
+ end
124
+
125
+ def subtract_from_date_time_value(date_time)
126
+ to_ri_cal_date_time_value.subtract_from_date_time_value(date_time)
127
+ end
128
+
129
+ # Return the sum of the receiver and duration
130
+ #
131
+ # The parameter other duration should be a RiCal::PropertyValue::Duration
132
+ #
133
+ # The result will be an RiCal::PropertyValue::DateTime
134
+ def +(duration)
135
+ duration.add_to_date_time_value(to_ri_cal_date_time_value)
136
+ end
137
+
138
+ # Delegate unknown messages to the wrappered Date instance.
139
+ # TODO: Is this really necessary?
140
+ def method_missing(selector, *args) #:nodoc:
141
+ @date_time_value.send(selector, *args)
142
+ end
143
+
144
+ # TODO: consider if this should be a period rather than a hash
145
+ def occurrence_period(default_duration) #:nodoc:
146
+ date_time = self.to_ri_cal_date_time_value
147
+ RiCal::OccurrencePeriod.new(date_time, date_time.advance(:hours => 24, :seconds => -1))
148
+ end
149
+
150
+ def start_of_day?
151
+ true
152
+ end
153
+
154
+ def to_zulu_occurrence_range_start_time
155
+ to_ri_cal_date_time_value.to_zulu_occurrence_range_start_time
156
+ end
157
+
158
+ def to_zulu_occurrence_range_finish_time
159
+ to_ri_cal_date_time_value.end_of_day.to_zulu_occurrence_range_finish_time
160
+ end
161
+
162
+ def to_finish_time
163
+ to_ri_cal_date_time_value.end_of_day.to_datetime
164
+ end
165
+
166
+ def for_occurrence(occurrence)
167
+ if occurrence.start_of_day?
168
+ occurrence.to_ri_cal_date_value(timezone_finder)
169
+ else
170
+ occurrence.for_parent(timezone_finder)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,335 @@
1
+ require 'date'
2
+ module RiCal
3
+ class PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ # RiCal::PropertyValue::CalAddress represents an icalendar CalAddress property value
7
+ # which is defined in RFC 2445 section 4.3.5 pp 35-37
8
+ class DateTime < PropertyValue
9
+
10
+ Dir[File.dirname(__FILE__) + "/date_time/*.rb"].sort.each do |path|
11
+ require path
12
+ end
13
+
14
+ include Comparable
15
+ include AdditiveMethods
16
+ include TimezoneSupport
17
+ include TimeMachine
18
+
19
+ def self.or_date(parent, line) # :nodoc:
20
+ if /T/.match(line[:value] || "")
21
+ new(parent, line)
22
+ else
23
+ PropertyValue::Date.new(parent, line)
24
+ end
25
+ end
26
+
27
+ def self.valid_string?(string) #:nodoc:
28
+ string =~ /^\d{8}T\d{6}Z?$/
29
+ end
30
+
31
+ def self.default_tzid # :nodoc:
32
+ @default_tzid ||= "UTC"
33
+ end
34
+
35
+ def self.params_for_tzid(tzid) #:nodoc:
36
+ if tzid == :floating
37
+ {}
38
+ else
39
+ {'TZID' => tzid}
40
+ end
41
+ end
42
+
43
+ # Set the default tzid to be used when instantiating an instance from a ruby object
44
+ # see RiCal::PropertyValue::DateTime.from_time
45
+ #
46
+ # The parameter tzid is a string value to be used for the default tzid, a value of :floating will cause
47
+ # values with NO timezone to be produced, which will be interpreted by iCalendar as floating times
48
+ # i.e. they are interpreted in the timezone of each client. Floating times are typically used
49
+ # to represent events which are 'repeated' in the various time zones, like the first hour of the year.
50
+ def self.default_tzid=(tzid)
51
+ @default_tzid = tzid
52
+ end
53
+
54
+ def self.default_tzid_hash # :nodoc:
55
+ if default_tzid.to_s == 'none'
56
+ {}
57
+ else
58
+ {'TZID' => default_tzid}
59
+ end
60
+ end
61
+
62
+ def inspect # :nodoc:
63
+ "#{@date_time_value}:#{tzid}"
64
+ end
65
+
66
+ # Returns the value of the receiver as an RFC 2445 iCalendar string
67
+ def value
68
+ if @date_time_value
69
+ @date_time_value.strftime("%Y%m%dT%H%M%S#{tzid == "UTC" ? "Z" : ""}")
70
+ else
71
+ nil
72
+ end
73
+ end
74
+
75
+ # Set the value of the property to val
76
+ #
77
+ # val may be either:
78
+ #
79
+ # * A string which can be parsed as a DateTime
80
+ # * A Time instance
81
+ # * A Date instance
82
+ # * A DateTime instance
83
+ def value=(val) # :nodoc:
84
+ case val
85
+ when nil
86
+ @date_time_value = nil
87
+ when String
88
+ @date_time_value = ::DateTime.parse(val)
89
+ if val =~/Z/
90
+ self.tzid = 'UTC'
91
+ else
92
+ @tzid ||= :floating
93
+ end
94
+ when ::DateTime
95
+ @date_time_value = val
96
+ when ::Date, ::Time
97
+ @date_time_value = ::DateTime.parse(val.to_s)
98
+ end
99
+ end
100
+
101
+ # Extract the time and timezone identifier from an object used to set the value of a DATETIME property.
102
+ #
103
+ # If the object is a string it should be of the form [TZID=identifier:]
104
+ #
105
+ # Otherwise determine if the object acts like an activesupport enhanced time, and extract its timezone
106
+ # idenfifier if it has one.
107
+ #
108
+ def self.time_and_parameters(object)
109
+ parameters = {}
110
+ if ::String === object
111
+ object, parameters = self.time_and_parameters_from_string(object)
112
+ else
113
+ identifier = object.tzid rescue nil
114
+ parameters["TZID"] = identifier if identifier
115
+ end
116
+ [object, parameters]
117
+ end
118
+
119
+
120
+ def self.convert(timezone_finder, ruby_object) # :nodoc:
121
+ ruby_object.to_ri_cal_date_or_date_time_value(timezone_finder)
122
+ end
123
+
124
+ def self.from_string(string) # :nodoc:
125
+ if string.match(/Z$/)
126
+ new(nil, :value => string, :tzid => 'UTC')
127
+ else
128
+ new(nil, :value => string)
129
+ end
130
+ end
131
+
132
+ def for_parent(parent) #:nodoc:
133
+ if timezone_finder.nil?
134
+ @timezone_finder = parent
135
+ self
136
+ elsif parent == timezone_finder
137
+ self
138
+ else
139
+ DateTime.new(parent, :value => @date_time_value, :params => params, :tzid => tzid)
140
+ end
141
+ end
142
+
143
+ def visible_params # :nodoc:
144
+ result = {"VALUE" => "DATE-TIME"}.merge(params)
145
+ if has_local_timezone?
146
+ result['TZID'] = tzid
147
+ else
148
+ result.delete('TZID')
149
+ end
150
+ result
151
+ end
152
+
153
+ def params=(value) #:nodoc:
154
+ @params = value.dup
155
+ if params_timezone = @params['TZID']
156
+ self.tzid = @params['TZID']
157
+ end
158
+ end
159
+
160
+ # Return a Hash representing this properties parameters
161
+ def params
162
+ result = @params.dup
163
+ case tzid
164
+ when :floating, nil, "UTC"
165
+ result.delete('TZID')
166
+ else
167
+ result['TZID'] = tzid
168
+ end
169
+ result
170
+ end
171
+
172
+ # Compare the receiver with another object which must respond to the to_datetime message
173
+ # The comparison is done using the Ruby DateTime representations of the two objects
174
+ def <=>(other)
175
+ @date_time_value <=> other.to_datetime
176
+ end
177
+
178
+ # Determine if the receiver and other are in the same month
179
+ def in_same_month_as?(other)
180
+ [other.year, other.month] == [year, month]
181
+ end
182
+
183
+ def nth_wday_in_month(n, which_wday) #:nodoc:
184
+ @date_time_value.nth_wday_in_month(n, which_wday, self)
185
+ end
186
+
187
+ def nth_wday_in_year(n, which_wday) #:nodoc:
188
+ @date_time_value.nth_wday_in_year(n, which_wday, self)
189
+ end
190
+
191
+ def self.civil(year, month, day, hour, min, sec, offset, start, params) #:nodoc:
192
+ PropertyValue::DateTime.new(
193
+ :value => ::DateTime.civil(year, month, day, hour, min, sec, offset, start),
194
+ :params =>(params ? params.dup : nil)
195
+ )
196
+ end
197
+
198
+ # Return the number of days in the month containing the receiver
199
+ def days_in_month
200
+ @date_time_value.days_in_month
201
+ end
202
+
203
+ # Determine if the receiver and another object are equivalent RiCal::PropertyValue::DateTime instances
204
+ def ==(other)
205
+ if self.class === other
206
+ self.value == other.value && self.visible_params == other.visible_params && self.tzid == other.tzid
207
+ else
208
+ super
209
+ end
210
+ end
211
+
212
+ # TODO: consider if this should be a period rather than a hash
213
+ def occurrence_period(default_duration) # :nodoc:
214
+ RiCal::OccurrencePeriod.new(self, (default_duration ? self + default_duration : nil))
215
+ end
216
+
217
+ # Return the year (including the century)
218
+ def year
219
+ @date_time_value.year
220
+ end
221
+
222
+ # Return the month of the year (1..12)
223
+ def month
224
+ @date_time_value.month
225
+ end
226
+
227
+ # Return the day of the month
228
+ def day
229
+ @date_time_value.day
230
+ end
231
+
232
+ alias_method :mday, :day
233
+
234
+ # Return the day of the week
235
+ def wday
236
+ @date_time_value.wday
237
+ end
238
+
239
+ # Return the hour
240
+ def hour
241
+ @date_time_value.hour
242
+ end
243
+
244
+ # Return the minute
245
+ def min
246
+ @date_time_value.min
247
+ end
248
+
249
+ # Return the second
250
+ def sec
251
+ @date_time_value.sec
252
+ end
253
+
254
+
255
+ # Return an RiCal::PropertyValue::DateTime representing the receiver.
256
+ def to_ri_cal_date_time_value(timezone=nil)
257
+ for_parent(timezone)
258
+ end
259
+
260
+ def iso_year_and_week_one_start(wkst) #:nodoc:
261
+ @date_time_value.iso_year_and_week_one_start(wkst)
262
+ end
263
+
264
+ def iso_weeks_in_year(wkst) #:nodoc:
265
+ @date_time_value.iso_weeks_in_year(wkst) #:nodoc:
266
+ end
267
+
268
+ # Return the "Natural' property value for the receover, in this case the receiver itself."
269
+ def to_ri_cal_date_or_date_time_value(timezone_finder = nil) #:nodoc:
270
+ self.for_parent(timezone_finder)
271
+ end
272
+
273
+ # Return a Date property for this DateTime
274
+ def to_ri_cal_date_value(timezone_finder=nil)
275
+ PropertyValue::Date.new(timezone_finder, :value => @date_time_value.strftime("%Y%m%d"))
276
+ end
277
+
278
+ # Return the Ruby DateTime representation of the receiver
279
+ def to_datetime #:nodoc:
280
+ @date_time_value
281
+ end
282
+
283
+ # Returns a ruby DateTime object representing the receiver.
284
+ def ruby_value
285
+ if has_valid_tzinfo_tzid? && RiCal::TimeWithZone && tz_info_source?
286
+ RiCal::TimeWithZone.new(utc.to_datetime, ::Time.__send__(:get_zone, @tzid))
287
+ else
288
+ ::DateTime.civil(year, month, day, hour, min, sec, rational_tz_offset).set_tzid(@tzid)
289
+ end
290
+ end
291
+
292
+ alias_method :to_ri_cal_ruby_value, :to_datetime
293
+ alias_method :to_finish_time, :to_datetime
294
+
295
+ def to_zulu_time
296
+ utc.to_datetime
297
+ end
298
+
299
+ # If a time is floating, then the utc of it's start time may actually be as early
300
+ # as 12 hours earlier if the occurrence is being viewed in a time zone just west
301
+ # of the International Date Line
302
+ def to_zulu_occurrence_range_start_time
303
+ if floating?
304
+ utc.advance(:hours => -12).to_datetime
305
+ else
306
+ to_zulu_time
307
+ end
308
+ end
309
+
310
+
311
+ # If a time is floating, then the utc of it's start time may actually be as early
312
+ # as 12 hours later if the occurrence is being viewed in a time zone just east
313
+ # of the International Date Line
314
+ def to_zulu_occurrence_range_finish_time
315
+ if floating?
316
+ utc.advance(:hours => 12).to_datetime
317
+ else
318
+ to_zulu_time
319
+ end
320
+ end
321
+
322
+ def add_date_times_to(required_timezones) #:nodoc:
323
+ required_timezones.add_datetime(self, tzid) if has_local_timezone?
324
+ end
325
+
326
+ def start_of_day?
327
+ [hour, min, sec] == [0,0,0]
328
+ end
329
+
330
+ def for_occurrence(occurrence)
331
+ occurrence.to_ri_cal_date_time_value(timezone_finder)
332
+ end
333
+ end
334
+ end
335
+ end