Class: Recurrence
- Inherits:
-
Object
- Object
- Recurrence
- Includes:
- Comparable
- Defined in:
- lib/recurable/recurrence.rb
Overview
Core model representing an iCal RRULE recurrence pattern.
Pure Ruby data class — no Rails dependencies. Handles RRULE string generation/parsing with named attributes and frequency comparison.
recurrence = Recurrence.new(frequency: 'DAILY', interval: 1)
recurrence.rrule # => "FREQ=DAILY;INTERVAL=1"
recurrence.daily? # => true
Constant Summary collapse
- DAYS_OF_WEEK =
iCal BYDAY codes derived from Date::DAYNAMES, ordered Sunday–Saturday. Exposes class constants: Recurrence::SUNDAY => ‘SU’, Recurrence::MONDAY => ‘MO’, etc. ‘const_set` returns the value of the constant and map returns an array of the transformed values.
Date::DAYNAMES.map { |name| const_set(name.upcase, name[0, 2].upcase) }.freeze
- FREQUENCIES =
Ordered by increasing frequency. Values are approximate period in days. Order used by Comparable#<=> for RruleAdapter strategy selection. Exposes class constants: Recurrence::YEARLY, Recurrence::DAILY, etc. and defines frequency predicates.
{ 'YEARLY' => 365, 'MONTHLY' => 31, 'WEEKLY' => 7, 'DAILY' => 1, 'HOURLY' => 1 / 24.0, 'MINUTELY' => 1 / 24.0 / 60.0 }.each_key do |freq| const_set(freq, freq) define_method(:"#{freq.downcase}?") { freq == frequency } end.freeze
- FREQ_ORDER =
FREQUENCIES.keys.each_with_index.to_h.freeze
- MONTHLY_OPTIONS =
Exposes class constants: Recurrence::MONTHLY_DATE => ‘DATE’, Recurrence::MONTHLY_NTH_DAY => ‘NTH_DAY’.
%w[DATE NTH_DAY].each { |opt| const_set("MONTHLY_#{opt}", opt) }.freeze
- NTH_DAY_OF_MONTH =
Maps symbolic positions to iCal BYSETPOS integers. Positive 1–4 covers typical forward positions; negative -1/-2 covers “last” and “second to last” (deeper negatives are better expressed counting forward). A month has at most 5 of any single weekday.
{ first: 1, second: 2, third: 3, fourth: 4, last: -1, second_to_last: -2 }.freeze
- DATE_OF_MONTH_RANGE =
Positive = calendar date (1st–28th), negative = from end (-1 = last day, -2 = second to last). Capped at ±28 because February has 28 days in a common year.
((-28..-1).to_a + (1..28).to_a).freeze
- HOUR_OF_DAY_RANGE =
0..23
- MINUTE_OF_HOUR_RANGE =
SECOND_OF_MINUTE_RANGE = 0..59
- MONTH_OF_YEAR_RANGE =
1..12
- DAY_OF_YEAR_RANGE =
((-366..-1).to_a + (1..366).to_a).freeze
- WEEK_OF_YEAR_RANGE =
((-53..-1).to_a + (1..53).to_a).freeze
- BYDAY_PATTERN =
/\A[+-]?\d*(?:#{Regexp.union(DAYS_OF_WEEK).source})\z/- UNTIL_PATTERN =
Parses an RRULE UNTIL string (e.g. “20261231T235959Z”) into a Time object.
/\A(?<Y>\d{4})(?<m>\d{2})(?<d>\d{2})T(?<H>\d{2})(?<M>\d{2})(?<S>\d{2})Z\z/- ATTRIBUTES =
%i[ by_day by_month_day by_set_pos count day_of_year frequency hour_of_day interval minute_of_hour month_of_year repeat_until second_of_minute week_of_year week_start ].freeze
- ARRAY_ATTRIBUTES =
%i[ by_day by_month_day by_set_pos day_of_year hour_of_day minute_of_hour month_of_year second_of_minute week_of_year ].freeze
Class Method Summary collapse
Instance Method Summary collapse
- #<=>(other) ⇒ Object
- #by_month_day_option? ⇒ Boolean
- #by_set_pos_option? ⇒ Boolean
-
#initialize(**attrs) ⇒ Recurrence
constructor
A new instance of Recurrence.
- #monthly_option ⇒ Object
- #repeat_until=(value) ⇒ Object
- #to_rrule ⇒ Object
Constructor Details
#initialize(**attrs) ⇒ Recurrence
Returns a new instance of Recurrence.
19 20 21 22 23 24 |
# File 'lib/recurable/recurrence.rb', line 19 def initialize(**attrs) unknown = attrs.keys - ATTRIBUTES raise ArgumentError, "Unknown attribute(s): #{unknown.join(', ')}" if unknown.any? attrs.each { |attr, value| public_send(:"#{attr}=", value) } end |
Class Method Details
.from_rrule(rrule) ⇒ Object
87 88 89 |
# File 'lib/recurable/recurrence.rb', line 87 def from_rrule(rrule) new(**attributes_from(parse_components(rrule))) end |
Instance Method Details
#<=>(other) ⇒ Object
178 179 180 181 182 |
# File 'lib/recurable/recurrence.rb', line 178 def <=>(other) return super unless other.is_a?(self.class) FREQ_ORDER[frequency] <=> FREQ_ORDER[other.frequency] end |
#by_month_day_option? ⇒ Boolean
175 |
# File 'lib/recurable/recurrence.rb', line 175 def by_month_day_option? = monthly_option == 'DATE' |
#by_set_pos_option? ⇒ Boolean
176 |
# File 'lib/recurable/recurrence.rb', line 176 def by_set_pos_option? = monthly_option == 'NTH_DAY' |
#monthly_option ⇒ Object
168 169 170 171 172 173 |
# File 'lib/recurable/recurrence.rb', line 168 def monthly_option return unless frequency == 'MONTHLY' return 'NTH_DAY' if by_set_pos&.any? && by_day&.any? 'DATE' if by_month_day&.any? end |
#repeat_until=(value) ⇒ Object
141 142 143 144 145 146 147 |
# File 'lib/recurable/recurrence.rb', line 141 def repeat_until=(value) @repeat_until = case value when nil, '' then nil when Time then value.utc when String then parse_until(value) end end |
#to_rrule ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/recurable/recurrence.rb', line 149 def to_rrule { 'FREQ' => frequency, 'INTERVAL' => interval, 'COUNT' => non_blank(count), 'UNTIL' => format_until(repeat_until), 'BYDAY' => join_list(by_day), 'BYMONTHDAY' => join_list(by_month_day), 'BYMONTH' => join_list(month_of_year), 'BYHOUR' => join_list(hour_of_day), 'BYMINUTE' => join_list(minute_of_hour), 'BYSECOND' => join_list(second_of_minute), 'BYYEARDAY' => join_list(day_of_year), 'BYWEEKNO' => join_list(week_of_year), 'BYSETPOS' => join_list(by_set_pos), 'WKST' => non_blank(week_start) }.filter_map { |k, v| "#{k}=#{v}" unless v.nil? }.join(';') end |