demingfactor-ri_cal 0.9.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.
- checksums.yaml +7 -0
- data/History.txt +402 -0
- data/Manifest.txt +161 -0
- data/README.txt +410 -0
- data/Rakefile +69 -0
- data/VERSION +1 -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/component/alarm.rb +19 -0
- data/lib/ri_cal/component/calendar.rb +257 -0
- data/lib/ri_cal/component/event.rb +58 -0
- data/lib/ri_cal/component/freebusy.rb +16 -0
- data/lib/ri_cal/component/journal.rb +27 -0
- data/lib/ri_cal/component/non_standard.rb +33 -0
- data/lib/ri_cal/component/t_z_info_timezone.rb +153 -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 +76 -0
- data/lib/ri_cal/component/timezone.rb +197 -0
- data/lib/ri_cal/component/todo.rb +42 -0
- data/lib/ri_cal/component.rb +256 -0
- data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
- data/lib/ri_cal/core_extensions/array.rb +7 -0
- data/lib/ri_cal/core_extensions/date/conversions.rb +56 -0
- data/lib/ri_cal/core_extensions/date.rb +13 -0
- data/lib/ri_cal/core_extensions/date_time/conversions.rb +50 -0
- data/lib/ri_cal/core_extensions/date_time.rb +15 -0
- data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
- data/lib/ri_cal/core_extensions/object.rb +8 -0
- data/lib/ri_cal/core_extensions/string/conversions.rb +57 -0
- data/lib/ri_cal/core_extensions/string.rb +8 -0
- data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
- data/lib/ri_cal/core_extensions/time/conversions.rb +42 -0
- data/lib/ri_cal/core_extensions/time/tzid_access.rb +50 -0
- data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +55 -0
- data/lib/ri_cal/core_extensions/time.rb +14 -0
- data/lib/ri_cal/core_extensions.rb +11 -0
- data/lib/ri_cal/fast_date_time.rb +234 -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_identifier.rb +20 -0
- data/lib/ri_cal/occurrence_enumerator.rb +265 -0
- data/lib/ri_cal/occurrence_period.rb +17 -0
- data/lib/ri_cal/parser.rb +148 -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 +1523 -0
- data/lib/ri_cal/properties/freebusy.rb +593 -0
- data/lib/ri_cal/properties/journal.rb +1237 -0
- data/lib/ri_cal/properties/timezone.rb +150 -0
- data/lib/ri_cal/properties/timezone_period.rb +416 -0
- data/lib/ri_cal/properties/todo.rb +1559 -0
- data/lib/ri_cal/properties.rb +12 -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 +184 -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 +159 -0
- data/lib/ri_cal/property_value/date_time/timezone_support.rb +100 -0
- data/lib/ri_cal/property_value/date_time.rb +359 -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 +86 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +100 -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/occurrence_incrementer/by_day_incrementer.rb +86 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_hour_incrementer.rb +31 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_minute_incrementer.rb +32 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_month_incrementer.rb +52 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_monthday_incrementer.rb +31 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_numbered_day_incrementer.rb +38 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_second_incrementer.rb +32 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_weekno_incrementer.rb +69 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_yearday_incrementer.rb +31 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/daily_incrementer.rb +28 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/frequency_incrementer.rb +80 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/hourly_incrementer.rb +23 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/list_incrementer.rb +106 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/minutely_incrementer.rb +23 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/monthly_incrementer.rb +33 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/null_sub_cycle_incrementer.rb +43 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/secondly_incrementer.rb +28 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/weekly_incrementer.rb +37 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/yearly_incrementer.rb +57 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer.rb +135 -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 +64 -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 +53 -0
- data/lib/ri_cal/property_value/recurrence_rule/time_manipulation.rb +42 -0
- data/lib/ri_cal/property_value/recurrence_rule/validations.rb +125 -0
- data/lib/ri_cal/property_value/recurrence_rule.rb +154 -0
- data/lib/ri_cal/property_value/text.rb +44 -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/property_value/zulu_date_time.rb +34 -0
- data/lib/ri_cal/property_value.rb +159 -0
- data/lib/ri_cal/required_timezones.rb +55 -0
- data/lib/ri_cal.rb +187 -0
- data/parked_specs/ri_cal/claudio_a_bug_spec.rb +100 -0
- data/performance/empty_propval/subject.rb +43 -0
- data/performance/paris_eastern/subject.rb +90 -0
- data/performance/penultimate_weekday/subject.rb +15 -0
- data/performance/psm_big_enum/ical.ics +3171 -0
- data/performance/psm_big_enum/subject.rb +16 -0
- data/performance/utah_cycling/subject.rb +55 -0
- data/ri_cal.gemspec +244 -0
- data/script/benchmark_subject +23 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/profile_subject +29 -0
- data/script/txt2html +71 -0
- data/spec/ri_cal/bugreports_spec.rb +276 -0
- data/spec/ri_cal/component/alarm_spec.rb +12 -0
- data/spec/ri_cal/component/calendar_spec.rb +88 -0
- data/spec/ri_cal/component/event_spec.rb +735 -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 +60 -0
- data/spec/ri_cal/component/timezone_spec.rb +236 -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/fast_date_time_spec.rb +77 -0
- data/spec/ri_cal/inf_loop_spec.rb +78 -0
- data/spec/ri_cal/occurrence_enumerator_spec.rb +611 -0
- data/spec/ri_cal/parser_spec.rb +337 -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 +63 -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 +50 -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 +412 -0
- data/tasks/spec.rake +102 -0
- metadata +246 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
module RiCal
|
2
|
+
class Component
|
3
|
+
class Timezone
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
# A TimezonePeriod is a component of a timezone representing a period during which a particular offset from UTC is
|
7
|
+
# in effect.
|
8
|
+
#
|
9
|
+
# to see the property accessing methods for this class see the RiCal::Properties::TimezonePeriod module
|
10
|
+
class TimezonePeriod < Component
|
11
|
+
include Properties::TimezonePeriod
|
12
|
+
|
13
|
+
include OccurrenceEnumerator
|
14
|
+
|
15
|
+
def occurrence_cache #:nodoc:
|
16
|
+
@occurrence_cache ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def zone_identifier #:nodoc:
|
20
|
+
tzname.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def dtend #:nodoc:
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def exdate_property #:nodoc:
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def utc_total_offset #:nodoc:
|
32
|
+
tzoffsetto_property.to_seconds
|
33
|
+
end
|
34
|
+
|
35
|
+
def exrule_property #:nodoc:
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def last_before_utc(utc_time) #:nodoc:
|
40
|
+
last_before_local(utc_time + tzoffsetfrom_property)
|
41
|
+
end
|
42
|
+
|
43
|
+
def fill_cache(local_time)
|
44
|
+
if occurrence_cache.empty? || occurrence_cache.last.dtstart_property <= local_time
|
45
|
+
while true
|
46
|
+
occurrence = enumeration_instance.next_occurrence
|
47
|
+
break unless occurrence
|
48
|
+
occurrence = recurrence(occurrence)
|
49
|
+
occurrence_cache << occurrence
|
50
|
+
break if occurrence.dtstart_property > local_time
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def last_before_local(local_time) #:nodoc:
|
56
|
+
if recurs?
|
57
|
+
fill_cache(local_time)
|
58
|
+
cand_occurrence = nil
|
59
|
+
occurrence_cache.each do |occurrence|
|
60
|
+
return cand_occurrence if occurrence.dtstart_property > local_time
|
61
|
+
cand_occurrence = occurrence
|
62
|
+
end
|
63
|
+
return cand_occurrence
|
64
|
+
else
|
65
|
+
return self
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def enumeration_instance
|
70
|
+
@enumeration_instance ||= super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module RiCal
|
2
|
+
class Component
|
3
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
4
|
+
#
|
5
|
+
# An Timezone (VTIMEZONE) calendar component describes a timezone used within the calendar.
|
6
|
+
# A Timezone has two or more TimezonePeriod subcomponents which describe the transitions between
|
7
|
+
# standard and daylight saving time.
|
8
|
+
#
|
9
|
+
# to see the property accessing methods for this class see the RiCal::Properties::Timezone module
|
10
|
+
class Timezone < Component
|
11
|
+
|
12
|
+
autoload :TimezonePeriod, "ri_cal/component/timezone/timezone_period.rb"
|
13
|
+
autoload :StandardPeriod, "ri_cal/component/timezone/standard_period.rb"
|
14
|
+
autoload :DaylightPeriod, "ri_cal/component/timezone/daylight_period.rb"
|
15
|
+
|
16
|
+
include RiCal::Properties::Timezone
|
17
|
+
|
18
|
+
# The identifier of the timezone, e.g. "Europe/Paris".
|
19
|
+
def identifier
|
20
|
+
tzid
|
21
|
+
end
|
22
|
+
|
23
|
+
# An alias for identifier.
|
24
|
+
def name
|
25
|
+
# Don't use alias, as identifier gets overridden.
|
26
|
+
identifier
|
27
|
+
end
|
28
|
+
|
29
|
+
def rational_utc_offset(local) #:nodoc:
|
30
|
+
# 86400 is the number of seconds in a day
|
31
|
+
RiCal.RationalOffset[period_for_local(local, true).utc_total_offset]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the TimezonePeriod for the given UTC time. utc can either be a DateTime,
|
35
|
+
# Time or integer timestamp (Time.to_i). Any timezone information in utc is ignored (it is treated as a UTC time).
|
36
|
+
def period_for_utc(time)
|
37
|
+
last_period(last_before_utc(standard, time), last_before_utc(daylight, time))
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the set of TimezonePeriod instances that are valid for the given
|
41
|
+
# local time as an array. If you just want a single period, use
|
42
|
+
# period_for_local instead and specify how ambiguities should be resolved.
|
43
|
+
# Returns an empty array if no periods are found for the given time.
|
44
|
+
def periods_for_local(local)
|
45
|
+
local = local.to_ri_cal_date_time_value
|
46
|
+
candidate_standard = last_before_local(standard, local)
|
47
|
+
candidate_daylight = last_before_local(daylight, local)
|
48
|
+
if candidate_daylight && candidate_daylight.swallows_local?(local, candidate_standard)
|
49
|
+
[] # Invalid local time
|
50
|
+
elsif candidate_standard
|
51
|
+
if candidate_daylight
|
52
|
+
if candidate_daylight.dtstart > candidate_standard.dtstart
|
53
|
+
[candidate_daylight]
|
54
|
+
elsif candidate_standard.ambiguous_local?(local)
|
55
|
+
[candidate_daylight, candidate_standard]
|
56
|
+
else
|
57
|
+
[candidate_standard].compact
|
58
|
+
end
|
59
|
+
else
|
60
|
+
[candidate_standard].compact
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Returns the TimezonePeriod for the given local time. local can either be
|
67
|
+
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
68
|
+
# information in local is ignored (it is treated as a time in the current
|
69
|
+
# timezone).
|
70
|
+
#
|
71
|
+
# Warning: There are local times that have no equivalent UTC times (e.g.
|
72
|
+
# in the transition from standard time to daylight savings time). There are
|
73
|
+
# also local times that have more than one UTC equivalent (e.g. in the
|
74
|
+
# transition from daylight savings time to standard time).
|
75
|
+
#
|
76
|
+
# In the first case (no equivalent UTC time), a PeriodNotFound exception
|
77
|
+
# will be raised.
|
78
|
+
#
|
79
|
+
# In the second case (more than one equivalent UTC time), an AmbiguousTime
|
80
|
+
# exception will be raised unless the optional dst parameter or block
|
81
|
+
# handles the ambiguity.
|
82
|
+
#
|
83
|
+
# If the ambiguity is due to a transition from daylight savings time to
|
84
|
+
# standard time, the dst parameter can be used to select whether the
|
85
|
+
# daylight savings time or local time is used. For example,
|
86
|
+
#
|
87
|
+
# Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
|
88
|
+
#
|
89
|
+
# would raise an AmbiguousTime exception.
|
90
|
+
#
|
91
|
+
# Specifying dst=true would the daylight savings period from April to
|
92
|
+
# October 2004. Specifying dst=false would return the standard period
|
93
|
+
# from October 2004 to April 2005.
|
94
|
+
#
|
95
|
+
# If the dst parameter does not resolve the ambiguity, and a block is
|
96
|
+
# specified, it is called. The block must take a single parameter - an
|
97
|
+
# array of the periods that need to be resolved. The block can select and
|
98
|
+
# return a single period or return nil or an empty array
|
99
|
+
# to cause an AmbiguousTime exception to be raised.
|
100
|
+
#
|
101
|
+
# TODO: need to check for ambiguity
|
102
|
+
def period_for_local(local, dst=nil)
|
103
|
+
results = periods_for_local(local)
|
104
|
+
|
105
|
+
if results.empty?
|
106
|
+
raise TZInfo::PeriodNotFound
|
107
|
+
elsif results.size < 2
|
108
|
+
results.first
|
109
|
+
else
|
110
|
+
# ambiguous result try to resolve
|
111
|
+
|
112
|
+
unless dst.nil?
|
113
|
+
matches = results.find_all {|period| period.dst? == dst}
|
114
|
+
results = matches unless matches.empty?
|
115
|
+
end
|
116
|
+
|
117
|
+
if results.size < 2
|
118
|
+
results.first
|
119
|
+
else
|
120
|
+
# still ambiguous, try the block
|
121
|
+
|
122
|
+
if block_given?
|
123
|
+
results = yield results
|
124
|
+
end
|
125
|
+
|
126
|
+
if results.is_a?(TimezonePeriod)
|
127
|
+
results
|
128
|
+
elsif results && results.size == 1
|
129
|
+
results.first
|
130
|
+
else
|
131
|
+
raise TZInfo::AmbiguousTime, "#{local} is an ambiguous local time."
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# convert time from utc time to this time zone
|
138
|
+
def utc_to_local(time)
|
139
|
+
time = time.to_ri_cal_date_time_value
|
140
|
+
converted = time + period_for_utc(time).tzoffsetto_property
|
141
|
+
converted.tzid = identifier
|
142
|
+
converted
|
143
|
+
end
|
144
|
+
|
145
|
+
# convert time from this time zone to utc time
|
146
|
+
def local_to_utc(time)
|
147
|
+
time = time.to_ri_cal_date_time_value
|
148
|
+
period = period_for_local(time)
|
149
|
+
converted = time - period.tzoffsetto_property
|
150
|
+
converted.tzid = "UTC"
|
151
|
+
converted
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.entity_name #:nodoc:
|
156
|
+
"VTIMEZONE"
|
157
|
+
end
|
158
|
+
|
159
|
+
def standard #:nodoc:
|
160
|
+
@subcomponents["STANDARD"]
|
161
|
+
end
|
162
|
+
|
163
|
+
def daylight #:nodoc:
|
164
|
+
@subcomponents["DAYLIGHT"]
|
165
|
+
end
|
166
|
+
|
167
|
+
def last_period(standard, daylight) #:nodoc:
|
168
|
+
if standard
|
169
|
+
if daylight
|
170
|
+
standard.dtstart > daylight.dtstart ? standard : daylight
|
171
|
+
else
|
172
|
+
standard
|
173
|
+
end
|
174
|
+
else
|
175
|
+
daylight
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def last_before_utc(period_array, time) #:nodoc:
|
180
|
+
candidates = period_array.map {|period|
|
181
|
+
period.last_before_utc(time)
|
182
|
+
}
|
183
|
+
result = candidates.max {|a, b| a.dtstart_property <=> b.dtstart_property}
|
184
|
+
result
|
185
|
+
end
|
186
|
+
|
187
|
+
def last_before_local(period_array, time) #:nodoc:
|
188
|
+
candidates = period_array.map {|period|
|
189
|
+
period.last_before_local(time)
|
190
|
+
}
|
191
|
+
result = candidates.max {|a, b| a.dtstart_property <=> b.dtstart_property}
|
192
|
+
result
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RiCal
|
2
|
+
class Component
|
3
|
+
#- ©2009 Rick DeNatale
|
4
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
# A Todo (VTODO) calendar component groups properties describing a to-do
|
7
|
+
# Todos may have multiple occurrences
|
8
|
+
#
|
9
|
+
# Todos may also contain one or more ALARM subcomponents
|
10
|
+
# to see the property accessing methods for this class see the RiCal::Properties::Todo module
|
11
|
+
# to see the methods for enumerating occurrences of recurring to-dos see the RiCal::OccurrenceEnumerator module
|
12
|
+
class Todo < Component
|
13
|
+
include Properties::Todo
|
14
|
+
include OccurrenceEnumerator
|
15
|
+
|
16
|
+
def self.entity_name #:nodoc:
|
17
|
+
"VTODO"
|
18
|
+
end
|
19
|
+
|
20
|
+
def subcomponent_class #:nodoc:
|
21
|
+
{:alarm => Alarm }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return a date_time representing the time at which the todo should start
|
25
|
+
def start_time
|
26
|
+
dtstart_property ? dtstart.to_datetime : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return a date_time representing the time at which the todo is due
|
30
|
+
def finish_time
|
31
|
+
if due
|
32
|
+
due_property.to_finish_time
|
33
|
+
elsif duration_property && dtstart_property
|
34
|
+
(dtstart_property + duration_property).to_finish_time
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,256 @@
|
|
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
|
+
|
6
|
+
autoload :Alarm, "ri_cal/component/alarm.rb"
|
7
|
+
autoload :Calendar, "ri_cal/component/calendar.rb"
|
8
|
+
autoload :Event, "ri_cal/component/event.rb"
|
9
|
+
autoload :Freebusy, "ri_cal/component/freebusy.rb"
|
10
|
+
autoload :Journal, "ri_cal/component/journal.rb"
|
11
|
+
autoload :NonStandard, "ri_cal/component/non_standard.rb"
|
12
|
+
autoload :TZInfoTimezone, "ri_cal/component/t_z_info_timezone.rb"
|
13
|
+
autoload :Timezone, "ri_cal/component/timezone.rb"
|
14
|
+
autoload :Todo, "ri_cal/component/todo.rb"
|
15
|
+
|
16
|
+
class ComponentBuilder #:nodoc:
|
17
|
+
def initialize(component)
|
18
|
+
@component = component
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(selector, *args, &init_block) #:nodoc:
|
22
|
+
if(sub_comp_class = @component.subcomponent_class[selector])
|
23
|
+
if init_block
|
24
|
+
sub_comp = sub_comp_class.new(@component)
|
25
|
+
if init_block.arity == 1
|
26
|
+
yield ComponentBuilder.new(sub_comp)
|
27
|
+
else
|
28
|
+
ComponentBuilder.new(sub_comp).instance_eval(&init_block)
|
29
|
+
end
|
30
|
+
self.add_subcomponent(sub_comp)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
sel = selector.to_s
|
34
|
+
sel = "#{sel}=" unless /(^(add_)|(remove_))|(=$)/ =~ sel
|
35
|
+
if @component.respond_to?(sel)
|
36
|
+
@component.send(sel, *args)
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :imported #:nodoc:
|
45
|
+
|
46
|
+
def initialize(parent=nil, entity_name = nil, &init_block) #:nodoc:
|
47
|
+
@parent = parent
|
48
|
+
if block_given?
|
49
|
+
if init_block.arity == 1
|
50
|
+
init_block.call(ComponentBuilder.new(self))
|
51
|
+
else
|
52
|
+
ComponentBuilder.new(self).instance_eval(&init_block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def default_tzid #:nodoc:
|
58
|
+
if @parent
|
59
|
+
@parent.default_tzid
|
60
|
+
else
|
61
|
+
PropertyValue::DateTime.default_tzid
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_timezone(identifier) #:nodoc:
|
66
|
+
if @parent
|
67
|
+
@parent.find_timezone(identifier)
|
68
|
+
else
|
69
|
+
begin
|
70
|
+
Calendar::TZInfoWrapper.new(TZInfo::Timezone.get(identifier), self)
|
71
|
+
rescue ::TZInfo::InvalidTimezoneIdentifier => ex
|
72
|
+
raise RiCal::InvalidTimezoneIdentifier.invalid_tzinfo_identifier(identifier)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def tz_info_source?
|
78
|
+
if @parent
|
79
|
+
@parent.tz_info_source?
|
80
|
+
else
|
81
|
+
true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def time_zone_for(ruby_object) #:nodoc:
|
86
|
+
@parent.time_zone_for(ruby_object) #:nodoc:
|
87
|
+
end
|
88
|
+
|
89
|
+
def subcomponent_class #:nodoc:
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.from_parser(parser, parent, entity_name) #:nodoc:
|
94
|
+
entity = self.new(parent, entity_name)
|
95
|
+
entity.imported = true
|
96
|
+
line = parser.next_separated_line
|
97
|
+
while parser.still_in(entity_name, line)
|
98
|
+
entity.process_line(parser, line)
|
99
|
+
line = parser.next_separated_line
|
100
|
+
end
|
101
|
+
entity
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.parse(io) #:nodoc:
|
105
|
+
Parser.new(io).parse
|
106
|
+
end
|
107
|
+
|
108
|
+
def imported? #:nodoc:
|
109
|
+
imported
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.parse_string(string) #:nodoc:
|
113
|
+
parse(StringIO.new(string))
|
114
|
+
end
|
115
|
+
|
116
|
+
def subcomponents #:nodoc:
|
117
|
+
@subcomponents ||= Hash.new {|h, k| h[k] = []}
|
118
|
+
end
|
119
|
+
|
120
|
+
def entity_name #:nodoc:
|
121
|
+
self.class.entity_name
|
122
|
+
end
|
123
|
+
|
124
|
+
# return an array of Alarm components within this component :nodoc:
|
125
|
+
# Alarms may be contained within Events, and Todos
|
126
|
+
def alarms
|
127
|
+
subcomponents["VALARM"]
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_subcomponent(component) #:nodoc:
|
131
|
+
subcomponents[component.entity_name] << component
|
132
|
+
end
|
133
|
+
|
134
|
+
def parse_subcomponent(parser, line) #:nodoc:
|
135
|
+
subcomponents[line[:value]] << parser.parse_one(line, self)
|
136
|
+
end
|
137
|
+
|
138
|
+
def process_line(parser, line) #:nodoc:
|
139
|
+
if line[:name] == "BEGIN"
|
140
|
+
parse_subcomponent(parser, line)
|
141
|
+
else
|
142
|
+
setter = self.class.property_parser[line[:name]]
|
143
|
+
if setter
|
144
|
+
send(setter, line)
|
145
|
+
else
|
146
|
+
self.add_x_property(line[:name], PropertyValue::Text.new(self, line))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# return a hash of any extended properties, (i.e. those with a property name starting with "X-"
|
152
|
+
# representing an extension to the RFC 2445 specification)
|
153
|
+
def x_properties
|
154
|
+
@x_properties ||= Hash.new {|h,k| h[k] = []}
|
155
|
+
end
|
156
|
+
|
157
|
+
# Add a n extended property
|
158
|
+
def add_x_property(name, prop, debug=false)
|
159
|
+
x_properties[name.gsub("_","-").upcase] << prop.to_ri_cal_text_property
|
160
|
+
end
|
161
|
+
|
162
|
+
def method_missing(selector, *args, &b) #:nodoc:
|
163
|
+
xprop_candidate = selector.to_s
|
164
|
+
if (match = /^(x_.+)(=?)$/.match(xprop_candidate))
|
165
|
+
x_property_key = match[1].gsub('_','-').upcase
|
166
|
+
if match[2] == "="
|
167
|
+
args.each do |val|
|
168
|
+
add_x_property(x_property_key, val)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
x_properties[x_property_key].map {|property| property.value}
|
172
|
+
end
|
173
|
+
else
|
174
|
+
super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Predicate to determine if the component is valid according to RFC 2445
|
179
|
+
def valid?
|
180
|
+
!mutual_exclusion_violation
|
181
|
+
end
|
182
|
+
|
183
|
+
def initialize_copy(original) #:nodoc:
|
184
|
+
end
|
185
|
+
|
186
|
+
def prop_string(prop_name, *properties) #:nodoc:
|
187
|
+
properties = properties.flatten.compact
|
188
|
+
if properties && !properties.empty?
|
189
|
+
properties.map {|prop| "#{prop_name}#{prop.to_s}"}.join("\n")
|
190
|
+
else
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def add_property_date_times_to(required_timezones, property) #:nodoc:
|
196
|
+
if property
|
197
|
+
if Array === property
|
198
|
+
property.each do |prop|
|
199
|
+
prop.add_date_times_to(required_timezones)
|
200
|
+
end
|
201
|
+
else
|
202
|
+
property.add_date_times_to(required_timezones)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def export_prop_to(export_stream, name, prop) #:nodoc:
|
208
|
+
if prop
|
209
|
+
string = prop_string(name, prop)
|
210
|
+
export_stream.puts(string) if string
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def export_x_properties_to(export_stream) #:nodoc:
|
215
|
+
x_properties.each do |name, props|
|
216
|
+
props.each do | prop |
|
217
|
+
export_stream.puts("#{name}:#{prop}")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def export_subcomponent_to(export_stream, subcomponent) #:nodoc:
|
223
|
+
subcomponent.each do |component|
|
224
|
+
component.export_to(export_stream)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# return a string containing the rfc2445 format of the component
|
229
|
+
def to_s
|
230
|
+
io = StringIO.new
|
231
|
+
export_to(io)
|
232
|
+
io.string
|
233
|
+
end
|
234
|
+
|
235
|
+
# Export this component to an export stream
|
236
|
+
def export_to(export_stream)
|
237
|
+
export_stream.puts("BEGIN:#{entity_name}")
|
238
|
+
export_properties_to(export_stream)
|
239
|
+
export_x_properties_to(export_stream)
|
240
|
+
subcomponents.values.each do |sub|
|
241
|
+
export_subcomponent_to(export_stream, sub)
|
242
|
+
end
|
243
|
+
export_stream.puts("END:#{entity_name}")
|
244
|
+
end
|
245
|
+
|
246
|
+
# Export this single component as an iCalendar component containing only this component and
|
247
|
+
# any required additional components (i.e. VTIMEZONES referenced from this component)
|
248
|
+
# if stream is nil (the default) then this method will return a string,
|
249
|
+
# otherwise stream should be an IO to which the iCalendar file contents will be written
|
250
|
+
def export(stream=nil)
|
251
|
+
wrapper_calendar = Calendar.new
|
252
|
+
wrapper_calendar.add_subcomponent(self)
|
253
|
+
wrapper_calendar.export(stream)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RiCal
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module Array #:nodoc:
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
module Conversions
|
8
|
+
# return the concatenation of the elements representation in rfc 2445 format
|
9
|
+
def to_rfc2445_string # :doc:
|
10
|
+
join(",")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RiCal
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module Date #:nodoc:
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
module Conversions #:nodoc:
|
8
|
+
# Return an RiCal::PropertyValue::DateTime representing the receiver
|
9
|
+
def to_ri_cal_date_time_value(timezone_finder = nil)
|
10
|
+
RiCal::PropertyValue::DateTime.new(timezone_finder, :value => self)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return an RiCal::PropertyValue::Date representing the receiver
|
14
|
+
def to_ri_cal_date_value(timezone_finder = nil)
|
15
|
+
RiCal::PropertyValue::Date.new(timezone_finder, :value => self)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :to_ri_cal_date_or_date_time_value, :to_ri_cal_date_value
|
19
|
+
alias_method :to_ri_cal_occurrence_list_value, :to_ri_cal_date_value
|
20
|
+
|
21
|
+
# Return the natural ri_cal_property for this object
|
22
|
+
def to_ri_cal_property_value(timezone_finder = nil)
|
23
|
+
to_ri_cal_date_value(timezone_finder)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_overlap_range_start
|
27
|
+
to_datetime
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_overlap_range_end
|
31
|
+
to_ri_cal_date_time_value.end_of_day.to_datetime
|
32
|
+
end
|
33
|
+
|
34
|
+
unless Date.instance_methods.map {|selector| selector.to_sym}.include?(:to_date)
|
35
|
+
# A method to keep Time, Date and DateTime instances interchangeable on conversions.
|
36
|
+
# In this case, it simply returns +self+.
|
37
|
+
def to_date
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
unless Date.instance_methods.map {|selector| selector.to_sym}.include?(:to_datetime)
|
42
|
+
# Converts a Date instance to a DateTime, where the time is set to the beginning of the day
|
43
|
+
# and UTC offset is set to 0.
|
44
|
+
#
|
45
|
+
# ==== Examples
|
46
|
+
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
|
47
|
+
#
|
48
|
+
# date.to_datetime # => Sat, 10 Nov 2007 00:00:00 0000
|
49
|
+
def to_datetime
|
50
|
+
::DateTime.civil(year, month, day, 0, 0, 0, 0)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "ri_cal/core_extensions/date/conversions.rb"
|
2
|
+
require "ri_cal/core_extensions/time/week_day_predicates.rb"
|
3
|
+
require "ri_cal/core_extensions/time/calculations.rb"
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
class Date #:nodoc:
|
7
|
+
#- ©2009 Rick DeNatale
|
8
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
9
|
+
#
|
10
|
+
include RiCal::CoreExtensions::Time::WeekDayPredicates
|
11
|
+
include RiCal::CoreExtensions::Time::Calculations
|
12
|
+
include RiCal::CoreExtensions::Date::Conversions
|
13
|
+
end
|