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,57 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
class OccurrenceIncrementer # :nodoc:
|
7
|
+
class YearlyIncrementer < FrequencyIncrementer #:nodoc:
|
8
|
+
|
9
|
+
attr_reader :wkst
|
10
|
+
|
11
|
+
def initialize(rrule, sub_cycle_incrementer)
|
12
|
+
@wkst = rrule.wkst_day
|
13
|
+
super(rrule, sub_cycle_incrementer)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_rrule(rrule, start_time)
|
17
|
+
conditional_incrementer(rrule, "YEARLY", ByMonthIncrementer)
|
18
|
+
end
|
19
|
+
|
20
|
+
def advance_what
|
21
|
+
:years
|
22
|
+
end
|
23
|
+
|
24
|
+
def step(date_time)
|
25
|
+
if contains_weeknum_incrementer?
|
26
|
+
result = date_time
|
27
|
+
multiplier.times do
|
28
|
+
result = result.at_start_of_next_iso_year(wkst)
|
29
|
+
end
|
30
|
+
result
|
31
|
+
else
|
32
|
+
super(date_time)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_of_cycle(date_time)
|
37
|
+
if contains_weeknum_incrementer?
|
38
|
+
date_time.at_start_of_iso_year(wkst)
|
39
|
+
elsif contains_daily_incrementer?
|
40
|
+
date_time.change(:month => 1, :day => 1)
|
41
|
+
else
|
42
|
+
date_time.change(:month => 1)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def end_of_occurrence(date_time)
|
47
|
+
if contains_weeknum_incrementer?
|
48
|
+
date_time.end_of_iso_year(wkst)
|
49
|
+
else
|
50
|
+
date_time.end_of_year
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
class OccurrenceIncrementer # :nodoc:
|
7
|
+
|
8
|
+
attr_accessor :sub_cycle_incrementer, :current_occurrence, :outer_range
|
9
|
+
attr_accessor :outer_incrementers
|
10
|
+
attr_accessor :contains_daily_incrementer, :contains_weeknum_incrementer
|
11
|
+
attr_reader :leaf_iterator
|
12
|
+
parent_path = "ri_cal/property_value/recurrence_rule/occurrence_incrementer"
|
13
|
+
|
14
|
+
autoload :ByDayIncrementer, "#{parent_path}/by_day_incrementer.rb"
|
15
|
+
autoload :ByHourIncrementer, "#{parent_path}/by_hour_incrementer.rb"
|
16
|
+
autoload :ByMinuteIncrementer, "#{parent_path}/by_minute_incrementer.rb"
|
17
|
+
autoload :ByMonthIncrementer, "#{parent_path}/by_month_incrementer.rb"
|
18
|
+
autoload :ByMonthdayIncrementer, "#{parent_path}/by_monthday_incrementer.rb"
|
19
|
+
autoload :ByNumberedDayIncrementer, "#{parent_path}/by_numbered_day_incrementer.rb"
|
20
|
+
autoload :BySecondIncrementer, "#{parent_path}/by_second_incrementer.rb"
|
21
|
+
autoload :ByYeardayIncrementer, "#{parent_path}/by_yearday_incrementer.rb"
|
22
|
+
autoload :ByWeekNoIncrementer, "#{parent_path}/by_weekno_incrementer.rb"
|
23
|
+
autoload :DailyIncrementer, "#{parent_path}/daily_incrementer.rb"
|
24
|
+
autoload :FrequencyIncrementer, "#{parent_path}/frequency_incrementer.rb"
|
25
|
+
autoload :HourlyIncrementer, "#{parent_path}/hourly_incrementer.rb"
|
26
|
+
autoload :ListIncrementer, "#{parent_path}/list_incrementer.rb"
|
27
|
+
autoload :MinutelyIncrementer, "#{parent_path}/minutely_incrementer.rb"
|
28
|
+
autoload :MonthlyIncrementer, "#{parent_path}/monthly_incrementer.rb"
|
29
|
+
autoload :NullSubCycleIncrementer, "#{parent_path}/null_sub_cycle_incrementer.rb"
|
30
|
+
autoload :SecondlyIncrementer, "#{parent_path}/secondly_incrementer.rb"
|
31
|
+
autoload :WeeklyIncrementer, "#{parent_path}/weekly_incrementer.rb"
|
32
|
+
autoload :YearlyIncrementer, "#{parent_path}/yearly_incrementer.rb"
|
33
|
+
|
34
|
+
include RecurrenceRule::TimeManipulation
|
35
|
+
|
36
|
+
def initialize(rrule, sub_cycle_incrementer)
|
37
|
+
self.sub_cycle_incrementer = sub_cycle_incrementer
|
38
|
+
@outermost = true
|
39
|
+
self.outer_incrementers = []
|
40
|
+
if sub_cycle_incrementer
|
41
|
+
self.contains_daily_incrementer = sub_cycle_incrementer.daily_incrementer? ||
|
42
|
+
sub_cycle_incrementer.contains_daily_incrementer?
|
43
|
+
self.contains_weeknum_incrementer = sub_cycle_incrementer.weeknum_incrementer?||
|
44
|
+
sub_cycle_incrementer.contains_weeknum_incrementer?
|
45
|
+
sub_cycle_incrementer.add_outer_incrementer(self)
|
46
|
+
else
|
47
|
+
self.sub_cycle_incrementer = NullSubCycleIncrementer
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.from_rrule(recurrence_rule, start_time)
|
52
|
+
YearlyIncrementer.from_rrule(recurrence_rule, start_time)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_outer_incrementer(incrementer)
|
56
|
+
@outermost = false
|
57
|
+
self.outer_incrementers << incrementer
|
58
|
+
sub_cycle_incrementer.add_outer_incrementer(incrementer)
|
59
|
+
end
|
60
|
+
|
61
|
+
def outermost?
|
62
|
+
@outermost
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
if sub_cycle_incrementer
|
67
|
+
"#{self.short_name}->#{sub_cycle_incrementer}"
|
68
|
+
else
|
69
|
+
self.short_name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def short_name
|
74
|
+
@short_name ||= self.class.name.split("::").last
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return the next time after previous_occurrence generated by this incrementer
|
78
|
+
# But the occurrence is outside the current cycle of any outer incrementer(s) return
|
79
|
+
# nil which will cause the outer incrementer to step to its next cycle
|
80
|
+
def next_time(previous_occurrence)
|
81
|
+
@previous_occurrence = previous_occurrence
|
82
|
+
if current_occurrence
|
83
|
+
sub_occurrence = @sub_cycle_incrementer.next_time(previous_occurrence)
|
84
|
+
else #first time
|
85
|
+
sub_occurrence = @sub_cycle_incrementer.first_sub_occurrence(previous_occurrence, update_cycle_range(previous_occurrence))
|
86
|
+
end
|
87
|
+
if sub_occurrence
|
88
|
+
candidate = sub_occurrence
|
89
|
+
else
|
90
|
+
candidate = next_cycle(previous_occurrence)
|
91
|
+
end
|
92
|
+
if in_outer_cycle?(candidate)
|
93
|
+
candidate
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def update_cycle_range(date_time)
|
100
|
+
self.current_occurrence = date_time
|
101
|
+
(date_time..end_of_occurrence(date_time))
|
102
|
+
end
|
103
|
+
|
104
|
+
def in_outer_cycle?(candidate)
|
105
|
+
candidate && (outer_range.nil? || (outer_range.first <= candidate && outer_range.last >= candidate))
|
106
|
+
end
|
107
|
+
|
108
|
+
def first_sub_occurrence(previous_occurrence, outer_cycle_range)
|
109
|
+
first_within_outer_cycle(previous_occurrence, outer_cycle_range)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Advance to the next cycle, if the result is within the current cycles of all outer incrementers
|
113
|
+
def next_cycle(previous_occurrence)
|
114
|
+
raise "next_cycle is a subclass responsibility"
|
115
|
+
end
|
116
|
+
|
117
|
+
def contains_daily_incrementer?
|
118
|
+
@contains_daily_incrementer
|
119
|
+
end
|
120
|
+
|
121
|
+
def daily_incrementer?
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
125
|
+
def contains_weeknum_incrementer?
|
126
|
+
@contains_weeknum_incrementer
|
127
|
+
end
|
128
|
+
|
129
|
+
def weeknum_incrementer?
|
130
|
+
false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
# Instances of RecurringDay are used to represent values in BYDAY recurrence rule parts
|
7
|
+
#
|
8
|
+
class RecurringDay # :nodoc:
|
9
|
+
|
10
|
+
attr_reader :wday, :index, :rrule
|
11
|
+
|
12
|
+
DayNames = %w{SU MO TU WE TH FR SA} unless defined? DayNames
|
13
|
+
day_nums = {}
|
14
|
+
unless defined? DayNums
|
15
|
+
DayNames.each_with_index { |name, i| day_nums[name] = i }
|
16
|
+
DayNums = day_nums
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :source, :scope
|
20
|
+
def initialize(source, rrule, scope = :monthly)
|
21
|
+
@source = source
|
22
|
+
@rrule = rrule
|
23
|
+
@scope = scope
|
24
|
+
wd_match = source.match(/([+-]?\d*)(SU|MO|TU|WE|TH|FR|SA)/)
|
25
|
+
if wd_match
|
26
|
+
@day, @ordinal = wd_match[2], wd_match[1]
|
27
|
+
@wday = DayNums[@day]
|
28
|
+
@index = (@ordinal == "") ? nil : @ordinal.to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid?
|
33
|
+
!@day.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(another)
|
37
|
+
self.class === another && to_a = another.to_a
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
[@day, @ordinal]
|
42
|
+
end
|
43
|
+
|
44
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
45
|
+
def list_id(time)
|
46
|
+
case @scope
|
47
|
+
when :yearly
|
48
|
+
time.year
|
49
|
+
when :monthly
|
50
|
+
(time.year * 100) + time.month
|
51
|
+
when :weekly
|
52
|
+
time.at_start_of_week_with_wkst(rrule.wkst_day).jd
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
57
|
+
def matches_for(time)
|
58
|
+
case @scope
|
59
|
+
when :yearly
|
60
|
+
yearly_matches_for(time)
|
61
|
+
when :monthly
|
62
|
+
monthly_matches_for(time)
|
63
|
+
when :weekly
|
64
|
+
weekly_matches_for(time)
|
65
|
+
else
|
66
|
+
walkback = caller.grep(/recurrence/i)
|
67
|
+
raise "Logic error!#{@scope.inspect}\n #{walkback.join("\n ")}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def yearly_matches_for(time)
|
72
|
+
if @ordinal == ""
|
73
|
+
t = time.nth_wday_in_year(1, wday)
|
74
|
+
result = []
|
75
|
+
year = time.year
|
76
|
+
while t.year == year
|
77
|
+
result << t
|
78
|
+
t = t.advance(:week => 1)
|
79
|
+
end
|
80
|
+
result
|
81
|
+
else
|
82
|
+
[time.nth_wday_in_year(@ordinal.to_i, wday)]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def monthly_matches_for(time)
|
87
|
+
if @ordinal == ""
|
88
|
+
t = time.nth_wday_in_month(1, wday)
|
89
|
+
result = []
|
90
|
+
month = time.month
|
91
|
+
while t.month == month
|
92
|
+
result << t
|
93
|
+
t = t.advance(:days => 7)
|
94
|
+
end
|
95
|
+
result
|
96
|
+
else
|
97
|
+
result = [time.nth_wday_in_month(index, wday)]
|
98
|
+
result
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def weekly_matches_for(time)
|
103
|
+
date = time.start_of_week_with_wkst(rrule.wkst_day)
|
104
|
+
date += 1 while date.wday != wday
|
105
|
+
[time.change(:year => date.year, :month => date.month, :day => date.day)]
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_s
|
109
|
+
"#{@ordinal}#{@day}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def ordinal_match(date_or_time)
|
113
|
+
if @ordinal == "" || @scope == :weekly
|
114
|
+
true
|
115
|
+
else
|
116
|
+
if @scope == :yearly
|
117
|
+
date_or_time.nth_wday_in_year?(index, wday)
|
118
|
+
else
|
119
|
+
date_or_time.nth_wday_in_month?(index, wday)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Determine if a particular date, time, or date_time is included in the recurrence
|
125
|
+
def include?(date_or_time)
|
126
|
+
date_or_time.wday == wday && ordinal_match(date_or_time)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
# Instances of RecurringMonthDay represent BYMONTHDAY parts in recurrence rules
|
7
|
+
class RecurringMonthDay < NumberedSpan # :nodoc:
|
8
|
+
|
9
|
+
def last
|
10
|
+
31
|
11
|
+
end
|
12
|
+
|
13
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
14
|
+
def list_id(time)
|
15
|
+
time.month
|
16
|
+
end
|
17
|
+
|
18
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
19
|
+
def matches_for(time)
|
20
|
+
[time.change(:day => 1).advance(:days => target_for(time)- 1)]
|
21
|
+
end
|
22
|
+
|
23
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
24
|
+
def list_id(time)
|
25
|
+
time.month
|
26
|
+
end
|
27
|
+
|
28
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
29
|
+
def matches_for(time)
|
30
|
+
[time.change(:day => 1).advance(:days => target_for(time)- 1)]
|
31
|
+
end
|
32
|
+
|
33
|
+
def target_date_time_for(date_time)
|
34
|
+
matches_for(date_time)[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
38
|
+
def matches_for(time)
|
39
|
+
[time.change(:day => 1).advance(:days => target_for(time)- 1)]
|
40
|
+
end
|
41
|
+
|
42
|
+
def target_date_time_for(date_time)
|
43
|
+
matches_for(date_time)[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def fixed_day?
|
47
|
+
@source > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def target_for(date_or_time)
|
51
|
+
if fixed_day?
|
52
|
+
@source
|
53
|
+
else
|
54
|
+
date_or_time.days_in_month + @source + 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def include?(date_or_time)
|
59
|
+
date_or_time.mday == target_for(date_or_time)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
class RecurringNumberedWeek < NumberedSpan # :nodoc:
|
7
|
+
def last
|
8
|
+
53
|
9
|
+
end
|
10
|
+
|
11
|
+
def rule_wkst
|
12
|
+
@rule && rule.wkst_day
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_wkst
|
16
|
+
rule_wkst || 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def adjusted_iso_weeknum(date_or_time)
|
20
|
+
if @source > 0
|
21
|
+
@source
|
22
|
+
else
|
23
|
+
date_or_time.iso_weeks_in_year(wkst) + @source + 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def include?(date_or_time, wkst=default_wkst)
|
28
|
+
date_or_time.iso_week_num(wkst) == adjusted_iso_weeknum(date_or_time)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
class RecurringYearDay < NumberedSpan # :nodoc:
|
7
|
+
|
8
|
+
def last
|
9
|
+
366
|
10
|
+
end
|
11
|
+
|
12
|
+
def leap_year?(year)
|
13
|
+
year % 4 == 0 && (year % 400 == 0 || year % 100 != 0)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def length_of_year(year)
|
18
|
+
leap_year?(year) ? 366 : 365
|
19
|
+
end
|
20
|
+
|
21
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
22
|
+
def list_id(time)
|
23
|
+
time.year
|
24
|
+
end
|
25
|
+
|
26
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
27
|
+
def matches_for(time)
|
28
|
+
[time.change(:month => 1, :day => 1).advance(:days => target_for(time)- 1)]
|
29
|
+
end
|
30
|
+
|
31
|
+
def target_date_time_for(date_time)
|
32
|
+
matches_for(date_time)[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
def fixed_day?
|
36
|
+
@source > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def target_for(date_or_time)
|
40
|
+
if fixed_day?
|
41
|
+
@source
|
42
|
+
else
|
43
|
+
length_of_year(date_or_time.year) + @source + 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def include?(date_or_time)
|
48
|
+
date_or_time.yday == target_for(date_or_time)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
module TimeManipulation #:nodoc:
|
7
|
+
|
8
|
+
def advance_day(date_time)
|
9
|
+
date_time.advance(:days => 1)
|
10
|
+
end
|
11
|
+
|
12
|
+
def first_hour_of_day(date_time)
|
13
|
+
date_time.change(:hour => 0)
|
14
|
+
end
|
15
|
+
|
16
|
+
def advance_week(date_time)
|
17
|
+
date_time.advance(:days => 7)
|
18
|
+
end
|
19
|
+
|
20
|
+
def first_day_of_week(wkst_day, date_time)
|
21
|
+
date_time.at_start_of_week_with_wkst(wkst_day)
|
22
|
+
end
|
23
|
+
|
24
|
+
def advance_month(date_time)
|
25
|
+
date_time.advance(:months => 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def first_day_of_month(date_time)
|
29
|
+
date_time.change(:day => 1)
|
30
|
+
end
|
31
|
+
|
32
|
+
def advance_year(date_time)
|
33
|
+
date_time.advance(:years => 1)
|
34
|
+
end
|
35
|
+
|
36
|
+
def first_day_of_year(date_time)
|
37
|
+
date_time.change(:month => 1, :day => 1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
module Validations #:nodoc:
|
7
|
+
# Validate that the parameters of the reciever conform to RFC 2445
|
8
|
+
# If errors are found they will be added to the receivers errors
|
9
|
+
#
|
10
|
+
# Whenever any of the parameters are set, e.g. with:
|
11
|
+
# recurrence_rule.count = 2
|
12
|
+
# the errors will be reset
|
13
|
+
def valid?
|
14
|
+
validate if @errors.nil?
|
15
|
+
errors.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return any errors found during validation
|
19
|
+
# See #valid?
|
20
|
+
def errors
|
21
|
+
@errors ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset_errors # :nodoc:
|
25
|
+
@errors = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Used by #valid? to validate that the parameters of the reciever conform to RFC 2445
|
29
|
+
# If errors are found they will be added to the receivers errors
|
30
|
+
#
|
31
|
+
# Whenever any of the parameters are set, e.g. with:
|
32
|
+
# recurrence_rule.count = 2
|
33
|
+
# the errors will be reset
|
34
|
+
def validate
|
35
|
+
@errors = []
|
36
|
+
validate_termination
|
37
|
+
validate_freq
|
38
|
+
validate_interval
|
39
|
+
validate_int_by_list(:bysecond, (0..59))
|
40
|
+
validate_int_by_list(:byminute, (0..59))
|
41
|
+
validate_int_by_list(:byhour, (0..23))
|
42
|
+
validate_int_by_list(:bymonth, (1..12))
|
43
|
+
validate_bysetpos
|
44
|
+
validate_byday_list
|
45
|
+
validate_bymonthday_list
|
46
|
+
validate_byyearday_list
|
47
|
+
validate_byweekno_list
|
48
|
+
validate_wkst
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate_termination
|
52
|
+
errors << "COUNT and UNTIL cannot both be specified" if @count && @until
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_freq
|
56
|
+
if @freq
|
57
|
+
unless %w{
|
58
|
+
SECONDLY MINUTELY HOURLY DAILY
|
59
|
+
WEEKLY MONTHLY YEARLY
|
60
|
+
}.include?(@freq.upcase)
|
61
|
+
errors << "Invalid frequency '#{@freq}'"
|
62
|
+
end
|
63
|
+
else
|
64
|
+
errors << "RecurrenceRule must have a value for FREQ"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_interval
|
69
|
+
if @interval
|
70
|
+
errors << "interval must be a positive integer" unless @interval > 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_wkst
|
75
|
+
errors << "#{wkst.inspect} is invalid for wkst" unless %w{MO TU WE TH FR SA SU}.include?(wkst)
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_int_by_list(which, test)
|
79
|
+
vals = by_list[which] || []
|
80
|
+
vals.each do |val|
|
81
|
+
errors << "#{val} is invalid for #{which}" unless test === val
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_bysetpos
|
86
|
+
vals = by_list[:bysetpos] || []
|
87
|
+
vals.each do |val|
|
88
|
+
errors << "#{val} is invalid for bysetpos" unless (-366..-1) === val || (1..366) === val
|
89
|
+
end
|
90
|
+
unless vals.empty?
|
91
|
+
errors << "bysetpos cannot be used without another by_xxx rule part" unless by_list.length > 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate_byday_list
|
96
|
+
days = by_list[:byday] || []
|
97
|
+
days.each do |day|
|
98
|
+
errors << "#{day.source.inspect} is not a valid day" unless day.valid?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_bymonthday_list
|
103
|
+
days = by_list[:bymonthday] || []
|
104
|
+
days.each do |day|
|
105
|
+
errors << "#{day.source.inspect} is not a valid month day" unless day.valid?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_byyearday_list
|
110
|
+
days = by_list[:byyearday] || []
|
111
|
+
days.each do |day|
|
112
|
+
errors << "#{day.source.inspect} is not a valid year day" unless day.valid?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def validate_byweekno_list
|
117
|
+
days = by_list[:byweekno] || []
|
118
|
+
days.each do |day|
|
119
|
+
errors << "#{day.source.inspect} is not a valid week number" unless day.valid?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|