Meetup
This exercise is categorized as easy, but it involves some date stuff which is not easy unless one is versed in date stuff in general and dates in ruby specifically.
Solution 1
require 'date'
class Meetup
DAYS_OF_WEEK = {
sunday: 0,
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6,
}.freeze
START_DAYS = {
first: 1,
second: 8,
teenth: 13,
third: 15,
fourth: 22,
#
# We don't know when last starts. Last day of the month
# could be one of 28, 29, 30, or 31. We use `last_start_day`
# to help figure this out.
#
}.freeze
def initialize(month, year)
@month = month
@year = year
end
##
# @param day_of_week [Symbol]
# @param nth [Integer]
#
def day(day_of_week, which)
date = Date.new(@year, @month, start_day(which))
6.times do
break if date.wday == DAYS_OF_WEEK[day_of_week]
date = date.next_day
end
date
end
private
def last_start_day
Date.new(@year, @month).next_month - 7
end
def start_day(which)
return last_start_day.mday if which == :last
START_DAYS[which]
end
end
Solution 2
require 'date'
class Meetup
VERSION = 3
DAYS_OF_WEEK = {
0 => :sunday,
1 => :monday,
2 => :tuesday,
3 => :wednesday,
4 => :thursday,
5 => :friday,
6 => :saturday,
}.freeze
def initialize(month, year)
@month = month
@year = year
@days_in_month = (Date.new(year, 12, 31) << (12 - month)).day
@calendar = {
first: [*1..7],
second: [*8..14],
third: [*15..21],
fourth: [*22..28],
teenth: [*13..19],
last: [*(@days_in_month - 6)..@days_in_month],
}
end
def day(weekday, schedule)
section_of_month = @calendar[schedule]
is_weekday = lambda do |n|
DAYS_OF_WEEK[Date.new(@year, @month, n).wday] == weekday
end
Date.new(@year, @month, section_of_month.find(&is_weekday))
end
end
Solution 3
Solution by Lapizistik on Ruby Discord server.
require 'date'
class Meetup
##
# Map the symbols to the week numbers.
#
DAYS_OF_WEEK = %i[
sunday monday tuesday wednesday thursday friday saturday
].map.with_index { |d, i| [d, i] }.to_h
def initialize(month, year)
@month = month
@year = year
days_in_month = (Date.new(year, month).next_month - 1).day
@schedules = {
first: 1,
second: 8,
third: 15,
fourth: 22,
teenth: 13,
last: days_in_month - 6,
}
end
def day(weekday, schedule)
start = @schedules[schedule]
wday = DAYS_OF_WEEK[weekday]
d = Date.new(@year, @month, start)
d + (wday - d.wday) + (wday < d.wday ? 7 : 0)
end
end
Lap
If the day of week targeted is after the start day of the schedule then I just use it. So if my schedule starts with Tuesday (2) and I want a Wednesday (3) I just need to add one day (3 - 1). But if my schedule starts on Friday (5) and I want a Monday (1) I would need to go back 4 days (5 - 1) but then I would end up before my start day, so I just add a full week (7) to get the next Monday.
Solution 4
require 'date'
class Meetup
VERSION = 5
CALENDAR_SCHEDULES = {
first: 1,
second: 8,
third: 15,
fourth: 22,
teenth: 13,
}.freeze
DAYS_OF_WEEK = %i[
sunday monday tuesday wednesday thursday friday saturday
].map.with_index { |d, i| [d, i] }.to_h
def initialize(month, year)
@month = month
@year = year
##
# The last schedule is month-specific.
#
days_in_month = (Date.new(year, month).next_month - 1).day
@last_schedule = days_in_month - 6
end
# 2024, 2, :second
# 2024, 2,
def day(weekday, schedule)
date_schedule_starts = Date.new(@year, @month, schedule_start(schedule))
wday = DAYS_OF_WEEK[weekday]
(1)
wday += 7 if wday < date_schedule_starts.wday
date_schedule_starts + (wday - date_schedule_starts.wday)
end
def schedule_start(schedule)
return @last_schedule if schedule == :last
CALENDAR_SCHEDULES[schedule] or
raise "Unknown schedule: “#{schedule.inspect}”"
end
end
1 | If the day of week we target is \(\ge\) the start day of our schedule then I just use it. So if the schedule starts with Tuesday (2) and we want a Wednesday (3) I just need to add one day \(3 - 1\). |
But if our schedule starts on Friday (5) and we want a Monday (1), we would need to go back 4 days \(5 - 1\), but then we would end up before our start day, so we just add a full week (7) to get the next Monday.