ri_cal 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/Manifest.txt +4 -0
- data/Rakefile +3 -2
- data/lib/ri_cal.rb +33 -2
- data/lib/ri_cal/component/calendar.rb +2 -1
- data/lib/ri_cal/component/timezone.rb +2 -1
- data/lib/ri_cal/component/timezone/timezone_period.rb +31 -6
- data/lib/ri_cal/component/todo.rb +1 -0
- data/lib/ri_cal/core_extensions/time/conversions.rb +2 -1
- data/lib/ri_cal/floating_timezone.rb +1 -1
- data/lib/ri_cal/occurrence_enumerator.rb +82 -47
- data/lib/ri_cal/property_value/date_time.rb +1 -0
- data/lib/ri_cal/property_value/date_time/time_machine.rb +5 -5
- data/lib/ri_cal/property_value/date_time/timezone_support.rb +9 -5
- data/profiling/ical_files/profile3.ics +3171 -0
- data/profiling/profile1.rb +97 -0
- data/profiling/profile2.rb +65 -0
- data/profiling/profile3.rb +31 -0
- data/ri_cal.gemspec +7 -4
- data/spec/ri_cal/component/timezone_spec.rb +95 -77
- data/spec/ri_cal/property_value/date_time_spec.rb +1 -1
- metadata +17 -3
data/History.txt
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
=== 0.5.3 - 1 June, 2009
|
2
|
+
* Improved performance of time zone enumeration, TimeZonePeriod now caches occurrences
|
3
|
+
* Added a profiling directory which contains ruby programs which benchmark and/or profile performance
|
1
4
|
=== 0.5.2
|
2
5
|
* Fixed http://rick_denatale.lighthouseapp.com/projects/30941/tickets/11
|
3
6
|
Export folding is not UTF-8 Safe
|
data/Manifest.txt
CHANGED
@@ -89,6 +89,10 @@ lib/ri_cal/property_value/text.rb
|
|
89
89
|
lib/ri_cal/property_value/uri.rb
|
90
90
|
lib/ri_cal/property_value/utc_offset.rb
|
91
91
|
lib/ri_cal/required_timezones.rb
|
92
|
+
profiling/ical_files/profile3.ics
|
93
|
+
profiling/profile1.rb
|
94
|
+
profiling/profile2.rb
|
95
|
+
profiling/profile3.rb
|
92
96
|
ri_cal.gemspec
|
93
97
|
sample_ical_files/from_ical_dot_app/test1.ics
|
94
98
|
script/console
|
data/Rakefile
CHANGED
@@ -10,12 +10,13 @@ $hoe = Hoe.new('ri_cal', RiCal::VERSION) do |p|
|
|
10
10
|
p.developer('author=Rick DeNatale', 'rick.denatale@gmail.com')
|
11
11
|
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
12
12
|
# p.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
13
|
-
p.rubyforge_name = '
|
13
|
+
p.rubyforge_name = 'ri-cal'
|
14
14
|
# p.extra_deps = [
|
15
15
|
# ['tzinfo','>= 2.0.2'],
|
16
16
|
# ]
|
17
17
|
p.extra_dev_deps = [
|
18
|
-
['newgem', ">= #{::Newgem::VERSION}"]
|
18
|
+
['newgem', ">= #{::Newgem::VERSION}"],
|
19
|
+
'ruby-prof'
|
19
20
|
]
|
20
21
|
|
21
22
|
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
data/lib/ri_cal.rb
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
# and building calendars and calendar components.
|
5
5
|
module RiCal
|
6
6
|
|
7
|
+
require 'stringio'
|
8
|
+
require 'rational'
|
9
|
+
|
7
10
|
my_dir = File.dirname(__FILE__)
|
8
11
|
|
9
12
|
autoload :Component, "#{my_dir}/ri_cal/component.rb"
|
@@ -11,7 +14,7 @@ module RiCal
|
|
11
14
|
autoload :OccurrenceEnumerator, "#{my_dir}/ri_cal/occurrence_enumerator.rb"
|
12
15
|
|
13
16
|
# :stopdoc:
|
14
|
-
VERSION = '0.5.
|
17
|
+
VERSION = '0.5.3'
|
15
18
|
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
16
19
|
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
17
20
|
|
@@ -137,8 +140,36 @@ module RiCal
|
|
137
140
|
def self.Todo(&init_block)
|
138
141
|
Component::Todo.new(&init_block)
|
139
142
|
end
|
143
|
+
|
144
|
+
def self.ro_calls=(value)
|
145
|
+
@ro_calls = value
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.ro_calls
|
149
|
+
@ro_calls ||= 0
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.ro_misses=(value)
|
153
|
+
@ro_misses = value
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.ro_misses
|
157
|
+
@ro_misses ||= 0
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.RationalOffset
|
161
|
+
self.ro_calls += 1
|
162
|
+
@rational_offset ||= Hash.new {|h, seconds|
|
163
|
+
self.ro_misses += 1
|
164
|
+
h[seconds] = Rational(seconds, 86400)}
|
165
|
+
end
|
166
|
+
|
140
167
|
end # module RiCal
|
141
168
|
|
142
|
-
|
169
|
+
(-12..12).each do |hour_offset|
|
170
|
+
RiCal.RationalOffset[hour_offset * 86400]
|
171
|
+
end
|
172
|
+
|
143
173
|
|
174
|
+
RiCal.require_all_libs_relative_to(__FILE__)
|
144
175
|
# EOF
|
@@ -140,7 +140,8 @@ module RiCal
|
|
140
140
|
|
141
141
|
|
142
142
|
def rational_utc_offset(local)
|
143
|
-
|
143
|
+
# 86400 is the number of seconds in a day
|
144
|
+
RiCal.RationalOffset[tzinfo.period_for_local(local, true).utc_total_offset]
|
144
145
|
end
|
145
146
|
|
146
147
|
end
|
@@ -24,7 +24,8 @@ module RiCal
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def rational_utc_offset(local) #:nodoc:
|
27
|
-
|
27
|
+
# 86400 is the number of seconds in a day
|
28
|
+
RiCal.RationalOffset[period_for_local(local, true).utc_total_offset]
|
28
29
|
end
|
29
30
|
|
30
31
|
# Returns the TimezonePeriod for the given UTC time. utc can either be a DateTime,
|
@@ -14,6 +14,10 @@ module RiCal
|
|
14
14
|
|
15
15
|
include OccurrenceEnumerator
|
16
16
|
|
17
|
+
def occurrence_cache #:nodoc:
|
18
|
+
@occurrence_cache ||= []
|
19
|
+
end
|
20
|
+
|
17
21
|
def zone_identifier #:nodoc:
|
18
22
|
tzname.first
|
19
23
|
end
|
@@ -25,7 +29,7 @@ module RiCal
|
|
25
29
|
def exdate_property #:nodoc:
|
26
30
|
nil
|
27
31
|
end
|
28
|
-
|
32
|
+
|
29
33
|
def utc_total_offset #:nodoc:
|
30
34
|
tzoffsetto_property.to_seconds
|
31
35
|
end
|
@@ -38,13 +42,34 @@ module RiCal
|
|
38
42
|
last_before_local(utc_time + tzoffsetfrom_property)
|
39
43
|
end
|
40
44
|
|
45
|
+
def fill_cache(local_time)
|
46
|
+
if occurrence_cache.empty? || occurrence_cache.last.dtstart_property <= local_time
|
47
|
+
while true
|
48
|
+
occurrence = enumeration_instance.next_occurrence
|
49
|
+
break unless occurrence
|
50
|
+
occurrence = recurrence(occurrence)
|
51
|
+
occurrence_cache << occurrence
|
52
|
+
break if occurrence.dtstart_property > local_time
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
41
57
|
def last_before_local(local_time) #:nodoc:
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
58
|
+
if recurs?
|
59
|
+
fill_cache(local_time)
|
60
|
+
cand_occurrence = nil
|
61
|
+
occurrence_cache.each do |occurrence|
|
62
|
+
return cand_occurrence if occurrence.dtstart_property > local_time
|
63
|
+
cand_occurrence = occurrence
|
64
|
+
end
|
65
|
+
return cand_occurrence
|
66
|
+
else
|
67
|
+
return self
|
46
68
|
end
|
47
|
-
|
69
|
+
end
|
70
|
+
|
71
|
+
def enumeration_instance
|
72
|
+
@enumeration_instance ||= super
|
48
73
|
end
|
49
74
|
end
|
50
75
|
end
|
@@ -52,7 +52,8 @@ module RiCal
|
|
52
52
|
# your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
|
53
53
|
# your_time.to_datetime # => Tue, 13 Jan 2009 13:13:03 -0500
|
54
54
|
def to_datetime
|
55
|
-
|
55
|
+
# 86400 is the number of seconds in a day
|
56
|
+
::DateTime.civil(year, month, day, hour, min, sec, RiCal.RationalOffset[utc_offset])
|
56
57
|
end
|
57
58
|
end
|
58
59
|
end
|
@@ -5,7 +5,7 @@ module RiCal
|
|
5
5
|
# OccurrenceEnumerator provides common methods for CalendarComponents that support recurrence
|
6
6
|
# i.e. Event, Journal, Todo, and TimezonePeriod
|
7
7
|
module OccurrenceEnumerator
|
8
|
-
|
8
|
+
|
9
9
|
include Enumerable
|
10
10
|
|
11
11
|
def default_duration # :nodoc:
|
@@ -20,17 +20,17 @@ module RiCal
|
|
20
20
|
def self.next_occurrence
|
21
21
|
nil
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def self.bounded?
|
25
25
|
true
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def self.empty?
|
29
29
|
true
|
30
30
|
end
|
31
31
|
end
|
32
|
-
|
33
|
-
# OccurrenceMerger takes multiple recurrence rules and enumerates the combination in sequence.
|
32
|
+
|
33
|
+
# OccurrenceMerger takes multiple recurrence rules and enumerates the combination in sequence.
|
34
34
|
class OccurrenceMerger # :nodoc:
|
35
35
|
def self.for(component, rules)
|
36
36
|
if rules.nil? || rules.empty?
|
@@ -41,20 +41,20 @@ module RiCal
|
|
41
41
|
new(component, rules)
|
42
42
|
end
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
attr_accessor :enumerators, :nexts
|
46
|
-
|
46
|
+
|
47
47
|
def initialize(component, rules)
|
48
48
|
self.enumerators = rules.map {|rrule| rrule.enumerator(component)}
|
49
49
|
@bounded = enumerators.all? {|enumerator| enumerator.bounded?}
|
50
50
|
@empty = enumerators.all? {|enumerator| enumerator.empty?}
|
51
51
|
self.nexts = @enumerators.map {|enumerator| enumerator.next_occurrence}
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
def empty?
|
55
55
|
@empty
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
# return the earliest of each of the enumerators next occurrences
|
59
59
|
def next_occurrence
|
60
60
|
result = nexts.compact.sort.first
|
@@ -63,25 +63,22 @@ module RiCal
|
|
63
63
|
end
|
64
64
|
result
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def bounded?
|
68
68
|
@bounded
|
69
69
|
end
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
# EnumerationInstance holds the values needed during the enumeration of occurrences for a component.
|
73
73
|
class EnumerationInstance # :nodoc:
|
74
74
|
include Enumerable
|
75
|
-
|
76
|
-
def initialize(component
|
75
|
+
|
76
|
+
def initialize(component)
|
77
77
|
@component = component
|
78
|
-
@start = options[:starting]
|
79
|
-
@cutoff = options[:before]
|
80
|
-
@count = options[:count]
|
81
78
|
@rrules = OccurrenceMerger.for(@component, [@component.rrule_property, @component.rdate_property].flatten.compact)
|
82
79
|
@exrules = OccurrenceMerger.for(@component, [@component.exrule_property, @component.exdate_property].flatten.compact)
|
83
80
|
end
|
84
|
-
|
81
|
+
|
85
82
|
# return the next exclusion which starts at the same time or after the start time of the occurrence
|
86
83
|
# return nil if this exhausts the exclusion rules
|
87
84
|
def exclusion_for(occurrence)
|
@@ -96,48 +93,76 @@ module RiCal
|
|
96
93
|
def exclusion_match?(occurrence, exclusion)
|
97
94
|
exclusion && (occurrence.dtstart == exclusion.dtstart)
|
98
95
|
end
|
99
|
-
|
96
|
+
|
100
97
|
# Also exclude occurrences before the :starting date_time
|
101
|
-
def
|
102
|
-
|
103
|
-
|
98
|
+
def before_start?(occurrence)
|
99
|
+
(@start && occurrence.dtstart.to_datetime < @start)
|
100
|
+
end
|
101
|
+
|
102
|
+
def next_occurrence
|
103
|
+
@yielded ||= 0
|
104
|
+
@next_exclusion ||= @exrules.next_occurrence
|
105
|
+
occurrence = nil
|
106
|
+
|
107
|
+
until occurrence
|
108
|
+
if (occurrence = @rrules.next_occurrence)
|
109
|
+
if exclusion_match?(occurrence, exclusion_for(occurrence))
|
110
|
+
occurrence = nil # Look for the next one
|
111
|
+
end
|
112
|
+
else
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
occurrence
|
117
|
+
end
|
118
|
+
|
119
|
+
def options_stop(occurrence)
|
120
|
+
occurrence != :excluded &&
|
121
|
+
(@cutoff && occurrence.dtstart.to_datetime >= @cutoff) || (@count && @yielded >= @count)
|
104
122
|
end
|
105
|
-
|
123
|
+
|
124
|
+
|
106
125
|
# yield each occurrence to a block
|
107
|
-
# some components may be open-ended, e.g. have no COUNT or DTEND
|
108
|
-
def each
|
126
|
+
# some components may be open-ended, e.g. have no COUNT or DTEND
|
127
|
+
def each(options = nil)
|
128
|
+
process_options(options) if options
|
109
129
|
if @rrules.empty?
|
110
130
|
yield @component
|
111
131
|
else
|
112
|
-
occurrence =
|
113
|
-
yielded = 0
|
114
|
-
@next_exclusion = @exrules.next_occurrence
|
132
|
+
occurrence = next_occurrence
|
115
133
|
while (occurrence)
|
116
|
-
if (
|
134
|
+
if options_stop(occurrence)
|
117
135
|
occurrence = nil
|
118
136
|
else
|
119
|
-
unless
|
120
|
-
yielded += 1
|
137
|
+
unless before_start?(occurrence)
|
138
|
+
@yielded += 1
|
121
139
|
yield @component.recurrence(occurrence)
|
122
140
|
end
|
123
|
-
occurrence =
|
141
|
+
occurrence = next_occurrence
|
124
142
|
end
|
125
143
|
end
|
126
144
|
end
|
127
145
|
end
|
128
|
-
|
146
|
+
|
129
147
|
def bounded?
|
130
148
|
@rrules.bounded? || @count || @cutoff
|
131
149
|
end
|
132
|
-
|
133
|
-
def
|
150
|
+
|
151
|
+
def process_options(options)
|
152
|
+
@start = options[:starting]
|
153
|
+
@cutoff = options[:before]
|
154
|
+
@count = options[:count]
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_a(options = {})
|
158
|
+
process_options(options)
|
134
159
|
raise ArgumentError.new("This component is unbounded, cannot produce an array of occurrences!") unless bounded?
|
135
|
-
super
|
160
|
+
super()
|
136
161
|
end
|
137
|
-
|
162
|
+
|
138
163
|
alias_method :entries, :to_a
|
139
164
|
end
|
140
|
-
|
165
|
+
|
141
166
|
# return an array of occurrences according to the options parameter. If a component is not bounded, and
|
142
167
|
# the number of occurrences to be returned is not constrained by either the :before, or :count options
|
143
168
|
# an ArgumentError will be raised.
|
@@ -148,22 +173,27 @@ module RiCal
|
|
148
173
|
#
|
149
174
|
# parameter options:
|
150
175
|
# * :starting:: a Date, Time, or DateTime, no occurrences starting before this argument will be returned
|
151
|
-
# * :before:: a Date, Time, or DateTime, no occurrences starting on or after this argument will be returned.
|
176
|
+
# * :before:: a Date, Time, or DateTime, no occurrences starting on or after this argument will be returned.
|
152
177
|
# * :count:: an integer which limits the number of occurrences returned.
|
153
178
|
def occurrences(options={})
|
154
|
-
|
179
|
+
enumeration_instance.to_a(options)
|
155
180
|
end
|
156
|
-
|
181
|
+
|
182
|
+
# TODO: Thread safe?
|
183
|
+
def enumeration_instance #:nodoc:
|
184
|
+
EnumerationInstance.new(self)
|
185
|
+
end
|
186
|
+
|
157
187
|
# execute the block for each occurrence
|
158
188
|
def each(&block) # :yields: Component
|
159
|
-
|
189
|
+
enumeration_instance.each(&block)
|
160
190
|
end
|
161
|
-
|
191
|
+
|
162
192
|
# A predicate which determines whether the component has a bounded set of occurrences
|
163
193
|
def bounded?
|
164
|
-
|
194
|
+
enumeration_instance.bounded?
|
165
195
|
end
|
166
|
-
|
196
|
+
|
167
197
|
# Return a array whose first element is a UTC DateTime representing the start of the first
|
168
198
|
# occurrence, and whose second element is a UTC DateTime representing the end of the last
|
169
199
|
# occurrence.
|
@@ -182,7 +212,7 @@ module RiCal
|
|
182
212
|
end
|
183
213
|
[first.zulu_occurrence_range_start_time, last ? last.zulu_occurrence_range_finish_time : nil]
|
184
214
|
end
|
185
|
-
|
215
|
+
|
186
216
|
def set_occurrence_properties!(occurrence) # :nodoc:
|
187
217
|
occurrence_end = occurrence.dtend
|
188
218
|
occurrence_start = occurrence.dtstart
|
@@ -196,11 +226,16 @@ module RiCal
|
|
196
226
|
end
|
197
227
|
@dtstart_property = dtstart_property.for_occurrence(occurrence_start)
|
198
228
|
@dtend_property = dtend_property.for_occurrence(occurrence_end) if @dtend_property
|
199
|
-
self
|
229
|
+
self
|
200
230
|
end
|
201
|
-
|
231
|
+
|
202
232
|
def recurrence(occurrence) # :nodoc:
|
203
233
|
result = self.dup.set_occurrence_properties!(occurrence)
|
204
234
|
end
|
235
|
+
|
236
|
+
def recurs?
|
237
|
+
@rrule_property && @rrule_property.length > 0 || @rdate_property && @rdate_property.length > 0
|
238
|
+
end
|
239
|
+
|
205
240
|
end
|
206
241
|
end
|