micahwedemeyer-ri_cal 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +2 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +21 -0
- data/History.txt +402 -0
- data/Manifest.txt +161 -0
- data/README.txt +410 -0
- data/Rakefile +66 -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 +145 -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/micahwedemeyer-ri_cal.gemspec +227 -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/sample_ical_files/from_ical_dot_app/test1.ics +38 -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 +287 -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
- data/website/images/rubytrends.png +0 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +159 -0
- metadata +307 -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
|