ggoodale-recurring 0.5.3 → 0.5.4
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 +1 -1
- data/Rakefile +1 -1
- data/lib/date_language.rb +25 -0
- data/lib/schedule.rb +447 -0
- metadata +4 -3
data/History.txt
CHANGED
data/Rakefile
CHANGED
@@ -23,7 +23,7 @@ specification = Gem::Specification.new do |s|
|
|
23
23
|
s.homepage = %q{http://jchris.mfdz.com}
|
24
24
|
s.description = %q{Recurring allows you to define Schedules, which can tell you whether or not a given Time falls in the Schedule, as well as being able to return a list of times which match the Schedule within a given range.}
|
25
25
|
s.authors = ["Chris Anderson"]
|
26
|
-
|
26
|
+
s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/recurring.rb", "lib/date_language.rb", "lib/schedule.rb", "spec/date_language_spec.rb", "spec/schedule_spec.rb"]
|
27
27
|
s.add_development_dependency(%q<rspec>, [">= 0.7.4"])
|
28
28
|
s.has_rdoc = true
|
29
29
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Recurring
|
2
|
+
|
3
|
+
|
4
|
+
# A wrapper for Schedule which allows its arguments to be designated in a block. _Under Construction_
|
5
|
+
class DateLanguage
|
6
|
+
class << self
|
7
|
+
def tell &block
|
8
|
+
x = self.new
|
9
|
+
x.instance_eval &block
|
10
|
+
x
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :frequency
|
15
|
+
|
16
|
+
def every(frequency=1, unit=nil, options={})
|
17
|
+
@frequency = frequency
|
18
|
+
end
|
19
|
+
|
20
|
+
def times string
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
data/lib/schedule.rb
ADDED
@@ -0,0 +1,447 @@
|
|
1
|
+
module Recurring
|
2
|
+
|
3
|
+
VERSION = '0.5.4'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# returns a number starting with 1. Needs to assume that weeks start on sunday
|
7
|
+
# for beginning_of_next to work at the scale of weeks.
|
8
|
+
def week_in_month date
|
9
|
+
# Work out the first date in the month
|
10
|
+
first_of_month = date - ((date.day - 1) * 86400)
|
11
|
+
|
12
|
+
# If the month starts on a Sunday, we're good.
|
13
|
+
if first_of_month.wday == 0
|
14
|
+
adjusted_day = date.day
|
15
|
+
else
|
16
|
+
# Otherwise, we need to offset by whatever partial week starts this month.
|
17
|
+
adjusted_day = date.day + first_of_month.wday
|
18
|
+
end
|
19
|
+
(((adjusted_day - 1).to_f / 7.0) + 1).floor
|
20
|
+
end
|
21
|
+
|
22
|
+
def negative_week_in_month date
|
23
|
+
end_of_month = (date.month < 12 ? Time.utc(date.year, date.month+1) : Time.utc(date.year + 1)) - 3600
|
24
|
+
|
25
|
+
(((end_of_month.day - date.day).to_f / 7.0) + 1).floor * -1
|
26
|
+
end
|
27
|
+
|
28
|
+
# just a wrapper for strftime
|
29
|
+
def week_of_year date
|
30
|
+
date.strftime('%U').to_i
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Initialize a Schedule object with the proper options to calculate occurances in
|
36
|
+
# that schedule. Schedule#new take a hash of options <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
|
37
|
+
#
|
38
|
+
# = Yearly
|
39
|
+
# [Every two years from an anchor time] <tt>Recurring::Schedule.new :unit => 'years', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
40
|
+
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
41
|
+
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
42
|
+
# = Monthly
|
43
|
+
# [Every two months from an anchor time] <tt>Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
44
|
+
# [The first and fifteenth of every month] <tt>Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]</tt>
|
45
|
+
# [The first and eighteenth of every third month] <tt>Recurring::Schedule.new :unit => 'months', :frequency => 3, :anchor => Time.utc(2006,4,15,10,30), :monthdays => [10,18]</tt>
|
46
|
+
# [The third Monday of every month at 6:30pm] <tt>Recurring::Schedule.new :unit => 'months', :weeks => 3, :weekdays => :monday, :times => '6:30pm'</tt>
|
47
|
+
# = Weekly
|
48
|
+
# [Monday, Wednesday, and Friday of every week] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => %w{monday weds friday}</tt>
|
49
|
+
# [Every week at the same time on the same day of the week as the anchor (Weds at 5:30pm)] <tt>Recurring::Schedule.new :unit => 'weeks', :anchor => Time.utc(2006,12,6,17,30)</tt>
|
50
|
+
# [Equivalently, Every Wednesday at 5:30] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => 'weds', :times => '5:30pm'</tt>
|
51
|
+
# = Daily
|
52
|
+
# [Everyday at the time of the anchor] <tt>Recurring::Schedule.new :unit => 'days', :anchor => Time.utc(2006,11,1,10,15,22)</tt>
|
53
|
+
# [Everyday at 7am and 5:45:20pm] <tt>Recurring::Schedule.new :unit => 'days', :times => '7am 5:45:20pm'</tt>
|
54
|
+
# = Hourly
|
55
|
+
# [Every hour at 15 minutes, 30 minutes, and 45 minutes and 30 seconds] <tt>Recurring::Schedule.new :unit => 'hours', :times => '0:15 4:30 0:45:30'</tt>
|
56
|
+
# [Offset every 2 hours from the anchor] <tt>Recurring::Schedule.new :unit => 'hours', :anchor => Time.utc(2001,5,15,11,17)</tt>
|
57
|
+
# = Minutely
|
58
|
+
# [Every five minutes offset from the anchor] <tt>Recurring::Schedule.new :unit => 'minutes', :frequency => 5, :anchor => Time.utc(2006,9,1,10,30)</tt>
|
59
|
+
# [Every minute at second 15] <tt>Recurring::Schedule.new :unit => 'minutes', :times => '0:0:15'</tt>
|
60
|
+
#
|
61
|
+
# See the specs using "rake spec" for even more examples.
|
62
|
+
class Schedule
|
63
|
+
|
64
|
+
attr_reader :unit, :frequency, :anchor, :months, :weeks, :monthdays, :weekdays, :times
|
65
|
+
|
66
|
+
# Options hash has keys <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
|
67
|
+
# * valid values for :unit are <tt>years, months, weeks, days, hours, minutes</tt>
|
68
|
+
# * :frequency defaults to 1
|
69
|
+
# * :anchor is required if the frequency is other than one
|
70
|
+
# * :weeks alongside :weekdays is used to specify the nth instance of a weekday in a month.
|
71
|
+
# * :weekdays takes an array of strings like <tt>%w{monday weds friday}</tt>
|
72
|
+
# * :monthdays takes an array of days of the month, eg. <tt>[1,7,15]</tt>
|
73
|
+
# * :times takes a string with a simple format. <tt>"4pm 5:15pm 6:45:30pm"</tt>
|
74
|
+
def initialize options
|
75
|
+
raise ArgumentError, 'specify a valid unit' unless options[:unit] &&
|
76
|
+
%w{years months weeks days hours minutes}.include?(options[:unit])
|
77
|
+
raise ArgumentError, 'frequency > 1 requires an anchor Time' if options[:frequency] && options[:frequency] != 1 && !options[:anchor]
|
78
|
+
@unit = options[:unit].to_sym
|
79
|
+
raise ArgumentError, 'weekdays are required with the weeks param, if there are times params' if @unit == :weeks &&
|
80
|
+
options[:times] &&
|
81
|
+
!options[:weekdays]
|
82
|
+
@frequency = options[:frequency] || 1
|
83
|
+
@anchor = options[:anchor]
|
84
|
+
@times = parse_times options[:times]
|
85
|
+
if options[:months]
|
86
|
+
@month_args = Array(options[:months]).collect{|d|d.to_s.downcase.to_sym}
|
87
|
+
raise ArgumentError, 'provide valid months' unless @month_args.all?{|m|ordinal_month(m)}
|
88
|
+
@months = @month_args.collect{|m|ordinal_month(m)}
|
89
|
+
end
|
90
|
+
|
91
|
+
@weeks = Array(options[:weeks]).collect{|n|n.to_i} if options[:weeks]
|
92
|
+
if options[:weekdays]
|
93
|
+
@weekdays_args = Array(options[:weekdays]).collect{|d|d.to_s.downcase.to_sym}
|
94
|
+
raise ArgumentError, 'provide valid weekdays' unless @weekdays_args.all?{|w|ordinal_weekday(w)}
|
95
|
+
@weekdays = @weekdays_args.collect{|w|ordinal_weekday(w)}
|
96
|
+
end
|
97
|
+
@monthdays = Array(options[:monthdays]).collect{|n|n.to_i} if options[:monthdays]
|
98
|
+
|
99
|
+
|
100
|
+
@anchor_multiple = options[:times].nil? && options[:weeks].nil? && options[:weekdays].nil? && options[:monthdays].nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
def timeout! time
|
104
|
+
@timeout = time
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns true or false depending on whether or not the time is included in the schedule.
|
108
|
+
def include? date
|
109
|
+
@resolution = nil
|
110
|
+
return true if check_anchor? && date == @anchor
|
111
|
+
return mismatch(:year) unless year_matches?(date) if @unit == :years
|
112
|
+
return mismatch(:month) unless month_matches?(date) if [:months, :years].include?(@unit)
|
113
|
+
return mismatch(:week) unless week_matches?(date) if [:years, :months, :weeks].include?(@unit)
|
114
|
+
if [:years, :months, :weeks, :days].include?(@unit)
|
115
|
+
return mismatch(:day) unless day_matches?(date)
|
116
|
+
return mismatch(:time) unless time_matches?(date)
|
117
|
+
end
|
118
|
+
if @unit == :hours
|
119
|
+
return mismatch(:hour) unless hour_matches?(date)
|
120
|
+
return mismatch(:sub_hour) unless sub_hour_matches?(date)
|
121
|
+
end
|
122
|
+
if @unit == :minutes
|
123
|
+
return mismatch(:minute) unless minute_matches?(date)
|
124
|
+
return mismatch(:second) unless second_matches?(date)
|
125
|
+
end
|
126
|
+
@resolution = nil
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Starts from the argument time, and returns the next included time. Returns the argument if it is included in the schedule.
|
131
|
+
def find_next date
|
132
|
+
loop do
|
133
|
+
return date if include?(date)
|
134
|
+
#puts "#{@resolution} : #{date}"
|
135
|
+
date = beginning_of_next @resolution, date
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Starts from the argument time, and works backwards until it hits a time that is included
|
140
|
+
def find_previous date
|
141
|
+
loop do
|
142
|
+
return date if include?(date)
|
143
|
+
#puts "#{@resolution} : #{date}"
|
144
|
+
date = end_of_previous @resolution, date
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Takes a range which responds to <tt>first</tt> and <tt>last</tt>, returning Time objects. The arguments need only to be duck-type compatible with Time#year, #month, #day, #hour, #min, #sec, #wday etc.
|
149
|
+
#
|
150
|
+
# <tt>rs.find_in_range(Time.now, Time.now+24*60*60)</tt>
|
151
|
+
#
|
152
|
+
# or
|
153
|
+
#
|
154
|
+
# <tt>range = (Time.now..Time.now+24*60*60)</tt>
|
155
|
+
#
|
156
|
+
# <tt>rs.find_in_range(range)</tt>
|
157
|
+
def find_in_range *args
|
158
|
+
if args[0].respond_to?(:first) && args[0].respond_to?(:last)
|
159
|
+
t_start = args[0].first
|
160
|
+
t_end = args[0].last
|
161
|
+
else
|
162
|
+
t_start = args[0]
|
163
|
+
t_end = args[1]
|
164
|
+
end
|
165
|
+
opts = args.last if args.last.respond_to?(:keys)
|
166
|
+
if opts
|
167
|
+
limit = opts[:limit]
|
168
|
+
end
|
169
|
+
result = []
|
170
|
+
count = 1
|
171
|
+
loop do
|
172
|
+
rnext = find_next t_start
|
173
|
+
break if count > limit if limit
|
174
|
+
break if rnext > t_end
|
175
|
+
result << rnext
|
176
|
+
t_start = rnext + 1
|
177
|
+
count += 1
|
178
|
+
end
|
179
|
+
result
|
180
|
+
end
|
181
|
+
|
182
|
+
# Two Schedules are equal if they have the same attributes.
|
183
|
+
def == other
|
184
|
+
return false unless self.class == other.class
|
185
|
+
[:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times].all? do |attribute|
|
186
|
+
self.send(attribute) == other.send(attribute)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def end_of_previous scope, date
|
193
|
+
case scope
|
194
|
+
when :year
|
195
|
+
Time.utc(date.year) - 1
|
196
|
+
when :month
|
197
|
+
Time.utc(date.year, date.month) -1
|
198
|
+
when :week
|
199
|
+
to_sunday = date.wday
|
200
|
+
previous_week = (date - to_sunday*24*60*60)
|
201
|
+
Time.utc(previous_week.year, previous_week.month, previous_week.day) - 1
|
202
|
+
when :day
|
203
|
+
Time.utc(date.year, date.month, date.day) - 1
|
204
|
+
when :time
|
205
|
+
previous_time date
|
206
|
+
else
|
207
|
+
date - 1
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
def beginning_of_next scope, date
|
213
|
+
case scope
|
214
|
+
when :year
|
215
|
+
Time.utc(date.year + 1)
|
216
|
+
when :month
|
217
|
+
date.month < 12 ? Time.utc(date.year, date.month+1) : beginning_of_next(:year, date)
|
218
|
+
when :week
|
219
|
+
to_sunday = 7 - date.wday
|
220
|
+
next_week = (date + to_sunday*24*60*60)
|
221
|
+
next_week = next_week - ((next_week.day - 1) * 86400) if next_week.month != date.month
|
222
|
+
Time.utc(next_week.year, next_week.month, next_week.day)
|
223
|
+
when :day
|
224
|
+
dayp = date + (24*60*60)
|
225
|
+
Time.utc(dayp.year, dayp.month, dayp.day)
|
226
|
+
when :time
|
227
|
+
next_time date
|
228
|
+
when :hour
|
229
|
+
date.hour < 23 ? Time.utc(date.year, date.month, date.day, date.hour+1) : beginning_of_next(:day, date)
|
230
|
+
when :sub_hour
|
231
|
+
next_sub_hour date
|
232
|
+
else
|
233
|
+
date + 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def previous_time date
|
238
|
+
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
239
|
+
my_times = times + [me]
|
240
|
+
my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
241
|
+
my_times.sort! do |a,b|
|
242
|
+
v = a[:hour] <=> b[:hour]
|
243
|
+
v = a[:minute] <=> b[:minute] if v == 0
|
244
|
+
v = a[:second] <=> b[:second] if v == 0
|
245
|
+
v
|
246
|
+
end
|
247
|
+
my_times.reverse!
|
248
|
+
ntime = my_times[my_times.index(me)+1]
|
249
|
+
if ntime
|
250
|
+
Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
|
251
|
+
else
|
252
|
+
end_of_previous :day, date
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def next_time date
|
257
|
+
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
258
|
+
my_times = times + [me]
|
259
|
+
my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
260
|
+
my_times.sort! do |a,b|
|
261
|
+
v = a[:hour] <=> b[:hour]
|
262
|
+
v = a[:minute] <=> b[:minute] if v == 0
|
263
|
+
v = a[:second] <=> b[:second] if v == 0
|
264
|
+
v
|
265
|
+
end
|
266
|
+
ntime = my_times[my_times.index(me)+1]
|
267
|
+
if ntime
|
268
|
+
Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
|
269
|
+
else
|
270
|
+
beginning_of_next :day, date
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def next_sub_hour date
|
275
|
+
me = {:minute => date.min, :second => date.sec, :me => true}
|
276
|
+
my_times = times + [me]
|
277
|
+
my_times += [{:minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
278
|
+
my_times.sort! do |a,b|
|
279
|
+
v = a[:minute] <=> b[:minute]
|
280
|
+
v = a[:second] <=> b[:second] if v == 0
|
281
|
+
v
|
282
|
+
end
|
283
|
+
ntime = my_times[my_times.index(me)+1]
|
284
|
+
if ntime
|
285
|
+
Time.utc(date.year, date.month, date.day, date.hour, ntime[:minute], ntime[:second])
|
286
|
+
else
|
287
|
+
beginning_of_next :hour, date
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def mismatch unit
|
292
|
+
@resolution = unit
|
293
|
+
false
|
294
|
+
end
|
295
|
+
|
296
|
+
def year_matches? date
|
297
|
+
return true if @frequency == 1
|
298
|
+
(date.year - @anchor.year) % @frequency == 0
|
299
|
+
end
|
300
|
+
|
301
|
+
def month_matches? date
|
302
|
+
if @unit == :months
|
303
|
+
return true if @frequency == 1
|
304
|
+
years_in_months = (date.year - @anchor.year) * 12
|
305
|
+
diff_months = date.month - @anchor.month
|
306
|
+
return (years_in_months + diff_months) % @frequency == 0
|
307
|
+
elsif @months
|
308
|
+
return @months.include?(date.month)
|
309
|
+
else
|
310
|
+
return false if date.month != @anchor.month
|
311
|
+
end
|
312
|
+
|
313
|
+
true
|
314
|
+
end
|
315
|
+
|
316
|
+
def week_matches? date
|
317
|
+
if @unit == :weeks
|
318
|
+
return true if @frequency == 1
|
319
|
+
return ((Recurring.week_of_year(date) - Recurring.week_of_year(@anchor)) % @frequency) == 0
|
320
|
+
end
|
321
|
+
if @weeks
|
322
|
+
@weeks.include?(Recurring.week_in_month(date)) || @weeks.include?(Recurring.negative_week_in_month(date))
|
323
|
+
else
|
324
|
+
true
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def day_matches? date
|
329
|
+
if @unit == :days
|
330
|
+
return true if @frequency == 1
|
331
|
+
diff = Time.utc(date.year, date.month, date.day) - Time.utc(@anchor.year, @anchor.month, @anchor.day)
|
332
|
+
return (diff / 86400) % @frequency == 0
|
333
|
+
end
|
334
|
+
return @monthdays.include?(date.day) if @monthdays
|
335
|
+
return @weekdays.include?(date.wday) if @weekdays
|
336
|
+
if @unit == :weeks && check_anchor?
|
337
|
+
return @anchor.wday == date.wday
|
338
|
+
end
|
339
|
+
return true if check_anchor? && date.day == @anchor.day
|
340
|
+
end
|
341
|
+
|
342
|
+
def time_matches? date
|
343
|
+
#concerned with groups of hour minute second
|
344
|
+
if check_anchor?
|
345
|
+
return @anchor.hour == date.hour && @anchor.min == date.min && @anchor.sec == date.sec
|
346
|
+
end
|
347
|
+
@times.any? do |time|
|
348
|
+
time[:hour] == date.hour && time[:minute] == date.min && time[:second] == date.sec
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def hour_matches? date
|
353
|
+
return true if @frequency == 1
|
354
|
+
diff = Time.utc(date.year, date.month, date.day, date.hour) - Time.utc(@anchor.year, @anchor.month, @anchor.day, @anchor.hour)
|
355
|
+
(diff / 3600) % @frequency == 0
|
356
|
+
end
|
357
|
+
|
358
|
+
def sub_hour_matches? date
|
359
|
+
if check_anchor?
|
360
|
+
return @anchor.min == date.min && @anchor.sec == date.sec
|
361
|
+
end
|
362
|
+
times.any? do |time|
|
363
|
+
time[:minute] == date.min && time[:second] == date.sec
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def minute_matches? date
|
368
|
+
return true if @frequency == 1
|
369
|
+
diff = Time.utc(date.year, date.month, date.day, date.hour, date.min) - Time.utc(@anchor.year, @anchor.month, @anchor.day, @anchor.hour, @anchor.min)
|
370
|
+
(diff / 60) % @frequency == 0
|
371
|
+
end
|
372
|
+
|
373
|
+
def second_matches? date
|
374
|
+
if check_anchor?
|
375
|
+
return @anchor.sec == date.sec
|
376
|
+
end
|
377
|
+
times.any? do |time|
|
378
|
+
time[:second] == date.sec
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def check_anchor?
|
383
|
+
@anchor && @anchor_multiple
|
384
|
+
end
|
385
|
+
|
386
|
+
def ordinal_weekday symbol
|
387
|
+
lookup = {0 => [:sunday, :sun],
|
388
|
+
1 => [:monday, :mon],
|
389
|
+
2 => [:tuesday, :tues],
|
390
|
+
3 => [:wednesday, :weds],
|
391
|
+
4 => [:thursday, :thurs],
|
392
|
+
5 => [:friday, :fri],
|
393
|
+
6 => [:saturday, :sat]}
|
394
|
+
pair = lookup.select{|k,v| v.include?(symbol)}.first
|
395
|
+
pair.first if pair
|
396
|
+
end
|
397
|
+
|
398
|
+
def ordinal_month symbol
|
399
|
+
lookup = {1 => [:january, :jan],
|
400
|
+
2 => [:february, :feb],
|
401
|
+
3 => [:march, :mar],
|
402
|
+
4 => [:april, :apr],
|
403
|
+
5 => [:may],
|
404
|
+
6 => [:june, :jun],
|
405
|
+
7 => [:july, :jul],
|
406
|
+
8 => [:august, :aug],
|
407
|
+
9 => [:september, :sept],
|
408
|
+
10 => [:october, :oct],
|
409
|
+
11 => [:november, :nov],
|
410
|
+
12 => [:december, :dec]}
|
411
|
+
pair = lookup.select{|k,v| v.include?(symbol)}.first
|
412
|
+
pair.first if pair
|
413
|
+
end
|
414
|
+
|
415
|
+
def parse_times string
|
416
|
+
if string.nil? || string.empty?
|
417
|
+
return [{:hour => 0, :minute => 0, :second => 0}]
|
418
|
+
end
|
419
|
+
times = string.downcase.gsub(',','').split(' ')
|
420
|
+
parsed = times.collect do |st|
|
421
|
+
st = st.gsub /pm|am/, ''
|
422
|
+
am_pm = $&
|
423
|
+
time = {}
|
424
|
+
time[:hour], time[:minute], time[:second] = st.split(':').collect {|n| n.to_i}
|
425
|
+
time[:minute] ||= 0
|
426
|
+
time[:second] ||= 0
|
427
|
+
time[:hour] = time[:hour] + 12 if am_pm == 'pm' && time[:hour] < 12
|
428
|
+
time[:hour] = 0 if am_pm == 'am' && time[:hour] == 12
|
429
|
+
time
|
430
|
+
end
|
431
|
+
#this is an implementation of Array#uniq required because Hash#eql? is not a synonym for Hash#==
|
432
|
+
result = []
|
433
|
+
parsed.each_with_index do |h,i|
|
434
|
+
result << h unless parsed[(i+1)..parsed.length].include?(h)
|
435
|
+
end
|
436
|
+
result
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# RDS does not match ranges as such, just times specified to varying precision
|
442
|
+
# eg, you can construct a Schedule that matches all of February, by not specifying the
|
443
|
+
# week, day, or time. If you want February through August, you'll have to specify all the months
|
444
|
+
# individually.
|
445
|
+
|
446
|
+
# Change of Behaviour in Recurring: Schedules include only points in time. The Mask model handles ranges.
|
447
|
+
|
metadata
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ggoodale-recurring
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Anderson
|
8
|
-
|
9
|
-
autorequire: recurring
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
11
|
|
@@ -37,6 +36,8 @@ files:
|
|
37
36
|
- README.txt
|
38
37
|
- Rakefile
|
39
38
|
- lib/recurring.rb
|
39
|
+
- lib/date_language.rb
|
40
|
+
- lib/schedule.rb
|
40
41
|
- spec/date_language_spec.rb
|
41
42
|
- spec/schedule_spec.rb
|
42
43
|
has_rdoc: false
|