rubyredrick-ri_cal 0.5.2 → 0.5.3
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 +3 -0
- data/Manifest.txt +4 -0
- data/Rakefile +3 -2
- data/lib/ri_cal/component/calendar.rb +2 -1
- data/lib/ri_cal/component/timezone/timezone_period.rb +31 -6
- data/lib/ri_cal/component/timezone.rb +2 -1
- 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/time_machine.rb +5 -5
- data/lib/ri_cal/property_value/date_time/timezone_support.rb +9 -5
- data/lib/ri_cal/property_value/date_time.rb +1 -0
- data/lib/ri_cal.rb +33 -2
- 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]
|
@@ -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
|
@@ -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
|
@@ -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,
|
@@ -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
|
@@ -19,13 +19,13 @@ module RiCal
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def compute_advance(d, options) # :nodoc:
|
22
|
-
|
23
|
-
d = d >>
|
24
|
-
|
25
|
-
d = d +
|
22
|
+
months_advance = (options[:years] || 0) * 12 + (options[:months] || 0)
|
23
|
+
d = d >> months_advance unless months_advance == 0
|
24
|
+
days_advance = (options[:weeks] || 0) * 7 + (options[:days] || 0)
|
25
|
+
d = d + days_advance unless days_advance == 0
|
26
26
|
datetime_advanced_by_date = compute_change(@date_time_value, :year => d.year, :month => d.month, :day => d.day)
|
27
27
|
seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
|
28
|
-
seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date +
|
28
|
+
seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date + RiCal.RationalOffset[seconds_to_advance.round]
|
29
29
|
end
|
30
30
|
|
31
31
|
def advance(options) # :nodoc:
|
@@ -13,7 +13,11 @@ module RiCal
|
|
13
13
|
def tzid=(timezone_id) #:nodoc:
|
14
14
|
timezone_id = default_tzid if timezone_id == :default
|
15
15
|
@tzid = timezone_id
|
16
|
-
|
16
|
+
reset_cached_values
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset_cached_values #:nodoc:
|
20
|
+
@timezone = @utc = @rational_tz_offset = nil
|
17
21
|
end
|
18
22
|
|
19
23
|
def find_timezone #:nodoc:
|
@@ -46,7 +50,7 @@ module RiCal
|
|
46
50
|
# Returns a instance that represents the time in UTC.
|
47
51
|
def utc
|
48
52
|
if has_local_timezone?
|
49
|
-
timezone.local_to_utc(self)
|
53
|
+
@utc ||= timezone.local_to_utc(self)
|
50
54
|
else # Already local or a floating time
|
51
55
|
self
|
52
56
|
end
|
@@ -54,9 +58,9 @@ module RiCal
|
|
54
58
|
|
55
59
|
def rational_tz_offset #:nodoc:
|
56
60
|
if has_local_timezone?
|
57
|
-
timezone.rational_utc_offset(@date_time_value)
|
61
|
+
@rational_tz_offset ||= timezone.rational_utc_offset(@date_time_value)
|
58
62
|
else
|
59
|
-
|
63
|
+
@rational_tz_offset ||= RiCal.RationalOffset[0]
|
60
64
|
end
|
61
65
|
end
|
62
66
|
|
@@ -69,7 +73,7 @@ module RiCal
|
|
69
73
|
def floating?
|
70
74
|
tzid.nil?
|
71
75
|
end
|
72
|
-
|
76
|
+
|
73
77
|
def has_valid_tzinfo_tzid? #:nodoc:
|
74
78
|
if tzid && tzid != :floating
|
75
79
|
TZInfo::Timezone.get(tzid) rescue false
|
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
|