rubyredrick-ri_cal 0.0.2
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 +122 -0
- data/README.txt +271 -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 +2 -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 +22 -0
- data/lib/ri_cal/component/calendar.rb +199 -0
- data/lib/ri_cal/component/event.rb +30 -0
- data/lib/ri_cal/component/freebusy.rb +19 -0
- data/lib/ri_cal/component/journal.rb +22 -0
- data/lib/ri_cal/component/t_z_info_timezone.rb +124 -0
- data/lib/ri_cal/component/timezone/daylight_period.rb +26 -0
- data/lib/ri_cal/component/timezone/standard_period.rb +24 -0
- data/lib/ri_cal/component/timezone/timezone_period.rb +54 -0
- data/lib/ri_cal/component/timezone.rb +193 -0
- data/lib/ri_cal/component/todo.rb +26 -0
- data/lib/ri_cal/component.rb +224 -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 +27 -0
- data/lib/ri_cal/core_extensions/date.rb +13 -0
- data/lib/ri_cal/core_extensions/date_time/conversions.rb +27 -0
- data/lib/ri_cal/core_extensions/date_time.rb +13 -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 +32 -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 +27 -0
- data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +88 -0
- data/lib/ri_cal/core_extensions/time.rb +11 -0
- data/lib/ri_cal/core_extensions.rb +6 -0
- data/lib/ri_cal/invalid_timezone_identifer.rb +20 -0
- data/lib/ri_cal/occurrence_enumerator.rb +172 -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/array.rb +19 -0
- data/lib/ri_cal/property_value/cal_address.rb +12 -0
- data/lib/ri_cal/property_value/date.rb +119 -0
- data/lib/ri_cal/property_value/date_time/additive_methods.rb +43 -0
- data/lib/ri_cal/property_value/date_time/time_machine.rb +180 -0
- data/lib/ri_cal/property_value/date_time/timezone_support.rb +65 -0
- data/lib/ri_cal/property_value/date_time.rb +324 -0
- data/lib/ri_cal/property_value/duration.rb +106 -0
- data/lib/ri_cal/property_value/geo.rb +12 -0
- data/lib/ri_cal/property_value/integer.rb +13 -0
- data/lib/ri_cal/property_value/occurrence_list.rb +82 -0
- data/lib/ri_cal/property_value/period.rb +63 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +98 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +77 -0
- data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +149 -0
- data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +54 -0
- data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +32 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurence_incrementer.rb +794 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +132 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +61 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +34 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +50 -0
- data/lib/ri_cal/property_value/recurrence_rule/validations.rb +126 -0
- data/lib/ri_cal/property_value/recurrence_rule.rb +146 -0
- data/lib/ri_cal/property_value/text.rb +41 -0
- data/lib/ri_cal/property_value/uri.rb +12 -0
- data/lib/ri_cal/property_value/utc_offset.rb +34 -0
- data/lib/ri_cal/property_value.rb +110 -0
- data/lib/ri_cal/required_timezones.rb +56 -0
- data/lib/ri_cal/time_with_floating_timezone.rb +59 -0
- data/lib/ri_cal.rb +134 -0
- data/ri_cal.gemspec +47 -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 +13 -0
- data/spec/ri_cal/component/calendar_spec.rb +55 -0
- data/spec/ri_cal/component/event_spec.rb +157 -0
- data/spec/ri_cal/component/freebusy_spec.rb +13 -0
- data/spec/ri_cal/component/journal_spec.rb +13 -0
- data/spec/ri_cal/component/t_z_info_timezone_spec.rb +37 -0
- data/spec/ri_cal/component/timezone_spec.rb +155 -0
- data/spec/ri_cal/component/todo_spec.rb +61 -0
- data/spec/ri_cal/component_spec.rb +212 -0
- data/spec/ri_cal/core_extensions/time/calculations_spec.rb +189 -0
- data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +46 -0
- data/spec/ri_cal/occurrence_enumerator_spec.rb +218 -0
- data/spec/ri_cal/parser_spec.rb +304 -0
- data/spec/ri_cal/property_value/date_spec.rb +22 -0
- data/spec/ri_cal/property_value/date_time_spec.rb +448 -0
- data/spec/ri_cal/property_value/duration_spec.rb +80 -0
- data/spec/ri_cal/property_value/period_spec.rb +50 -0
- data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +22 -0
- data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1815 -0
- data/spec/ri_cal/property_value/text_spec.rb +14 -0
- data/spec/ri_cal/property_value/utc_offset_spec.rb +49 -0
- data/spec/ri_cal/property_value_spec.rb +111 -0
- data/spec/ri_cal/required_timezones_spec.rb +68 -0
- data/spec/ri_cal_spec.rb +54 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +24 -0
- data/tasks/ri_cal.rake +403 -0
- data/tasks/spec.rake +35 -0
- metadata +201 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
# Instances of RecurringDay are used to represent values in BYDAY recurrence rule parts
|
8
|
+
#
|
9
|
+
class RecurringDay # :nodoc:
|
10
|
+
|
11
|
+
attr_reader :wday, :index, :rrule
|
12
|
+
|
13
|
+
DayNames = %w{SU MO TU WE TH FR SA} unless defined? DayNames
|
14
|
+
day_nums = {}
|
15
|
+
unless defined? DayNums
|
16
|
+
DayNames.each_with_index { |name, i| day_nums[name] = i }
|
17
|
+
DayNums = day_nums
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :source, :scope
|
21
|
+
def initialize(source, rrule, scope = :monthly)
|
22
|
+
@source = source
|
23
|
+
@rrule = rrule
|
24
|
+
@scope = scope
|
25
|
+
wd_match = source.match(/([+-]?\d*)(SU|MO|TU|WE|TH|FR|SA)/)
|
26
|
+
if wd_match
|
27
|
+
@day, @ordinal = wd_match[2], wd_match[1]
|
28
|
+
@wday = DayNums[@day]
|
29
|
+
@index = (@ordinal == "") ? nil : @ordinal.to_i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?
|
34
|
+
!@day.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(another)
|
38
|
+
self.class === another && to_a = another.to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_a
|
42
|
+
[@day, @ordinal]
|
43
|
+
end
|
44
|
+
|
45
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
46
|
+
def list_id(time)
|
47
|
+
case @scope
|
48
|
+
when :yearly
|
49
|
+
time.year
|
50
|
+
when :monthly
|
51
|
+
(time.year * 100) + time.month
|
52
|
+
when :weekly
|
53
|
+
time.at_start_of_week_with_wkst(rrule.wkst_day).jd
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
58
|
+
def matches_for(time)
|
59
|
+
case @scope
|
60
|
+
when :yearly
|
61
|
+
yearly_matches_for(time)
|
62
|
+
when :monthly
|
63
|
+
monthly_matches_for(time)
|
64
|
+
when :weekly
|
65
|
+
weekly_matches_for(time)
|
66
|
+
else
|
67
|
+
walkback = caller.grep(/recurrence/i)
|
68
|
+
raise "Logic error!#{@scope.inspect}\n #{walkback.join("\n ")}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def yearly_matches_for(time)
|
73
|
+
if @ordinal == ""
|
74
|
+
t = time.nth_wday_in_year(1, wday)
|
75
|
+
result = []
|
76
|
+
year = time.year
|
77
|
+
while t.year == year
|
78
|
+
result << t
|
79
|
+
t = t.advance(:week => 1)
|
80
|
+
end
|
81
|
+
result
|
82
|
+
else
|
83
|
+
[time.nth_wday_in_year(@ordinal.to_i, wday)]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def monthly_matches_for(time)
|
88
|
+
if @ordinal == ""
|
89
|
+
t = time.nth_wday_in_month(1, wday)
|
90
|
+
result = []
|
91
|
+
month = time.month
|
92
|
+
while t.month == month
|
93
|
+
result << t
|
94
|
+
t = t.advance(:days => 7)
|
95
|
+
end
|
96
|
+
result
|
97
|
+
else
|
98
|
+
result = [time.nth_wday_in_month(index, wday)]
|
99
|
+
result
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def weekly_matches_for(time)
|
104
|
+
date = time.start_of_week_with_wkst(rrule.wkst_day)
|
105
|
+
date += 1 while date.wday != wday
|
106
|
+
[time.change(:year => date.year, :month => date.month, :day => date.day)]
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
"#{@ordinal}#{@day}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def ordinal_match(date_or_time)
|
114
|
+
if @ordinal == "" || @scope == :weekly
|
115
|
+
true
|
116
|
+
else
|
117
|
+
if @scope == :yearly
|
118
|
+
date_or_time.nth_wday_in_year?(index, wday)
|
119
|
+
else
|
120
|
+
date_or_time.nth_wday_in_month?(index, wday)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Determine if a particular date, time, or date_time is included in the recurrence
|
126
|
+
def include?(date_or_time)
|
127
|
+
date_or_time.wday == wday && ordinal_match(date_or_time)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
# Instances of RecurringMonthDay represent BYMONTHDAY parts in recurrence rules
|
8
|
+
class RecurringMonthDay < NumberedSpan # :nodoc:
|
9
|
+
|
10
|
+
def last
|
11
|
+
31
|
12
|
+
end
|
13
|
+
|
14
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
15
|
+
def list_id(time)
|
16
|
+
time.month
|
17
|
+
end
|
18
|
+
|
19
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
20
|
+
def matches_for(time)
|
21
|
+
[time.change(:day => 1).advance(:days => target_for(time)- 1)]
|
22
|
+
end
|
23
|
+
|
24
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
25
|
+
def list_id(time)
|
26
|
+
time.month
|
27
|
+
end
|
28
|
+
|
29
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
30
|
+
def matches_for(time)
|
31
|
+
[time.change(:day => 1).advance(:days => target_for(time)- 1)]
|
32
|
+
end
|
33
|
+
|
34
|
+
def target_date_time_for(date_time)
|
35
|
+
matches_for(date_time)[0]
|
36
|
+
end
|
37
|
+
|
38
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
39
|
+
def matches_for(time)
|
40
|
+
[time.change(:day => 1).advance(:days => target_for(time)- 1)]
|
41
|
+
end
|
42
|
+
|
43
|
+
def target_date_time_for(date_time)
|
44
|
+
matches_for(date_time)[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def target_for(date_or_time)
|
48
|
+
if @source > 0
|
49
|
+
@source
|
50
|
+
else
|
51
|
+
date_or_time.days_in_month + @source + 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def include?(date_or_time)
|
56
|
+
date_or_time.mday == target_for(date_or_time)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
class RecurringNumberedWeek < NumberedSpan # :nodoc:
|
8
|
+
def last
|
9
|
+
53
|
10
|
+
end
|
11
|
+
|
12
|
+
def rule_wkst
|
13
|
+
@rule && rule.wkst_day
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_wkst
|
17
|
+
rule_wkst || 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def adjusted_iso_weeknum(date_or_time)
|
21
|
+
if @source > 0
|
22
|
+
@source
|
23
|
+
else
|
24
|
+
date_or_time.iso_weeks_in_year(wkst) + @source + 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def include?(date_or_time, wkst=default_wkst)
|
29
|
+
date_or_time.iso_week_num(wkst) == adjusted_iso_weeknum(date_or_time)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
class RecurringYearDay < NumberedSpan # :nodoc:
|
8
|
+
|
9
|
+
def last
|
10
|
+
366
|
11
|
+
end
|
12
|
+
|
13
|
+
def leap_year?(year)
|
14
|
+
year % 4 == 0 && (year % 400 == 0 || year % 100 != 0)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def length_of_year(year)
|
19
|
+
leap_year?(year) ? 366 : 365
|
20
|
+
end
|
21
|
+
|
22
|
+
# return a list id for a given time to allow the enumerator to cache lists
|
23
|
+
def list_id(time)
|
24
|
+
time.year
|
25
|
+
end
|
26
|
+
|
27
|
+
# return a list of times which match the time parameter within the scope of the RecurringDay
|
28
|
+
def matches_for(time)
|
29
|
+
[time.change(:month => 1, :day => 1).advance(:days => target_for(time)- 1)]
|
30
|
+
end
|
31
|
+
|
32
|
+
def target_date_time_for(date_time)
|
33
|
+
matches_for(date_time)[0]
|
34
|
+
end
|
35
|
+
|
36
|
+
def target_for(date_or_time)
|
37
|
+
if @source > 0
|
38
|
+
@source
|
39
|
+
else
|
40
|
+
length_of_year(date_or_time.year) + @source + 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def include?(date_or_time)
|
45
|
+
date_or_time.yday == target_for(date_or_time)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
class RecurrenceRule < PropertyValue
|
4
|
+
#- ©2009 Rick DeNatale
|
5
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
module Validations #:nodoc:
|
8
|
+
# Validate that the parameters of the reciever conform to RFC 2445
|
9
|
+
# If errors are found they will be added to the receivers errors
|
10
|
+
#
|
11
|
+
# Whenever any of the parameters are set, e.g. with:
|
12
|
+
# recurrence_rule.count = 2
|
13
|
+
# the errors will be reset
|
14
|
+
def valid?
|
15
|
+
validate if @errors.nil?
|
16
|
+
errors.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return any errors found during validation
|
20
|
+
# See #valid?
|
21
|
+
def errors
|
22
|
+
@errors ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset_errors # :nodoc:
|
26
|
+
@errors = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Used by #valid? to validate that the parameters of the reciever conform to RFC 2445
|
30
|
+
# If errors are found they will be added to the receivers errors
|
31
|
+
#
|
32
|
+
# Whenever any of the parameters are set, e.g. with:
|
33
|
+
# recurrence_rule.count = 2
|
34
|
+
# the errors will be reset
|
35
|
+
def validate
|
36
|
+
@errors = []
|
37
|
+
validate_termination
|
38
|
+
validate_freq
|
39
|
+
validate_interval
|
40
|
+
validate_int_by_list(:bysecond, (0..59))
|
41
|
+
validate_int_by_list(:byminute, (0..59))
|
42
|
+
validate_int_by_list(:byhour, (0..23))
|
43
|
+
validate_int_by_list(:bymonth, (1..12))
|
44
|
+
validate_bysetpos
|
45
|
+
validate_byday_list
|
46
|
+
validate_bymonthday_list
|
47
|
+
validate_byyearday_list
|
48
|
+
validate_byweekno_list
|
49
|
+
validate_wkst
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_termination
|
53
|
+
errors << "COUNT and UNTIL cannot both be specified" if @count && @until
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_freq
|
57
|
+
if @freq
|
58
|
+
unless %w{
|
59
|
+
SECONDLY MINUTELY HOURLY DAILY
|
60
|
+
WEEKLY MONTHLY YEARLY
|
61
|
+
}.include?(@freq.upcase)
|
62
|
+
errors << "Invalid frequency '#{@freq}'"
|
63
|
+
end
|
64
|
+
else
|
65
|
+
errors << "RecurrenceRule must have a value for FREQ"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_interval
|
70
|
+
if @interval
|
71
|
+
errors << "interval must be a positive integer" unless @interval > 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def validate_wkst
|
76
|
+
errors << "#{wkst.inspect} is invalid for wkst" unless %w{MO TU WE TH FR SA SU}.include?(wkst)
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate_int_by_list(which, test)
|
80
|
+
vals = by_list[which] || []
|
81
|
+
vals.each do |val|
|
82
|
+
errors << "#{val} is invalid for #{which}" unless test === val
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate_bysetpos
|
87
|
+
vals = by_list[:bysetpos] || []
|
88
|
+
vals.each do |val|
|
89
|
+
errors << "#{val} is invalid for bysetpos" unless (-366..-1) === val || (1..366) === val
|
90
|
+
end
|
91
|
+
unless vals.empty?
|
92
|
+
errors << "bysetpos cannot be used without another by_xxx rule part" unless by_list.length > 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def validate_byday_list
|
97
|
+
days = by_list[:byday] || []
|
98
|
+
days.each do |day|
|
99
|
+
errors << "#{day.source.inspect} is not a valid day" unless day.valid?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_bymonthday_list
|
104
|
+
days = by_list[:bymonthday] || []
|
105
|
+
days.each do |day|
|
106
|
+
errors << "#{day.source.inspect} is not a valid month day" unless day.valid?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def validate_byyearday_list
|
111
|
+
days = by_list[:byyearday] || []
|
112
|
+
days.each do |day|
|
113
|
+
errors << "#{day.source.inspect} is not a valid year day" unless day.valid?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_byweekno_list
|
118
|
+
days = by_list[:byweekno] || []
|
119
|
+
days.each do |day|
|
120
|
+
errors << "#{day.source.inspect} is not a valid week number" unless day.valid?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + "/recurrence_rule/*.rb"].sort.each do |path|
|
2
|
+
require path
|
3
|
+
end
|
4
|
+
|
5
|
+
module RiCal
|
6
|
+
class PropertyValue
|
7
|
+
#- ©2009 Rick DeNatale
|
8
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
9
|
+
#
|
10
|
+
# RiCal::PropertyValue::RecurrenceRule represents an icalendar Recurrence Rule property value
|
11
|
+
# which is defined in
|
12
|
+
# rfc 2445 section 4.3.10 pp 40-45
|
13
|
+
class RecurrenceRule < PropertyValue
|
14
|
+
|
15
|
+
def initialize(parent, value_hash) # :nodoc:
|
16
|
+
@by_list_hash = {}
|
17
|
+
super
|
18
|
+
init_by_lists
|
19
|
+
@by_list_hash = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.convert(parent, value) #:nodoc:
|
23
|
+
if String === value
|
24
|
+
result = new(parent, :value => value)
|
25
|
+
else
|
26
|
+
result = new(parent, value)
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
include Validations
|
32
|
+
include InitializationMethods
|
33
|
+
include EnumerationSupportMethods
|
34
|
+
|
35
|
+
# The integer count value of the receiver, or nil
|
36
|
+
attr_reader :count
|
37
|
+
# The DATE-TIME value of until limit of the receiver, or nil
|
38
|
+
attr_reader :until
|
39
|
+
|
40
|
+
def value=(string) # :nodoc:
|
41
|
+
if string
|
42
|
+
@value = string
|
43
|
+
dup_hash = {}
|
44
|
+
string.split(";").each do |value_part|
|
45
|
+
initialize_from_value_part(value_part, dup_hash)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Set the frequency of the recurrence rule
|
51
|
+
# freq_value:: a String which should be in %w[SECONDLY MINUTELY HOURLY DAILY WEEKLY MONTHLY YEARLY]
|
52
|
+
#
|
53
|
+
# This method resets the receivers list of errors
|
54
|
+
def freq=(freq_value)
|
55
|
+
reset_errors
|
56
|
+
@freq = freq_value
|
57
|
+
end
|
58
|
+
|
59
|
+
# return the frequency of the rule which will be a string
|
60
|
+
def freq
|
61
|
+
@freq.upcase
|
62
|
+
end
|
63
|
+
|
64
|
+
# return the starting week day for the recurrence rule, which for a valid instance will be one of
|
65
|
+
# "SU", "MO", "TU", "WE", "TH", "FR", or "SA"
|
66
|
+
def wkst
|
67
|
+
@wkst || 'MO'
|
68
|
+
end
|
69
|
+
|
70
|
+
def wkst_day # :nodoc:
|
71
|
+
@wkst_day ||= (%w{SU MO TU WE FR SA}.index(wkst) || 1)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Set the starting week day for the recurrence rule, which should be one of
|
75
|
+
# "SU", "MO", "TU", "WE", "TH", "FR", or "SA" for the instance to be valid.
|
76
|
+
# The parameter is however case-insensitive.
|
77
|
+
#
|
78
|
+
# This method resets the receivers list of errors
|
79
|
+
def wkst=(value)
|
80
|
+
reset_errors
|
81
|
+
@wkst = value
|
82
|
+
@wkst_day = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set the count parameter of the recurrence rule, the count value will be converted to an integer using to_i
|
86
|
+
#
|
87
|
+
# This method resets the receivers list of errors
|
88
|
+
|
89
|
+
def count=(count_value)
|
90
|
+
reset_errors
|
91
|
+
@count = count_value
|
92
|
+
@until = nil unless @count.nil? || @by_list_hash
|
93
|
+
end
|
94
|
+
|
95
|
+
# Set the until parameter of the recurrence rule
|
96
|
+
#
|
97
|
+
# until_value:: the value to be set, this may be either a string in RFC 2446 Date or DateTime value format
|
98
|
+
# Or a Date, Time, DateTime, RiCal::PropertyValue::Date, or RiCal::PropertyValue::DateTime
|
99
|
+
#
|
100
|
+
def until=(until_value)
|
101
|
+
reset_errors
|
102
|
+
@until = until_value && until_value.to_ri_cal_date_or_date_time_value
|
103
|
+
@count = nil unless @count.nil? || @by_list_hash
|
104
|
+
end
|
105
|
+
|
106
|
+
# return the INTERVAL parameter of the recurrence rule
|
107
|
+
# This returns an Integer
|
108
|
+
def interval
|
109
|
+
@interval ||= 1
|
110
|
+
end
|
111
|
+
|
112
|
+
# Set the INTERVAL parameter of the recurrence rule
|
113
|
+
#
|
114
|
+
# interval_value:: an Integer
|
115
|
+
#
|
116
|
+
# This method resets the receivers list of errors
|
117
|
+
def interval=(interval_value)
|
118
|
+
reset_errors
|
119
|
+
@interval = interval_value
|
120
|
+
end
|
121
|
+
|
122
|
+
def value
|
123
|
+
@value || to_ical
|
124
|
+
end
|
125
|
+
|
126
|
+
# Return a string containing the RFC 2445 representation of the recurrence rule
|
127
|
+
def to_ical
|
128
|
+
result = ["FREQ=#{freq}"]
|
129
|
+
result << "INTERVAL=#{interval}" unless interval == 1
|
130
|
+
result << "COUNT=#{count}" if count
|
131
|
+
result << "UNTIL=#{self.until.value}" if self.until
|
132
|
+
%w{bysecond byminute byhour byday bymonthday byyearday byweekno bymonth bysetpos}.each do |by_part|
|
133
|
+
val = by_list[by_part.to_sym]
|
134
|
+
result << "#{by_part.upcase}=#{[val].flatten.join(',')}" if val
|
135
|
+
end
|
136
|
+
result << "WKST=#{wkst}" unless wkst == "MO"
|
137
|
+
result.join(";")
|
138
|
+
end
|
139
|
+
|
140
|
+
# Predicate to determine if the receiver generates a bounded or infinite set of occurrences
|
141
|
+
def bounded?
|
142
|
+
@count || @until
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
#- ©2009 Rick DeNatale
|
4
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
# RiCal::PropertyValue::Text represents an icalendar Text property value
|
7
|
+
# which is defined in
|
8
|
+
# rfc 2445 section 4.3.11 pp 45-46
|
9
|
+
class Text < PropertyValue
|
10
|
+
|
11
|
+
# Return the string value of the receiver
|
12
|
+
def ruby_value
|
13
|
+
if value
|
14
|
+
value.gsub(/\\[;,nN\\]/) {|match|
|
15
|
+
case match[1,1]
|
16
|
+
when /[,;\\]/
|
17
|
+
match[1,1]
|
18
|
+
when 'n', 'N'
|
19
|
+
"\n"
|
20
|
+
else
|
21
|
+
match
|
22
|
+
end
|
23
|
+
}
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.convert(parent, string) #:nodoc:
|
30
|
+
ical_str = string.gsub(/\n|,|;/) {|match|
|
31
|
+
if match == "\n"
|
32
|
+
'\n'
|
33
|
+
else
|
34
|
+
"\\#{match}"
|
35
|
+
end
|
36
|
+
}
|
37
|
+
self.new(parent, :value => ical_str)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
#- ©2009 Rick DeNatale
|
4
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
# RiCal::PropertyValue::Uri represents an icalendar Uri property value
|
7
|
+
# which is defined in
|
8
|
+
# rfc 2445 section 4.8.4.6 p 110
|
9
|
+
class Uri < PropertyValue
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RiCal
|
2
|
+
class PropertyValue
|
3
|
+
#- ©2009 Rick DeNatale
|
4
|
+
#- All rights reserved. Refer to the file README.txt for the license
|
5
|
+
#
|
6
|
+
class UtcOffset < PropertyValue # :nodoc:
|
7
|
+
attr_accessor :sign, :hours, :minutes, :seconds
|
8
|
+
|
9
|
+
def value=(string)
|
10
|
+
@value = string
|
11
|
+
parse_match = /([+-])(\d\d)(\d\d)(\d\d)?/.match(string)
|
12
|
+
if parse_match
|
13
|
+
@sign = parse_match[1] == "+" ? 1 : -1
|
14
|
+
@hours = parse_match[2].to_i
|
15
|
+
@minutes = parse_match[3].to_i
|
16
|
+
@seconds = parse_match[4].to_i || 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_seconds
|
21
|
+
@sign * ((((hours*60) + minutes) * 60) + seconds)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_to_date_time_value(date_time_value)
|
25
|
+
date_time_value.advance(:hours => sign * hours, :minutes => sign * minutes, :seconds => sign * minutes)
|
26
|
+
end
|
27
|
+
|
28
|
+
def subtract_from_date_time_value(date_time_value)
|
29
|
+
signum = -1 * sign
|
30
|
+
date_time_value.advance(:hours => signum * hours, :minutes => signum * minutes, :seconds => signum * minutes)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|