recurring 0.1.0 → 0.2.0
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/README.txt +7 -9
- data/Rakefile +49 -10
- data/lib/recurring.rb +45 -26
- data/spec/recurring_spec.rb +85 -1
- metadata +8 -8
data/README.txt
CHANGED
@@ -11,8 +11,13 @@ Schedules can be like:
|
|
11
11
|
* The 1st and 15th of every other month
|
12
12
|
* Every 2 hours
|
13
13
|
* The first Friday of every month at 11:15am and 4:45:15pm
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
= See more examples at Recurring::Schedule documentation
|
16
|
+
|
17
|
+
# This Schedule will match every Tuesday at 5pm
|
18
|
+
@rs = Recurring::Schedule.new :unit => 'weeks', :weekdays => 'tuesday', :times => '5pm'
|
19
|
+
@rs.include?(Time.utc(2006,12,12,17)) #=> true
|
20
|
+
@rs.include?(Time.utc(2006,12,13,17)) #=> false
|
16
21
|
|
17
22
|
= Features
|
18
23
|
* Fast searching of long ranges for matching times.
|
@@ -24,13 +29,6 @@ Schedules can be like:
|
|
24
29
|
* Untested timezone support
|
25
30
|
* Plans to offer complex Schedule and Masks
|
26
31
|
|
27
|
-
== SYNOPSYS:
|
28
|
-
|
29
|
-
# This Schedule will match every Tuesday at 5pm
|
30
|
-
@rs = Recurring::Schedule.new :unit => 'weeks', :weekdays => 'tuesday', :times => '5pm'
|
31
|
-
@rs.include?(Time.utc(2006,12,12,17)) #=> true
|
32
|
-
@rs.include?(Time.utc(2006,12,13,17)) #=> false
|
33
|
-
|
34
32
|
== REQUIREMENTS:
|
35
33
|
|
36
34
|
* RSpec >= 0.7.4 to run the specs.
|
data/Rakefile
CHANGED
@@ -1,23 +1,62 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require 'hoe'
|
4
|
+
#require 'hoe'
|
5
5
|
#require_gem 'rspec'
|
6
6
|
#require 'rspec/lib/spec/rake/spectask'
|
7
7
|
require './lib/recurring.rb'
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
require 'rake/gempackagetask'
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
13
|
-
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
14
|
-
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
11
|
+
Spec::Rake::SpecTask.new :spec do |t|
|
12
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
13
|
+
t.spec_opts << "-f s"
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
task :default => :spec
|
17
|
+
|
18
|
+
# Hoe.new('recurring', Recurring::VERSION) do |p|
|
19
|
+
# p.rubyforge_name = 'recurring'
|
20
|
+
# p.summary = 'A scheduling library for recurring events'
|
21
|
+
# p.description =<<-DESC
|
22
|
+
# 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.
|
23
|
+
# DESC
|
24
|
+
#
|
25
|
+
# # p.authors = "Chris Anderson"
|
26
|
+
# p.email = 'jchris@mfdz.com'
|
27
|
+
# p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
28
|
+
# p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
29
|
+
# end
|
30
|
+
|
31
|
+
specification = Gem::Specification.new do |s|
|
32
|
+
s.name = %q{recurring}
|
33
|
+
s.version = "0.2.0"
|
34
|
+
s.platform = Gem::Platform::RUBY
|
35
|
+
s.date = %q{2006-12-10}
|
36
|
+
s.summary = %q{A scheduling library for recurring events}
|
37
|
+
s.email = %q{jchris@mfdz.com}
|
38
|
+
s.homepage = %q{http://jchris.mfdz.com}
|
39
|
+
s.rubyforge_project = %q{recurring}
|
40
|
+
s.autorequire = 'recurring'
|
41
|
+
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.}
|
42
|
+
s.authors = ["Chris Anderson"]
|
43
|
+
s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/recurring.rb", "spec/recurring_spec.rb"]
|
44
|
+
#s.add_dependency(%q<hoe>, [">= 1.1.6"])
|
45
|
+
s.add_dependency(%q<rspec>, [">= 0.7.4"])
|
46
|
+
s.has_rdoc = true
|
20
47
|
end
|
48
|
+
|
49
|
+
|
50
|
+
Rake::GemPackageTask.new specification do |pkg|
|
51
|
+
pkg.need_zip = true
|
52
|
+
pkg.need_tar = true
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# desc "Run the specs"
|
57
|
+
# task :spec do
|
58
|
+
# system "spec spec/recurring_spec.rb -f specdoc"
|
59
|
+
# end
|
21
60
|
#Spec::Rake::SpecTask.new('spec') do |t|
|
22
61
|
# t.spec_files = FileList['examples/**/*.rb']
|
23
62
|
#end
|
data/lib/recurring.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Recurring
|
2
|
-
VERSION = '0.1.
|
2
|
+
VERSION = '0.1.1'
|
3
3
|
|
4
4
|
class << self
|
5
5
|
# returns a number starting with 1
|
@@ -17,7 +17,11 @@ module Recurring
|
|
17
17
|
# Initialize a Schedule object with the proper options to calculate occurances in
|
18
18
|
# that schedule. Schedule#new take a hash of options <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
|
19
19
|
#
|
20
|
-
# =
|
20
|
+
# = Yearly
|
21
|
+
# [Every two years from an anchor time] <tt>Recurring::Schedule.new :unit => 'years', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
22
|
+
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
23
|
+
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
24
|
+
# = Monthly
|
21
25
|
# [Every two months from an anchor time] <tt>Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
22
26
|
# [The first and fifteenth of every month] <tt>Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]</tt>
|
23
27
|
# [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>
|
@@ -39,10 +43,10 @@ module Recurring
|
|
39
43
|
# See the specs using "rake spec" for even more examples.
|
40
44
|
class Schedule
|
41
45
|
|
42
|
-
attr_reader :unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times
|
46
|
+
attr_reader :unit, :frequency, :anchor, :months, :weeks, :monthdays, :weekdays, :times
|
43
47
|
|
44
48
|
# * options is a hash with keys <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
|
45
|
-
# * valid units are <tt>months, weeks, days, hours, and minutes</tt>
|
49
|
+
# * valid units are <tt>years, months, weeks, days, hours, and minutes</tt>
|
46
50
|
# * :frequency defaults to 1
|
47
51
|
# * :anchor is required if the frequency is other than one
|
48
52
|
# * :weeks alongside :weekdays is used to specify the nth instance of a weekday in a month.
|
@@ -51,12 +55,13 @@ module Recurring
|
|
51
55
|
# * :times takes a string with a simple format. <tt>"4pm 5:15pm 6:45:30pm"</tt>
|
52
56
|
def initialize options
|
53
57
|
raise ArgumentError, 'specify a valid unit' unless options[:unit] &&
|
54
|
-
%w{months weeks days hours minutes}.include?(options[:unit])
|
58
|
+
%w{years months weeks days hours minutes}.include?(options[:unit])
|
55
59
|
raise ArgumentError, 'frequency > 1 requires an anchor Time' if options[:frequency] && !options[:anchor]
|
56
60
|
@unit = options[:unit].to_sym
|
57
61
|
@frequency = options[:frequency] || 1
|
58
62
|
@anchor = options[:anchor]
|
59
63
|
@times = parse_times options[:times]
|
64
|
+
@months = Array(options[:months]).collect{|d|d.to_sym} if options[:months]
|
60
65
|
@weeks = Array(options[:weeks]) if options[:weeks]
|
61
66
|
@weekdays = Array(options[:weekdays]).collect{|d|d.to_sym} if options[:weekdays]
|
62
67
|
@monthdays = Array(options[:monthdays]) if options[:monthdays]
|
@@ -69,9 +74,10 @@ module Recurring
|
|
69
74
|
def include? date
|
70
75
|
@resolution = nil
|
71
76
|
return true if check_anchor? && date == @anchor
|
72
|
-
return mismatch(:
|
73
|
-
return mismatch(:
|
74
|
-
if [:
|
77
|
+
return mismatch(:year) unless year_matches?(date) if @unit == :years
|
78
|
+
return mismatch(:month) unless month_matches?(date) if [:months, :years].include?(@unit)
|
79
|
+
return mismatch(:week) unless week_matches?(date) if [:years, :months, :weeks].include?(@unit)
|
80
|
+
if [:years, :months, :weeks, :days].include?(@unit)
|
75
81
|
return mismatch(:day) unless day_matches?(date)
|
76
82
|
return mismatch(:time) unless time_matches?(date)
|
77
83
|
end
|
@@ -95,7 +101,7 @@ module Recurring
|
|
95
101
|
end
|
96
102
|
end
|
97
103
|
|
98
|
-
# Takes a range, which can specified as two Times, or as an object that returns
|
104
|
+
# Takes a range, which can specified as two Times, or as an object that returns Time objects from the methods <tt>first</tt> and <tt>last</tt>. Really, the argment objects just need to be duck-type compatible with most of the Time instance methods, and the <tt>utc</tt> factory method.
|
99
105
|
#
|
100
106
|
# <tt>rs.find_in_range(Time.now, Time.now+24*60*60)</tt>
|
101
107
|
#
|
@@ -169,10 +175,23 @@ module Recurring
|
|
169
175
|
false
|
170
176
|
end
|
171
177
|
|
178
|
+
def year_matches? date
|
179
|
+
return true if @frequency == 1
|
180
|
+
return (date.year - @anchor.year) % @frequency == 0
|
181
|
+
end
|
182
|
+
|
172
183
|
def month_matches? date
|
173
184
|
#only concerned with multiples
|
174
|
-
|
175
|
-
|
185
|
+
if @unit == :months
|
186
|
+
return true if @frequency == 1
|
187
|
+
return (date.month - @anchor.month) % @frequency == 0
|
188
|
+
end
|
189
|
+
if @months
|
190
|
+
month_nums = @months.collect{|w| ordinal_month w}
|
191
|
+
return month_nums.include?(date.month)
|
192
|
+
else
|
193
|
+
true
|
194
|
+
end
|
176
195
|
end
|
177
196
|
|
178
197
|
def week_matches? date
|
@@ -258,21 +277,21 @@ module Recurring
|
|
258
277
|
lookup.select{|k,v| v.include?(symbol)}.first.first
|
259
278
|
end
|
260
279
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
280
|
+
def ordinal_month symbol
|
281
|
+
lookup = {1 => [:january, :jan],
|
282
|
+
2 => [:february, :feb],
|
283
|
+
3 => [:march, :mar],
|
284
|
+
4 => [:april, :apr],
|
285
|
+
5 => [:may],
|
286
|
+
6 => [:june, :jun],
|
287
|
+
7 => [:july, :jul],
|
288
|
+
8 => [:august, :aug],
|
289
|
+
9 => [:september, :sept],
|
290
|
+
10 => [:october, :oct],
|
291
|
+
11 => [:november, :nov],
|
292
|
+
12 => [:december, :dec]}
|
293
|
+
lookup.select{|k,v| v.include?(symbol)}.first.first
|
294
|
+
end
|
276
295
|
|
277
296
|
def parse_times string
|
278
297
|
unless string
|
data/spec/recurring_spec.rb
CHANGED
@@ -10,9 +10,10 @@ def results_should_be_included results, schedule
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def should_find_in_range schedule, length, *range
|
13
|
-
result = schedule.find_in_range(range
|
13
|
+
result = schedule.find_in_range(range)
|
14
14
|
result.length.should_be length
|
15
15
|
results_should_be_included result, schedule
|
16
|
+
result
|
16
17
|
end
|
17
18
|
|
18
19
|
context "Initializing a Schedule" do
|
@@ -48,6 +49,11 @@ context "Initializing a Schedule" do
|
|
48
49
|
rs.weekdays.should == [:monday, :wednesday]
|
49
50
|
end
|
50
51
|
|
52
|
+
specify "should accept months params" do
|
53
|
+
rs = Recurring::Schedule.new :unit => 'years', :months => 'feb', :monthdays => [4]
|
54
|
+
rs.months.should == [:feb]
|
55
|
+
end
|
56
|
+
|
51
57
|
specify "should accept days as integers" do
|
52
58
|
rs = Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]
|
53
59
|
rs.monthdays.should == [1,15]
|
@@ -82,6 +88,71 @@ context "A complex Schedule" do
|
|
82
88
|
|
83
89
|
end
|
84
90
|
|
91
|
+
#YEARS
|
92
|
+
context "A yearly schedule with month and monthdays" do
|
93
|
+
setup do
|
94
|
+
@rs = Recurring::Schedule.new :unit => 'years', :months => 'feb', :monthdays => [4]
|
95
|
+
end
|
96
|
+
|
97
|
+
specify "should include the day of the month in any year" do
|
98
|
+
@rs.should_include Time.utc(1980,2,4)
|
99
|
+
@rs.should_include Time.utc(1989,2,4)
|
100
|
+
end
|
101
|
+
specify "should not include other days of the month in any year" do
|
102
|
+
@rs.should_not_include Time.utc(1989,4,4)
|
103
|
+
@rs.should_not_include Time.utc(1980,2,5)
|
104
|
+
end
|
105
|
+
|
106
|
+
specify "should find 20 times in 20 years" do
|
107
|
+
should_find_in_range(@rs, 20, Time.utc(2006,12,12,1), Time.utc(2026,12,12,1,45))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "A yearly schedule with months and weekdays" do
|
112
|
+
setup do
|
113
|
+
@rs = Recurring::Schedule.new :unit => 'years', :months => 'feb', :weekdays => ['tuesday', 'weds']
|
114
|
+
end
|
115
|
+
specify "should match every tuesday and wednesday in febraury" do
|
116
|
+
@rs.should_include Time.utc(2007,2,6)
|
117
|
+
@rs.should_include Time.utc(2007,2,7)
|
118
|
+
@rs.should_include Time.utc(2007,2,20)
|
119
|
+
@rs.should_include Time.utc(2007,2,21)
|
120
|
+
end
|
121
|
+
specify "should not include other days of the week in feburary" do
|
122
|
+
@rs.should_not_include Time.utc(2007,2,8)
|
123
|
+
@rs.should_not_include Time.utc(2007,2,9)
|
124
|
+
@rs.should_not_include Time.utc(2007,2,23)
|
125
|
+
@rs.should_not_include Time.utc(2007,2,24)
|
126
|
+
end
|
127
|
+
specify "should not include tuesday and wednesday outside of febraury" do
|
128
|
+
@rs.should_not_include Time.utc(2007,3,6)
|
129
|
+
@rs.should_not_include Time.utc(2007,3,7)
|
130
|
+
@rs.should_not_include Time.utc(2007,3,20)
|
131
|
+
@rs.should_not_include Time.utc(2007,3,21)
|
132
|
+
end
|
133
|
+
specify "should find 20 times in 20 years" do
|
134
|
+
should_find_in_range(@rs, 161, Time.utc(2006,12,12,1), Time.utc(2026,12,12,1,45))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "A bi-yearly schedule with months, weeks, weekdays, and times" do
|
139
|
+
setup do
|
140
|
+
@rs = Recurring::Schedule.new :unit => 'years', :frequency => 2, :anchor => Time.utc(2006,6), :months => ['feb', 'may'], :weeks => [2], :weekdays => 'fri', :times => '5:15'
|
141
|
+
end
|
142
|
+
|
143
|
+
specify "should include the correct days at the right times" do
|
144
|
+
@rs.should_include Time.utc(2006,2,10,5,15)
|
145
|
+
@rs.should_include Time.utc(2006,5,12,5,15)
|
146
|
+
@rs.should_include Time.utc(2008,2,8,5,15)
|
147
|
+
@rs.should_include Time.utc(2008,5,9,5,15)
|
148
|
+
end
|
149
|
+
|
150
|
+
specify "should not include good times in bad years" do
|
151
|
+
@rs.should_not_include Time.utc(2007,2,9,5,15)
|
152
|
+
@rs.should_not_include Time.utc(2007,5,11,5,15)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
85
156
|
#MONTHS
|
86
157
|
|
87
158
|
context "A bi-monthly Schedule without more params" do
|
@@ -395,6 +466,19 @@ context "A fourth-daily schedule with only the unit and frequency != 1" do
|
|
395
466
|
end
|
396
467
|
end
|
397
468
|
|
469
|
+
context "A daily schedule with a sub-second precise anchor" do
|
470
|
+
setup do
|
471
|
+
@rs = Recurring::Schedule.new :unit => 'days', :anchor => Time.utc(2006,11,27,0,0,30,45)
|
472
|
+
end
|
473
|
+
specify "should include times equal up to second accuracy" do
|
474
|
+
@rs.should_include Time.utc(2006,11,27,0,0,30)
|
475
|
+
@rs.should_include Time.utc(2006,11,27,0,0,30,55)
|
476
|
+
end
|
477
|
+
specify "should not include other times" do
|
478
|
+
@rs.should_not_include Time.utc(2006,11,27,0,0,31,45)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
398
482
|
context "A fourth-daily schedule with no other params" do
|
399
483
|
|
400
484
|
setup do
|
metadata
CHANGED
@@ -3,16 +3,16 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: recurring
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
6
|
+
version: 0.2.0
|
7
7
|
date: 2006-12-10 00:00:00 -08:00
|
8
8
|
summary: A scheduling library for recurring events
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
|
-
email:
|
12
|
-
homepage:
|
11
|
+
email: jchris@mfdz.com
|
12
|
+
homepage: http://jchris.mfdz.com
|
13
13
|
rubyforge_project: recurring
|
14
|
-
description:
|
15
|
-
autorequire:
|
14
|
+
description: 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.
|
15
|
+
autorequire: recurring
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
18
18
|
has_rdoc: true
|
@@ -26,7 +26,7 @@ platform: ruby
|
|
26
26
|
signing_key:
|
27
27
|
cert_chain:
|
28
28
|
authors:
|
29
|
-
-
|
29
|
+
- Chris Anderson
|
30
30
|
files:
|
31
31
|
- History.txt
|
32
32
|
- Manifest.txt
|
@@ -48,11 +48,11 @@ requirements: []
|
|
48
48
|
|
49
49
|
dependencies:
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
51
|
+
name: rspec
|
52
52
|
version_requirement:
|
53
53
|
version_requirements: !ruby/object:Gem::Version::Requirement
|
54
54
|
requirements:
|
55
55
|
- - ">="
|
56
56
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
57
|
+
version: 0.7.4
|
58
58
|
version:
|