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,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