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.
- data/History.txt +45 -0
- data/Manifest.txt +129 -0
- data/README.txt +394 -0
- data/Rakefile +31 -0
- data/bin/ri_cal +8 -0
- data/component_attributes/alarm.yml +10 -0
- data/component_attributes/calendar.yml +4 -0
- data/component_attributes/component_property_defs.yml +180 -0
- data/component_attributes/event.yml +45 -0
- data/component_attributes/freebusy.yml +16 -0
- data/component_attributes/journal.yml +35 -0
- data/component_attributes/timezone.yml +3 -0
- data/component_attributes/timezone_period.yml +11 -0
- data/component_attributes/todo.yml +46 -0
- data/copyrights.txt +1 -0
- data/docs/draft-ietf-calsify-2446bis-08.txt +7280 -0
- data/docs/draft-ietf-calsify-rfc2445bis-09.txt +10416 -0
- data/docs/incrementers.txt +7 -0
- data/docs/rfc2445.pdf +0 -0
- data/lib/ri_cal.rb +144 -0
- data/lib/ri_cal/component.rb +247 -0
- data/lib/ri_cal/component/alarm.rb +21 -0
- data/lib/ri_cal/component/calendar.rb +219 -0
- data/lib/ri_cal/component/event.rb +60 -0
- data/lib/ri_cal/component/freebusy.rb +18 -0
- data/lib/ri_cal/component/journal.rb +30 -0
- data/lib/ri_cal/component/t_z_info_timezone.rb +123 -0
- data/lib/ri_cal/component/timezone.rb +196 -0
- data/lib/ri_cal/component/timezone/daylight_period.rb +25 -0
- data/lib/ri_cal/component/timezone/standard_period.rb +23 -0
- data/lib/ri_cal/component/timezone/timezone_period.rb +53 -0
- data/lib/ri_cal/component/todo.rb +43 -0
- data/lib/ri_cal/core_extensions.rb +6 -0
- data/lib/ri_cal/core_extensions/array.rb +7 -0
- data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
- data/lib/ri_cal/core_extensions/date.rb +13 -0
- data/lib/ri_cal/core_extensions/date/conversions.rb +61 -0
- data/lib/ri_cal/core_extensions/date_time.rb +15 -0
- data/lib/ri_cal/core_extensions/date_time/conversions.rb +50 -0
- data/lib/ri_cal/core_extensions/object.rb +8 -0
- data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
- data/lib/ri_cal/core_extensions/string.rb +8 -0
- data/lib/ri_cal/core_extensions/string/conversions.rb +63 -0
- data/lib/ri_cal/core_extensions/time.rb +13 -0
- data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
- data/lib/ri_cal/core_extensions/time/conversions.rb +61 -0
- data/lib/ri_cal/core_extensions/time/tzid_access.rb +50 -0
- data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +88 -0
- data/lib/ri_cal/floating_timezone.rb +32 -0
- data/lib/ri_cal/invalid_property_value.rb +8 -0
- data/lib/ri_cal/invalid_timezone_identifer.rb +20 -0
- data/lib/ri_cal/occurrence_enumerator.rb +206 -0
- data/lib/ri_cal/occurrence_period.rb +17 -0
- data/lib/ri_cal/parser.rb +138 -0
- data/lib/ri_cal/properties/alarm.rb +390 -0
- data/lib/ri_cal/properties/calendar.rb +164 -0
- data/lib/ri_cal/properties/event.rb +1526 -0
- data/lib/ri_cal/properties/freebusy.rb +594 -0
- data/lib/ri_cal/properties/journal.rb +1240 -0
- data/lib/ri_cal/properties/timezone.rb +151 -0
- data/lib/ri_cal/properties/timezone_period.rb +416 -0
- data/lib/ri_cal/properties/todo.rb +1562 -0
- data/lib/ri_cal/property_value.rb +149 -0
- data/lib/ri_cal/property_value/array.rb +27 -0
- data/lib/ri_cal/property_value/cal_address.rb +11 -0
- data/lib/ri_cal/property_value/date.rb +175 -0
- data/lib/ri_cal/property_value/date_time.rb +335 -0
- data/lib/ri_cal/property_value/date_time/additive_methods.rb +44 -0
- data/lib/ri_cal/property_value/date_time/time_machine.rb +181 -0
- data/lib/ri_cal/property_value/date_time/timezone_support.rb +96 -0
- data/lib/ri_cal/property_value/duration.rb +110 -0
- data/lib/ri_cal/property_value/geo.rb +11 -0
- data/lib/ri_cal/property_value/integer.rb +12 -0
- data/lib/ri_cal/property_value/occurrence_list.rb +144 -0
- data/lib/ri_cal/property_value/period.rb +82 -0
- data/lib/ri_cal/property_value/recurrence_rule.rb +145 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +97 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +79 -0
- data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +148 -0
- data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +53 -0
- data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +31 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurence_incrementer.rb +793 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +131 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +60 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +33 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +49 -0
- data/lib/ri_cal/property_value/recurrence_rule/validations.rb +125 -0
- data/lib/ri_cal/property_value/text.rb +40 -0
- data/lib/ri_cal/property_value/uri.rb +11 -0
- data/lib/ri_cal/property_value/utc_offset.rb +33 -0
- data/lib/ri_cal/required_timezones.rb +55 -0
- data/ri_cal.gemspec +49 -0
- data/sample_ical_files/from_ical_dot_app/test1.ics +38 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +71 -0
- data/spec/ri_cal/component/alarm_spec.rb +12 -0
- data/spec/ri_cal/component/calendar_spec.rb +54 -0
- data/spec/ri_cal/component/event_spec.rb +601 -0
- data/spec/ri_cal/component/freebusy_spec.rb +12 -0
- data/spec/ri_cal/component/journal_spec.rb +37 -0
- data/spec/ri_cal/component/t_z_info_timezone_spec.rb +36 -0
- data/spec/ri_cal/component/timezone_spec.rb +218 -0
- data/spec/ri_cal/component/todo_spec.rb +112 -0
- data/spec/ri_cal/component_spec.rb +224 -0
- data/spec/ri_cal/core_extensions/string/conversions_spec.rb +78 -0
- data/spec/ri_cal/core_extensions/time/calculations_spec.rb +188 -0
- data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +45 -0
- data/spec/ri_cal/occurrence_enumerator_spec.rb +573 -0
- data/spec/ri_cal/parser_spec.rb +303 -0
- data/spec/ri_cal/property_value/date_spec.rb +53 -0
- data/spec/ri_cal/property_value/date_time_spec.rb +383 -0
- data/spec/ri_cal/property_value/duration_spec.rb +126 -0
- data/spec/ri_cal/property_value/occurrence_list_spec.rb +72 -0
- data/spec/ri_cal/property_value/period_spec.rb +49 -0
- data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +21 -0
- data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1814 -0
- data/spec/ri_cal/property_value/text_spec.rb +25 -0
- data/spec/ri_cal/property_value/utc_offset_spec.rb +48 -0
- data/spec/ri_cal/property_value_spec.rb +125 -0
- data/spec/ri_cal/required_timezones_spec.rb +67 -0
- data/spec/ri_cal_spec.rb +53 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +46 -0
- data/tasks/gem_loader/load_active_support.rb +3 -0
- data/tasks/gem_loader/load_tzinfo_gem.rb +2 -0
- data/tasks/ri_cal.rake +410 -0
- data/tasks/spec.rake +50 -0
- 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
|