ri_cal 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +45 -0
- data/Manifest.txt +129 -0
- data/README.txt +394 -0
- data/Rakefile +31 -0
- data/bin/ri_cal +8 -0
- data/component_attributes/alarm.yml +10 -0
- data/component_attributes/calendar.yml +4 -0
- data/component_attributes/component_property_defs.yml +180 -0
- data/component_attributes/event.yml +45 -0
- data/component_attributes/freebusy.yml +16 -0
- data/component_attributes/journal.yml +35 -0
- data/component_attributes/timezone.yml +3 -0
- data/component_attributes/timezone_period.yml +11 -0
- data/component_attributes/todo.yml +46 -0
- data/copyrights.txt +1 -0
- data/docs/draft-ietf-calsify-2446bis-08.txt +7280 -0
- data/docs/draft-ietf-calsify-rfc2445bis-09.txt +10416 -0
- data/docs/incrementers.txt +7 -0
- data/docs/rfc2445.pdf +0 -0
- data/lib/ri_cal.rb +144 -0
- data/lib/ri_cal/component.rb +247 -0
- data/lib/ri_cal/component/alarm.rb +21 -0
- data/lib/ri_cal/component/calendar.rb +219 -0
- data/lib/ri_cal/component/event.rb +60 -0
- data/lib/ri_cal/component/freebusy.rb +18 -0
- data/lib/ri_cal/component/journal.rb +30 -0
- data/lib/ri_cal/component/t_z_info_timezone.rb +123 -0
- data/lib/ri_cal/component/timezone.rb +196 -0
- data/lib/ri_cal/component/timezone/daylight_period.rb +25 -0
- data/lib/ri_cal/component/timezone/standard_period.rb +23 -0
- data/lib/ri_cal/component/timezone/timezone_period.rb +53 -0
- data/lib/ri_cal/component/todo.rb +43 -0
- data/lib/ri_cal/core_extensions.rb +6 -0
- data/lib/ri_cal/core_extensions/array.rb +7 -0
- data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
- data/lib/ri_cal/core_extensions/date.rb +13 -0
- data/lib/ri_cal/core_extensions/date/conversions.rb +61 -0
- data/lib/ri_cal/core_extensions/date_time.rb +15 -0
- data/lib/ri_cal/core_extensions/date_time/conversions.rb +50 -0
- data/lib/ri_cal/core_extensions/object.rb +8 -0
- data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
- data/lib/ri_cal/core_extensions/string.rb +8 -0
- data/lib/ri_cal/core_extensions/string/conversions.rb +63 -0
- data/lib/ri_cal/core_extensions/time.rb +13 -0
- data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
- data/lib/ri_cal/core_extensions/time/conversions.rb +61 -0
- data/lib/ri_cal/core_extensions/time/tzid_access.rb +50 -0
- data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +88 -0
- data/lib/ri_cal/floating_timezone.rb +32 -0
- data/lib/ri_cal/invalid_property_value.rb +8 -0
- data/lib/ri_cal/invalid_timezone_identifer.rb +20 -0
- data/lib/ri_cal/occurrence_enumerator.rb +206 -0
- data/lib/ri_cal/occurrence_period.rb +17 -0
- data/lib/ri_cal/parser.rb +138 -0
- data/lib/ri_cal/properties/alarm.rb +390 -0
- data/lib/ri_cal/properties/calendar.rb +164 -0
- data/lib/ri_cal/properties/event.rb +1526 -0
- data/lib/ri_cal/properties/freebusy.rb +594 -0
- data/lib/ri_cal/properties/journal.rb +1240 -0
- data/lib/ri_cal/properties/timezone.rb +151 -0
- data/lib/ri_cal/properties/timezone_period.rb +416 -0
- data/lib/ri_cal/properties/todo.rb +1562 -0
- data/lib/ri_cal/property_value.rb +149 -0
- data/lib/ri_cal/property_value/array.rb +27 -0
- data/lib/ri_cal/property_value/cal_address.rb +11 -0
- data/lib/ri_cal/property_value/date.rb +175 -0
- data/lib/ri_cal/property_value/date_time.rb +335 -0
- data/lib/ri_cal/property_value/date_time/additive_methods.rb +44 -0
- data/lib/ri_cal/property_value/date_time/time_machine.rb +181 -0
- data/lib/ri_cal/property_value/date_time/timezone_support.rb +96 -0
- data/lib/ri_cal/property_value/duration.rb +110 -0
- data/lib/ri_cal/property_value/geo.rb +11 -0
- data/lib/ri_cal/property_value/integer.rb +12 -0
- data/lib/ri_cal/property_value/occurrence_list.rb +144 -0
- data/lib/ri_cal/property_value/period.rb +82 -0
- data/lib/ri_cal/property_value/recurrence_rule.rb +145 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +97 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +79 -0
- data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +148 -0
- data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +53 -0
- data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +31 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurence_incrementer.rb +793 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +131 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +60 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +33 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +49 -0
- data/lib/ri_cal/property_value/recurrence_rule/validations.rb +125 -0
- data/lib/ri_cal/property_value/text.rb +40 -0
- data/lib/ri_cal/property_value/uri.rb +11 -0
- data/lib/ri_cal/property_value/utc_offset.rb +33 -0
- data/lib/ri_cal/required_timezones.rb +55 -0
- data/ri_cal.gemspec +49 -0
- data/sample_ical_files/from_ical_dot_app/test1.ics +38 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +71 -0
- data/spec/ri_cal/component/alarm_spec.rb +12 -0
- data/spec/ri_cal/component/calendar_spec.rb +54 -0
- data/spec/ri_cal/component/event_spec.rb +601 -0
- data/spec/ri_cal/component/freebusy_spec.rb +12 -0
- data/spec/ri_cal/component/journal_spec.rb +37 -0
- data/spec/ri_cal/component/t_z_info_timezone_spec.rb +36 -0
- data/spec/ri_cal/component/timezone_spec.rb +218 -0
- data/spec/ri_cal/component/todo_spec.rb +112 -0
- data/spec/ri_cal/component_spec.rb +224 -0
- data/spec/ri_cal/core_extensions/string/conversions_spec.rb +78 -0
- data/spec/ri_cal/core_extensions/time/calculations_spec.rb +188 -0
- data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +45 -0
- data/spec/ri_cal/occurrence_enumerator_spec.rb +573 -0
- data/spec/ri_cal/parser_spec.rb +303 -0
- data/spec/ri_cal/property_value/date_spec.rb +53 -0
- data/spec/ri_cal/property_value/date_time_spec.rb +383 -0
- data/spec/ri_cal/property_value/duration_spec.rb +126 -0
- data/spec/ri_cal/property_value/occurrence_list_spec.rb +72 -0
- data/spec/ri_cal/property_value/period_spec.rb +49 -0
- data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +21 -0
- data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1814 -0
- data/spec/ri_cal/property_value/text_spec.rb +25 -0
- data/spec/ri_cal/property_value/utc_offset_spec.rb +48 -0
- data/spec/ri_cal/property_value_spec.rb +125 -0
- data/spec/ri_cal/required_timezones_spec.rb +67 -0
- data/spec/ri_cal_spec.rb +53 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +46 -0
- data/tasks/gem_loader/load_active_support.rb +3 -0
- data/tasks/gem_loader/load_tzinfo_gem.rb +2 -0
- data/tasks/ri_cal.rake +410 -0
- data/tasks/spec.rake +50 -0
- metadata +221 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
module RiCal
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module Time #:nodoc:
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
module Conversions
|
8
|
+
# Return an RiCal::PropertyValue::DateTime representing the receiver
|
9
|
+
def to_ri_cal_date_time_value(timezone_finder = nil) #:nodoc:
|
10
|
+
RiCal::PropertyValue::DateTime.new(
|
11
|
+
timezone_finder,
|
12
|
+
:value => strftime("%Y%m%dT%H%M%S"),
|
13
|
+
:params => {"TZID" => self.tzid || :default})
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :to_ri_cal_date_or_date_time_value, :to_ri_cal_date_time_value #:nodoc:
|
17
|
+
alias_method :to_ri_cal_occurrence_list_value, :to_ri_cal_date_time_value #:nodoc:
|
18
|
+
|
19
|
+
# Return the natural ri_cal_property for this object
|
20
|
+
def to_ri_cal_property_value(timezone_finder = nil) #:nodoc:
|
21
|
+
to_ri_cal_date_time_value(timezone_finder)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return a copy of this object which will be interpreted as a floating time.
|
25
|
+
def with_floating_timezone
|
26
|
+
dup.set_tzid(:floating)
|
27
|
+
end
|
28
|
+
|
29
|
+
unless defined? ActiveSupport
|
30
|
+
# Converts a Time object to a Date, dropping hour, minute, and second precision.
|
31
|
+
#
|
32
|
+
# my_time = Time.now # => Mon Nov 12 22:59:51 -0500 2007
|
33
|
+
# my_time.to_date # => Mon, 12 Nov 2007
|
34
|
+
#
|
35
|
+
# your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
|
36
|
+
# your_time.to_date # => Tue, 13 Jan 2009
|
37
|
+
def to_date
|
38
|
+
::Date.new(year, month, day)
|
39
|
+
end
|
40
|
+
|
41
|
+
# A method to keep Time, Date and DateTime instances interchangeable on conversions.
|
42
|
+
# In this case, it simply returns +self+.
|
43
|
+
def to_time
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Converts a Time instance to a Ruby DateTime instance, preserving UTC offset.
|
48
|
+
#
|
49
|
+
# my_time = Time.now # => Mon Nov 12 23:04:21 -0500 2007
|
50
|
+
# my_time.to_datetime # => Mon, 12 Nov 2007 23:04:21 -0500
|
51
|
+
#
|
52
|
+
# your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
|
53
|
+
# your_time.to_datetime # => Tue, 13 Jan 2009 13:13:03 -0500
|
54
|
+
def to_datetime
|
55
|
+
::DateTime.civil(year, month, day, hour, min, sec, Rational(utc_offset, 86400))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RiCal
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module Time #:nodoc:
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
# Provides a tzid attribute for ::Time and ::DateTime
|
8
|
+
module TzidAccess
|
9
|
+
# The tzid attribute is used by RiCal, it should be a valid timezone identifier within a calendar,
|
10
|
+
# :floating to indicate a floating time, or nil to use the default timezone in effect
|
11
|
+
#
|
12
|
+
# See PropertyValue::DateTime#default_tzid= and Component::Calendar#tzid=
|
13
|
+
attr_accessor :tzid
|
14
|
+
|
15
|
+
# Convenience method, sets the tzid and returns the receiver
|
16
|
+
def set_tzid(time_zone_identifier)
|
17
|
+
self.tzid = time_zone_identifier
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Predicate indicating whether or not the instance represents a floating time
|
22
|
+
def has_floating_timezone?
|
23
|
+
tzid == :floating
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module TimeWithZoneExtension #:nodoc:
|
31
|
+
def tzid
|
32
|
+
utc? ? "UTC" : time_zone.tzinfo.identifier
|
33
|
+
end
|
34
|
+
|
35
|
+
# Predicate indicating whether or not the instance represents a floating time
|
36
|
+
def has_floating_timezone?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_ri_cal_date_time_value(timezone_finder=nil)
|
41
|
+
::RiCal::PropertyValue::DateTime.new(timezone_finder, :params => {"TZID" => tzid}, :value => strftime("%Y%m%dT%H%M%S"))
|
42
|
+
end
|
43
|
+
alias_method :to_ri_cal_date_or_date_time_value, :to_ri_cal_date_time_value
|
44
|
+
alias_method :to_ri_cal_occurrence_list_value, :to_ri_cal_date_time_value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
if RiCal::TimeWithZone
|
49
|
+
RiCal::TimeWithZone.class_eval {include RiCal::TimeWithZoneExtension}
|
50
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module RiCal
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module Time #:nodoc:
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
# Provide predicate and related methods for use by the RiCal gem
|
8
|
+
# This module is included by Time, Date, and DateTime
|
9
|
+
module WeekDayPredicates
|
10
|
+
|
11
|
+
# Determine the equivalent time on the day which falls on a particular weekday of the same year as the receiver
|
12
|
+
#
|
13
|
+
# == Parameters
|
14
|
+
# n:: the ordinal number being requested
|
15
|
+
# which_wday:: the weekday using Ruby time conventions, i.e. 0 => Sunday, 1 => Monday, ...
|
16
|
+
|
17
|
+
# e.g. to obtain the 2nd Monday of the receivers year use
|
18
|
+
#
|
19
|
+
# time.nth_wday_in_year(2, 1)
|
20
|
+
def nth_wday_in_year(n, which_wday, for_time = self)
|
21
|
+
if n > 0
|
22
|
+
first_of_year = for_time.to_ri_cal_property_value.change(:month => 1, :day => 1)
|
23
|
+
first_in_year = first_of_year.advance(:days => (which_wday - first_of_year.wday + 7) % 7)
|
24
|
+
first_in_year.advance(:days => (7*(n - 1)))
|
25
|
+
else
|
26
|
+
december25 = for_time.to_ri_cal_property_value.change(:month => 12, :day => 25)
|
27
|
+
last_in_year = december25.advance(:days => (which_wday - december25.wday + 7) % 7)
|
28
|
+
last_in_year.advance(:days => (7 * (n + 1)))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# A predicate to determine whether or not the receiver falls on a particular weekday of its year.
|
33
|
+
#
|
34
|
+
# See #nth_wday_in_year
|
35
|
+
#
|
36
|
+
# == Parameters
|
37
|
+
# n:: the ordinal number being requested
|
38
|
+
# which_wday:: the weekday using Ruby time conventions, i.e. 0 => Sunday, 1 => Monday, ...
|
39
|
+
def nth_wday_in_year?(n, which_wday)
|
40
|
+
target = nth_wday_in_year(n, which_wday)
|
41
|
+
[self.year, self.mon, self.day] == [target.year, target.mon, target.day]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Determine the day which falls on a particular weekday of the same month as the receiver
|
45
|
+
#
|
46
|
+
# == Parameters
|
47
|
+
# n:: the ordinal number being requested
|
48
|
+
# which_wday:: the weekday using Ruby time conventions, i.e. 0 => Sunday, 1 => Monday, ...
|
49
|
+
|
50
|
+
# e.g. to obtain the 3nd Tuesday of the receivers month use
|
51
|
+
#
|
52
|
+
# time.nth_wday_in_month(2, 2)
|
53
|
+
def nth_wday_in_month(n, which_wday, for_time = self)
|
54
|
+
first_of_month = for_time.to_ri_cal_property_value.change(:day => 1)
|
55
|
+
first_in_month = first_of_month.advance(:days => (which_wday - first_of_month.wday))
|
56
|
+
first_in_month = first_in_month.advance(:days => 7) if first_in_month.month != first_of_month.month
|
57
|
+
if n > 0
|
58
|
+
first_in_month.advance(:days => (7*(n - 1)))
|
59
|
+
else
|
60
|
+
possible = first_in_month.advance(:days => 21)
|
61
|
+
possible = possible.advance(:days => 7) while possible.month == first_in_month.month
|
62
|
+
last_in_month = possible.advance(:days => - 7)
|
63
|
+
(last_in_month.advance(:days => - (7*(n.abs - 1))))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# A predicate to determine whether or not the receiver falls on a particular weekday of its month.
|
68
|
+
#
|
69
|
+
# == Parameters
|
70
|
+
# n:: the ordinal number being requested
|
71
|
+
# which_wday:: the weekday using Ruby time conventions, i.e. 0 => Sunday, 1 => Monday, ...
|
72
|
+
def nth_wday_in_month?(n, which_wday)
|
73
|
+
target = nth_wday_in_month(n, which_wday)
|
74
|
+
[self.year, self.month, self.day] == [target.year, target.month, target.day]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return a DateTime which is the beginning of the first day on or before the receiver
|
78
|
+
# with the specified wday
|
79
|
+
def start_of_week_with_wkst(wkst)
|
80
|
+
wkst ||= 1
|
81
|
+
date = ::Date.civil(self.year, self.month, self.day)
|
82
|
+
date -= 1 while date.wday != wkst
|
83
|
+
date
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RiCal
|
2
|
+
#- ©2009 Rick DeNatale
|
3
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
4
|
+
#
|
5
|
+
# FloatingTimezone represents the 'time zone' for a time or date time with no timezone
|
6
|
+
# Times with floating timezones are always interpreted in the timezone of the observer
|
7
|
+
class FloatingTimezone
|
8
|
+
|
9
|
+
def self.identifier #:nodoc:
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.tzinfo_timezone #:nodoc:
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.rational_utc_offset(local) #:nodoc:
|
18
|
+
Rational(0, 24)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return the time unchanged
|
22
|
+
def self.utc_to_local(time)
|
23
|
+
time.with_floating_timezone.to_ri_cal_date_time_value
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return the time unchanged
|
27
|
+
def self.local_to_utc(time)
|
28
|
+
time.with_floating_timezone.to_ri_cal_date_time_value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RiCal
|
2
|
+
#- ©2009 Rick DeNatale
|
3
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
4
|
+
#
|
5
|
+
# An InvalidTimezoneIdentifier error is raised when a DATETIME property with an invalid timezone is
|
6
|
+
# involved in a timezone conversion operation
|
7
|
+
#
|
8
|
+
# Rather than attempting to detect invalid timezones immediately the detection is deferred to avoid problems
|
9
|
+
# such as importing a calendar which has forward reference to VTIMEZONE components.
|
10
|
+
class InvalidTimezoneIdentifier < StandardError
|
11
|
+
|
12
|
+
def self.not_found_in_calendar(identifier) #:nodoc:
|
13
|
+
new("#{identifier.inspect} is not the identifier of a VTIMEZONE component of this calendar")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.invalid_tzinfo_identifier(identifier) #:nodoc:
|
17
|
+
new("#{identifier.inspect} is not known to the tzinfo database")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module RiCal
|
2
|
+
#- ©2009 Rick DeNatale
|
3
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
4
|
+
#
|
5
|
+
# OccurrenceEnumerator provides common methods for CalendarComponents that support recurrence
|
6
|
+
# i.e. Event, Journal, Todo, and TimezonePeriod
|
7
|
+
module OccurrenceEnumerator
|
8
|
+
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def default_duration # :nodoc:
|
12
|
+
dtend && dtstart.to_ri_cal_date_time_value.duration_until(dtend.to_ri_cal_date_time_value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_start_time # :nodoc:
|
16
|
+
dtstart && dtstart.to_ri_cal_date_time_value
|
17
|
+
end
|
18
|
+
|
19
|
+
class EmptyRulesEnumerator # :nodoc:
|
20
|
+
def self.next_occurrence
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.bounded?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.empty?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# OccurrenceMerger takes multiple recurrence rules and enumerates the combination in sequence.
|
34
|
+
class OccurrenceMerger # :nodoc:
|
35
|
+
def self.for(component, rules)
|
36
|
+
if rules.nil? || rules.empty?
|
37
|
+
EmptyRulesEnumerator
|
38
|
+
elsif rules.length == 1
|
39
|
+
rules.first.enumerator(component)
|
40
|
+
else
|
41
|
+
new(component, rules)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :enumerators, :nexts
|
46
|
+
|
47
|
+
def initialize(component, rules)
|
48
|
+
self.enumerators = rules.map {|rrule| rrule.enumerator(component)}
|
49
|
+
@bounded = enumerators.all? {|enumerator| enumerator.bounded?}
|
50
|
+
@empty = enumerators.all? {|enumerator| enumerator.empty?}
|
51
|
+
self.nexts = @enumerators.map {|enumerator| enumerator.next_occurrence}
|
52
|
+
end
|
53
|
+
|
54
|
+
def empty?
|
55
|
+
@empty
|
56
|
+
end
|
57
|
+
|
58
|
+
# return the earliest of each of the enumerators next occurrences
|
59
|
+
def next_occurrence
|
60
|
+
result = nexts.compact.sort.first
|
61
|
+
if result
|
62
|
+
nexts.each_with_index { |datetimevalue, i| @nexts[i] = @enumerators[i].next_occurrence if result == datetimevalue }
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
def bounded?
|
68
|
+
@bounded
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# EnumerationInstance holds the values needed during the enumeration of occurrences for a component.
|
73
|
+
class EnumerationInstance # :nodoc:
|
74
|
+
include Enumerable
|
75
|
+
|
76
|
+
def initialize(component, options = {})
|
77
|
+
@component = component
|
78
|
+
@start = options[:starting]
|
79
|
+
@cutoff = options[:before]
|
80
|
+
@count = options[:count]
|
81
|
+
@rrules = OccurrenceMerger.for(@component, [@component.rrule_property, @component.rdate_property].flatten.compact)
|
82
|
+
@exrules = OccurrenceMerger.for(@component, [@component.exrule_property, @component.exdate_property].flatten.compact)
|
83
|
+
end
|
84
|
+
|
85
|
+
# return the next exclusion which starts at the same time or after the start time of the occurrence
|
86
|
+
# return nil if this exhausts the exclusion rules
|
87
|
+
def exclusion_for(occurrence)
|
88
|
+
while (@next_exclusion && @next_exclusion.dtstart < occurrence.dtstart)
|
89
|
+
@next_exclusion = @exrules.next_occurrence
|
90
|
+
end
|
91
|
+
@next_exclusion
|
92
|
+
end
|
93
|
+
|
94
|
+
# TODO: Need to research this, I beleive that this should also take the end time into account,
|
95
|
+
# but I need to research
|
96
|
+
def exclusion_match?(occurrence, exclusion)
|
97
|
+
exclusion && (occurrence.dtstart == exclusion.dtstart)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Also exclude occurrences before the :starting date_time
|
101
|
+
def exclude?(occurrence)
|
102
|
+
exclusion_match?(occurrence, exclusion_for(occurrence)) ||
|
103
|
+
(@start && occurrence.dtstart.to_datetime < @start)
|
104
|
+
end
|
105
|
+
|
106
|
+
# yield each occurrence to a block
|
107
|
+
# some components may be open-ended, e.g. have no COUNT or DTEND
|
108
|
+
def each
|
109
|
+
if @rrules.empty?
|
110
|
+
yield @component
|
111
|
+
else
|
112
|
+
occurrence = @rrules.next_occurrence
|
113
|
+
yielded = 0
|
114
|
+
@next_exclusion = @exrules.next_occurrence
|
115
|
+
while (occurrence)
|
116
|
+
if (@cutoff && occurrence.dtstart.to_datetime >= @cutoff) || (@count && yielded >= @count)
|
117
|
+
occurrence = nil
|
118
|
+
else
|
119
|
+
unless exclude?(occurrence)
|
120
|
+
yielded += 1
|
121
|
+
yield @component.recurrence(occurrence)
|
122
|
+
end
|
123
|
+
occurrence = @rrules.next_occurrence
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def bounded?
|
130
|
+
@rrules.bounded? || @count || @cutoff
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_a
|
134
|
+
raise ArgumentError.new("This component is unbounded, cannot produce an array of occurrences!") unless bounded?
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
alias_method :entries, :to_a
|
139
|
+
end
|
140
|
+
|
141
|
+
# return an array of occurrences according to the options parameter. If a component is not bounded, and
|
142
|
+
# the number of occurrences to be returned is not constrained by either the :before, or :count options
|
143
|
+
# an ArgumentError will be raised.
|
144
|
+
#
|
145
|
+
# The components returned will be the same type as the receiver, but will have any recurrence properties
|
146
|
+
# (rrule, rdate, exrule, exdate) removed since they are single occurrences, and will have the recurrence-id
|
147
|
+
# property set to the occurrences dtstart value. (see RFC 2445 sec 4.8.4.4 pp 107-109)
|
148
|
+
#
|
149
|
+
# parameter options:
|
150
|
+
# * :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.
|
152
|
+
# * :count:: an integer which limits the number of occurrences returned.
|
153
|
+
def occurrences(options={})
|
154
|
+
EnumerationInstance.new(self, options).to_a
|
155
|
+
end
|
156
|
+
|
157
|
+
# execute the block for each occurrence
|
158
|
+
def each(&block) # :yields: Component
|
159
|
+
EnumerationInstance.new(self).each(&block)
|
160
|
+
end
|
161
|
+
|
162
|
+
# A predicate which determines whether the component has a bounded set of occurrences
|
163
|
+
def bounded?
|
164
|
+
EnumerationInstance.new(self).bounded?
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return a array whose first element is a UTC DateTime representing the start of the first
|
168
|
+
# occurrence, and whose second element is a UTC DateTime representing the end of the last
|
169
|
+
# occurrence.
|
170
|
+
# If the receiver is not bounded then the second element will be nil.
|
171
|
+
#
|
172
|
+
# The purpose of this method is to provide values which may be used as database attributes so
|
173
|
+
# that a query can find all occurence enumerating components which may have occurrences within
|
174
|
+
# a range of times.
|
175
|
+
def zulu_occurrence_range
|
176
|
+
if bounded?
|
177
|
+
all = occurrences
|
178
|
+
first, last = all.first, all.last
|
179
|
+
else
|
180
|
+
first = occurrences(:count => 1).first
|
181
|
+
last = nil
|
182
|
+
end
|
183
|
+
[first.zulu_occurrence_range_start_time, last ? last.zulu_occurrence_range_finish_time : nil]
|
184
|
+
end
|
185
|
+
|
186
|
+
def set_occurrence_properties!(occurrence) # :nodoc:
|
187
|
+
occurrence_end = occurrence.dtend
|
188
|
+
occurrence_start = occurrence.dtstart
|
189
|
+
@rrule_property = nil
|
190
|
+
@exrule_property = nil
|
191
|
+
@rdate_property = nil
|
192
|
+
@exdate_property = nil
|
193
|
+
@recurrence_id_property = occurrence_start
|
194
|
+
if @dtend_property && !occurrence_end
|
195
|
+
occurrence_end = occurrence_start + (@dtend_property - @dtstart_property)
|
196
|
+
end
|
197
|
+
@dtstart_property = dtstart_property.for_occurrence(occurrence_start)
|
198
|
+
@dtend_property = dtend_property.for_occurrence(occurrence_end) if @dtend_property
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
def recurrence(occurrence) # :nodoc:
|
203
|
+
result = self.dup.set_occurrence_properties!(occurrence)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|