/
readable_recurrences.rb
107 lines (92 loc) · 3.33 KB
/
readable_recurrences.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
require "date"
module ReadableRecurrences
extend self
def find(dates)
match_recurrences(dates)
end
#sorts the days of the month into a nested hash based on year then month then
#day of week
def sort_dates(parsed_dates)
date_hasher = lambda {|h, k| h[k] = Hash.new(&date_hasher)}
parsed_dates.inject(Hash.new(&date_hasher)) do |hsh, d|
if hsh[d.year][d.month][d.wday].kind_of?(Array)
hsh[d.year][d.month][d.wday] << d.day
else
hsh[d.year][d.month][d.wday] = [d.day]
end
hsh
end
end
#parses the array of strings into Date objects
def parse_dates(dates)
dates.inject([]) {|arr, s| arr << Date.parse(s); arr;}
end
def match_recurrences(dates)
parsed_dates = parse_dates(dates)
sorted_dates = sort_dates(parsed_dates)
recurrence_schedules = []
recurrence_schedules += match_weekly(sorted_dates)
if recurrence_schedules.empty?
recurrence_schedules += match_first_and_third(sorted_dates)
recurrence_schedules += match_second_and_fourth(sorted_dates)
end
recurrence_schedules.join(' and ')
end
#matches every week occurrences
def match_weekly(sorted_dates)
matches = []
sorted_dates.keys.each do |year|
sorted_dates[year].keys.each do |month|
sorted_dates[year][month].keys.each do |weekday|
dates = sorted_dates[year][month][weekday]
if dates.size == days_of_week_count_for_month(month, year, weekday)
matches << "Every #{Date::DAYNAMES[weekday]} in #{Date::MONTHNAMES[month]} #{year}"
end
end
end
end
matches
end
def match_first_and_third(sorted_dates)
matches = []
sorted_dates.keys.each do |year|
sorted_dates[year].keys.each do |month|
sorted_month = sort_dates(all_dates_in_month(month, year))[year][month]
sorted_dates[year][month].keys.each do |weekday|
dates = sorted_dates[year][month][weekday]
if (dates.size == 2) && (sorted_month[weekday][0] == dates[0]) && (sorted_month[weekday][2] == dates[1])
matches << "First and Third #{Date::DAYNAMES[weekday]} in #{Date::MONTHNAMES[month]} #{year}"
end
end
end
end
matches
end
def match_second_and_fourth(sorted_dates)
matches = []
sorted_dates.keys.each do |year|
sorted_dates[year].keys.each do |month|
sorted_month = sort_dates(all_dates_in_month(month, year))[year][month]
sorted_dates[year][month].keys.each do |weekday|
dates = sorted_dates[year][month][weekday]
if (dates.size == 2) && (sorted_month[weekday][1] == dates[0]) && (sorted_month[weekday][3] == dates[1])
matches << "Second and Fourth #{Date::DAYNAMES[weekday]} in #{Date::MONTHNAMES[month]} #{year}"
end
end
end
end
matches
end
# Returns the count for a given day of the week in a given month,
# i.e. there are 5 Tuesdays in November 2011
def days_of_week_count_for_month(month, year, day_of_week)
whole_month = all_dates_in_month(month, year)
sort_dates(whole_month)[year][month][day_of_week].size
end
# returns an Array of Date objects for all days in the month
def all_dates_in_month(month, year)
first_day = Date.civil(year, month, 1)
last_day = Date.civil(year, month, -1)
(first_day..last_day).to_a
end
end