Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thomas07vt issues/21 multiple monthly days #3

Merged
merged 2 commits into from
Jul 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ r = Recurrence.weekly(on: :thursday)
# Monthly by month day
r = Recurrence.new(every: :month, on: 15)
r = Recurrence.new(every: :month, on: 31)
r = Recurrence.new(:every => :month, :on => [15, 31])
r = Recurrence.new(every: :month, on: 7, interval: 2)
r = Recurrence.new(every: :month, on: 7, interval: :monthly)
r = Recurrence.new(every: :month, on: 7, interval: :bimonthly)
Expand Down Expand Up @@ -79,6 +80,7 @@ r = Recurrence.new(every: :day, except: [Date.current, '2010-01-31'])
r = Recurrence.new(every: :month, on: 1, handler: Proc.new { |day, month, year| raise("Date not allowed!") if year == 2011 && month == 12 && day == 31 })

# Shift the recurrences to maintain dates around boundaries (Jan 31 -> Feb 28 -> Mar 28)
# Shift cannot be used with multiple month days e.g: :on => [1,15]
r = Recurrence.new(every: :month, on: 31, shift: true)

# Getting an array with all events
Expand Down
2 changes: 2 additions & 0 deletions lib/recurrence/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module Event # :nodoc: all
require "recurrence/event/base"
require "recurrence/event/daily"
require "recurrence/event/monthly"
require "recurrence/event/monthly/monthday"
require "recurrence/event/monthly/weekday"
require "recurrence/event/weekly"
require "recurrence/event/yearly"
end
Expand Down
23 changes: 3 additions & 20 deletions lib/recurrence/event/monthly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,12 @@ class Monthly < Base # :nodoc: all

private def validate
if @options.key?(:weekday)

# Allow :on => :last, :weekday => :thursday contruction.
if @options[:on].to_s == "last"
@options[:on] = 5
elsif @options[:on].is_a?(Numeric)
valid_week?(@options[:on])
else
valid_ordinal?(@options[:on])
@options[:on] = ORDINALS.index(@options[:on].to_s) + 1
end

@options[:weekday] =
valid_weekday_or_weekday_name?(@options[:weekday])
extend Weekday
else
valid_month_day?(@options[:on])
extend Monthday
end

return unless @options[:interval].is_a?(Symbol)

valid_interval?(@options[:interval])
@options[:interval] = INTERVALS[@options[:interval]]
validate_and_prepare!
end

private def next_in_recurrence
Expand Down Expand Up @@ -100,8 +85,6 @@ class Monthly < Base # :nodoc: all
weeks = (date.day - 1).div(7) + 1
date -= weeks * 7
end

@options[:handler].call(date.day, date.month, date.year)
end

private def shift_to(date)
Expand Down
59 changes: 59 additions & 0 deletions lib/recurrence/event/monthly/monthday.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
class Recurrence_
module Event
class Monthly < Base
module Monthday
def advance(date, interval=@options[:interval])
if initialized? && @_day_count > @_day_pointer += 1
@options[:handler].call(
@options[:on][@_day_pointer],
date.month,
date.year
)
else
@_day_pointer = 0

# Have a raw month from 0 to 11 interval
raw_month = date.month + interval - 1

next_year = date.year + raw_month.div(12)
next_month = (raw_month % 12) + 1 # change back to ruby interval

@options[:handler].call(
@options[:on][@_day_pointer],
next_month,
next_year
)
end
end

def validate_and_prepare!
@options[:on] = Array.wrap(@options[:on]).map do |day|
valid_month_day?(day)
day
end.sort

valid_shift_options?

if @options[:interval].kind_of?(Symbol)
valid_interval?(@options[:interval])
@options[:interval] = INTERVALS[@options[:interval]]
end

@_day_pointer = 0
@_day_count = @options[:on].length
end

def valid_shift_options?
if @options[:shift] && @options[:on].length > 1
raise ArgumentError, "Invalid options. Unable to use :shift with multiple :on days"
end
end

def shift_to(date)
@options[:on][0] = date.day
end
end
end
end
end

53 changes: 53 additions & 0 deletions lib/recurrence/event/monthly/weekday.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
class Recurrence_
module Event
class Monthly < Base
module Weekday
def advance(date, interval=@options[:interval])
raw_month = date.month + interval - 1
next_year = date.year + raw_month.div(12)
next_month = (raw_month % 12) + 1 # change back to ruby interval
date = Date.new(next_year, next_month, 1)

weekday, month = @options[:weekday], date.month

# Adjust week day
to_add = weekday - date.wday
to_add += 7 if to_add < 0
to_add += (@options[:on] - 1) * 7
date += to_add

# Go to the previous month if we lost it
if date.month != month
weeks = (date.day - 1).div(7) + 1
date -= weeks * 7
end

@options[:handler].call(date.day, date.month, date.year)
end

def validate_and_prepare!
# Allow :on => :last, :weekday => :thursday contruction.
if @options[:on].to_s == "last"
@options[:on] = 5
elsif @options[:on].kind_of?(Numeric)
valid_week?(@options[:on])
else
valid_ordinal?(@options[:on])
@options[:on] = Monthly::ORDINALS.index(@options[:on].to_s) + 1
end

@options[:weekday] = valid_weekday_or_weekday_name?(@options[:weekday])

if @options[:interval].kind_of?(Symbol)
valid_interval?(@options[:interval])
@options[:interval] = INTERVALS[@options[:interval]]
end
end

def shift_to(date)
@options[:on] = date.day
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/recurrence/namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def self.weekly(options = {})
# Create a monthly recurrence.
#
# Recurrence.monthly(on: 15) #=> every 15th day
# Recurrence.monthly(:on => [1, 15]) #=> every 1st and 15th day
# Recurrence.monthly(on: :first, :weekday => :sunday)
# Recurrence.monthly(on: :second, :weekday => :sunday)
# Recurrence.monthly(on: :third, :weekday => :sunday)
Expand Down
26 changes: 26 additions & 0 deletions test/recurrence/monthly_recurring/day_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,30 @@ class MonthlyRecurringDayTest < Minitest::Test
assert r.events.include?(7.months.from_now.to_date)
refute r.events.include?(8.months.from_now.to_date)
end

test "allows multiple days" do
r = recurrence(
every: :month,
on: [1, 15],
starts: "2017-05-01",
until: "2017-06-30"
)
assert_equal "2017-05-01", r.events[0].to_s
assert_equal "2017-05-15", r.events[1].to_s
assert_equal "2017-06-01", r.events[2].to_s
assert_equal "2017-06-15", r.events[3].to_s
assert_nil r.events[4]
end

test "raises when :shift is true and :on is multiple days" do
assert_raises(ArgumentError) {
recurrence(
every: :month,
on: [1, 15],
starts: "2017-05-01",
until: "2017-06-30",
shift: true
)
}
end
end