recurring 0.2.0 → 0.3.2
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 +11 -0
- data/Rakefile +47 -43
- data/lib/recurring.rb +65 -10
- data/spec/recurring_spec.rb +36 -7
- metadata +5 -5
data/History.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
|
4
|
+
require 'hoe'
|
5
5
|
#require_gem 'rspec'
|
6
6
|
#require 'rspec/lib/spec/rake/spectask'
|
7
7
|
require './lib/recurring.rb'
|
8
8
|
require 'spec/rake/spectask'
|
9
9
|
require 'rake/gempackagetask'
|
10
|
+
require 'rake/rdoctask'
|
10
11
|
|
11
12
|
Spec::Rake::SpecTask.new :spec do |t|
|
12
13
|
t.spec_files = FileList['spec/**/*_spec.rb']
|
@@ -15,50 +16,53 @@ end
|
|
15
16
|
|
16
17
|
task :default => :spec
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
Rake::GemPackageTask.new specification do |pkg|
|
51
|
-
pkg.need_zip = true
|
52
|
-
pkg.need_tar = true
|
19
|
+
Hoe.new('recurring', Recurring::VERSION) do |p|
|
20
|
+
p.url = 'http://jchris.mfdz.com'
|
21
|
+
p.author = "Chris Anderson"
|
22
|
+
p.email = 'jchris@mfdz.com'
|
23
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
24
|
+
p.summary = 'A scheduling library for recurring events'
|
25
|
+
p.description =<<-DESC
|
26
|
+
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.
|
27
|
+
DESC
|
28
|
+
puts p.changes
|
53
29
|
end
|
54
30
|
|
31
|
+
# specification = Gem::Specification.new do |s|
|
32
|
+
# s.name = %q{recurring}
|
33
|
+
# s.version = Recurring::VERSION
|
34
|
+
# s.platform = Gem::Platform::RUBY
|
35
|
+
# s.date = %q{2006-12-11}
|
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
|
47
|
+
# end
|
55
48
|
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
49
|
+
#
|
50
|
+
# Rake::GemPackageTask.new specification do |pkg|
|
51
|
+
# pkg.need_zip = true
|
52
|
+
# pkg.need_tar = true
|
59
53
|
# end
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
54
|
+
#
|
55
|
+
# Rake::RDocTask.new(:docs) do |rd|
|
56
|
+
# rd.main = "README.txt"
|
57
|
+
# rd.options << '-d' if RUBY_PLATFORM !~ /win32/ and `which dot` =~ /\/dot/
|
58
|
+
# rd.rdoc_dir = 'doc'
|
59
|
+
# files = ["History.txt", "README.txt", "Rakefile", "lib/recurring.rb", "spec/recurring_spec.rb"]
|
60
|
+
# rd.rdoc_files.push(*files)
|
61
|
+
#
|
62
|
+
# title = "#{specification.name}-#{specification.version} Documentation"
|
63
|
+
# #title = "#{rubyforge_name}'s " + title if rubyforge_name != title
|
64
|
+
#
|
65
|
+
# rd.options << "-t #{title}"
|
66
|
+
# end
|
67
|
+
|
63
68
|
|
64
|
-
# vim: syntax=Ruby
|
data/lib/recurring.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Recurring
|
2
|
-
VERSION = '0.
|
2
|
+
VERSION = '0.3.2'
|
3
3
|
|
4
4
|
class << self
|
5
5
|
# returns a number starting with 1
|
@@ -56,14 +56,17 @@ module Recurring
|
|
56
56
|
def initialize options
|
57
57
|
raise ArgumentError, 'specify a valid unit' unless options[:unit] &&
|
58
58
|
%w{years months weeks days hours minutes}.include?(options[:unit])
|
59
|
-
raise ArgumentError, 'frequency > 1 requires an anchor Time' if options[:frequency] && !options[:anchor]
|
59
|
+
raise ArgumentError, 'frequency > 1 requires an anchor Time' if options[:frequency] && options[:frequency] != 1 && !options[:anchor]
|
60
60
|
@unit = options[:unit].to_sym
|
61
|
+
raise ArgumentError, 'weekdays are required with the weeks param, if there are times params' if @unit == :weeks &&
|
62
|
+
options[:times] &&
|
63
|
+
!options[:weekdays]
|
61
64
|
@frequency = options[:frequency] || 1
|
62
65
|
@anchor = options[:anchor]
|
63
66
|
@times = parse_times options[:times]
|
64
|
-
@months = Array(options[:months]).collect{|d|d.to_sym} if options[:months]
|
67
|
+
@months = Array(options[:months]).collect{|d|d.to_s.downcase.to_sym} if options[:months]
|
65
68
|
@weeks = Array(options[:weeks]) if options[:weeks]
|
66
|
-
@weekdays = Array(options[:weekdays]).collect{|d|d.to_sym} if options[:weekdays]
|
69
|
+
@weekdays = Array(options[:weekdays]).collect{|d|d.to_s.downcase.to_sym} if options[:weekdays]
|
67
70
|
@monthdays = Array(options[:monthdays]) if options[:monthdays]
|
68
71
|
|
69
72
|
@anchor_multiple = options[:times].nil? && options[:weeks].nil? && options[:weekdays].nil? && options[:monthdays].nil?
|
@@ -93,14 +96,22 @@ module Recurring
|
|
93
96
|
true
|
94
97
|
end
|
95
98
|
|
96
|
-
# Starts from the argument, and returns the next included time. Returns the argument if it is included in the schedule.
|
99
|
+
# Starts from the argument time, and returns the next included time. Returns the argument if it is included in the schedule.
|
97
100
|
def find_next date
|
98
101
|
loop do
|
99
102
|
return date if include?(date)
|
100
103
|
date = beginning_of_next @resolution, date
|
101
104
|
end
|
102
105
|
end
|
103
|
-
|
106
|
+
|
107
|
+
# Starts from the argument time, and works backwards until it hits a time that is included
|
108
|
+
def find_previous date
|
109
|
+
loop do
|
110
|
+
return date if include?(date)
|
111
|
+
date = end_of_previous @resolution, date
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
104
115
|
# 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.
|
105
116
|
#
|
106
117
|
# <tt>rs.find_in_range(Time.now, Time.now+24*60*60)</tt>
|
@@ -125,6 +136,7 @@ module Recurring
|
|
125
136
|
|
126
137
|
# Two Schedules are equal if they have the same attributes.
|
127
138
|
def == other
|
139
|
+
return false unless self.class == other.class
|
128
140
|
[:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times].all? do |attribute|
|
129
141
|
self.send(attribute) == other.send(attribute)
|
130
142
|
end
|
@@ -132,6 +144,26 @@ module Recurring
|
|
132
144
|
|
133
145
|
private
|
134
146
|
|
147
|
+
def end_of_previous scope, date
|
148
|
+
case scope
|
149
|
+
when :year
|
150
|
+
Time.utc(date.year) - 1
|
151
|
+
when :month
|
152
|
+
Time.utc(date.year, date.month) -1
|
153
|
+
when :week
|
154
|
+
to_sunday = date.wday
|
155
|
+
previous_week = (date - to_sunday*24*60*60)
|
156
|
+
Time.utc(previous_week.year, previous_week.month, previous_week.day)
|
157
|
+
when :day
|
158
|
+
Time.utc(date.year, date.month, date.day) - 1
|
159
|
+
when :time
|
160
|
+
previous_time date
|
161
|
+
else
|
162
|
+
date - 1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
135
167
|
def beginning_of_next scope, date
|
136
168
|
case scope
|
137
169
|
when :year
|
@@ -152,6 +184,25 @@ module Recurring
|
|
152
184
|
end
|
153
185
|
end
|
154
186
|
|
187
|
+
def previous_time date
|
188
|
+
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
189
|
+
my_times = times + [me]
|
190
|
+
my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
191
|
+
my_times.sort! do |a,b|
|
192
|
+
v = a[:hour] <=> b[:hour]
|
193
|
+
v = a[:minute] <=> b[:minute] if v == 0
|
194
|
+
v = a[:second] <=> b[:second] if v == 0
|
195
|
+
v
|
196
|
+
end
|
197
|
+
my_times.reverse!
|
198
|
+
ntime = my_times[my_times.index(me)+1]
|
199
|
+
if ntime
|
200
|
+
Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
|
201
|
+
else
|
202
|
+
end_of_previous :day, date
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
155
206
|
def next_time date
|
156
207
|
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
157
208
|
my_times = times + [me]
|
@@ -294,7 +345,7 @@ module Recurring
|
|
294
345
|
end
|
295
346
|
|
296
347
|
def parse_times string
|
297
|
-
|
348
|
+
if string.nil? || string.empty?
|
298
349
|
return [{:hour => 0, :minute => 0, :second => 0}]
|
299
350
|
end
|
300
351
|
times = string.downcase.gsub(',','').split(' ')
|
@@ -302,18 +353,22 @@ module Recurring
|
|
302
353
|
st = st.gsub /pm|am/, ''
|
303
354
|
am_pm = $&
|
304
355
|
time = {}
|
305
|
-
|
356
|
+
time[:hour], time[:minute], time[:second] = st.split(':').collect {|n| n.to_i}
|
306
357
|
time[:minute] ||= 0
|
307
358
|
time[:second] ||= 0
|
308
359
|
time[:hour] = time[:hour] + 12 if am_pm == 'pm'
|
309
360
|
time
|
310
361
|
end
|
311
|
-
|
362
|
+
#this is an implementation of Array#uniq required because Hash#eql? is not a synonym for Hash#==
|
363
|
+
result = []
|
364
|
+
parsed.each_with_index do |h,i|
|
365
|
+
result << h unless parsed[(i+1)..parsed.length].include?(h)
|
366
|
+
end
|
367
|
+
result
|
312
368
|
end
|
313
369
|
end
|
314
370
|
end
|
315
371
|
|
316
|
-
|
317
372
|
# RDS does not match ranges as such, just times specified to varying precision
|
318
373
|
# eg, you can construct a Schedule that matches all of February, by not specifying the
|
319
374
|
# week, day, or time. If you want February through August, you'll have to specify all the months
|
data/spec/recurring_spec.rb
CHANGED
@@ -33,7 +33,22 @@ context "Initializing a Schedule" do
|
|
33
33
|
rs = Recurring::Schedule.new :unit => 'days', :times => '4:30pm 5pm 3:30:30'
|
34
34
|
rs.times.should == [{:hour => 16, :minute => 30, :second => 0}, {:hour => 17, :minute => 0, :second => 0}, {:hour => 3, :minute => 30, :second => 30}]
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
|
+
specify "should be graceful about a duplicate times string" do
|
38
|
+
rs = Recurring::Schedule.new :unit => 'days', :times => '4:30 4:30'
|
39
|
+
rs.times.should == [{:hour => 4, :minute => 30, :second => 0}]
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "should be graceful about a busted times string" do
|
43
|
+
rs = Recurring::Schedule.new :unit => 'days', :times => 'afs4 th'
|
44
|
+
rs.times.should == [{:hour => 0, :minute => 0, :second => 0}]
|
45
|
+
end
|
46
|
+
|
47
|
+
specify "should be graceful about an empty times string" do
|
48
|
+
rs = Recurring::Schedule.new :unit => 'days', :times => ''
|
49
|
+
rs.times.should == [{:hour => 0, :minute => 0, :second => 0}]
|
50
|
+
end
|
51
|
+
|
37
52
|
specify "should provide a sensible default time param" do
|
38
53
|
rs = Recurring::Schedule.new :unit => 'days'
|
39
54
|
rs.times.should == [{:hour => 0, :minute => 0, :second => 0}]
|
@@ -330,6 +345,16 @@ context "A weekly schedule with weekday params" do
|
|
330
345
|
|
331
346
|
end
|
332
347
|
|
348
|
+
context "A Schedule with uppercase weekdays" do
|
349
|
+
setup do
|
350
|
+
@rs = Recurring::Schedule.new :unit => 'weeks', :weekdays => %w{Friday}, :anchor => Time.now, :times => '12'
|
351
|
+
end
|
352
|
+
|
353
|
+
specify "should include Friday's at noon" do
|
354
|
+
@rs.should_include Time.utc(2006,12,8,12)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
333
358
|
context "A weekly schedule with only an anchor" do
|
334
359
|
|
335
360
|
setup do
|
@@ -350,6 +375,10 @@ context "A weekly schedule with only an anchor" do
|
|
350
375
|
@rs.find_next(Time.utc(2006,5,4)).should == Time.utc(2006,5,10,17,30)
|
351
376
|
@rs.find_next(Time.utc(2006,6,11)).should == Time.utc(2006,6,14,17,30)
|
352
377
|
end
|
378
|
+
|
379
|
+
specify "should find the previous date" do
|
380
|
+
@rs.find_previous(Time.utc(2006,5,14)).should == Time.utc(2006,5,10,17,30)
|
381
|
+
end
|
353
382
|
end
|
354
383
|
|
355
384
|
context "A third-weekly schedule with weekday and times params" do
|
@@ -405,12 +434,8 @@ end
|
|
405
434
|
|
406
435
|
context "A weekly schedule with times params but no days params" do
|
407
436
|
|
408
|
-
setup do
|
409
|
-
@rs = Recurring::Schedule.new :unit => 'weeks', :times => '4pm'
|
410
|
-
end
|
411
|
-
|
412
437
|
specify "should flip out hard" do
|
413
|
-
lambda{@rs.
|
438
|
+
lambda{@rs = Recurring::Schedule.new :unit => 'weeks', :times => '4pm'}.should_raise ArgumentError
|
414
439
|
end
|
415
440
|
|
416
441
|
end
|
@@ -603,6 +628,10 @@ context "An hourly schedule with times params" do
|
|
603
628
|
@rs.find_next(Time.utc(2006,6,13,5,31)).should == Time.utc(2006,6,13,5,45,30)
|
604
629
|
end
|
605
630
|
|
631
|
+
specify "should find the previous minute that match" do
|
632
|
+
@rs.find_previous(Time.utc(2006,5,5,5,17)).should == Time.utc(2006,5,5,5,15)
|
633
|
+
end
|
634
|
+
|
606
635
|
end
|
607
636
|
|
608
637
|
context "An eight-hourly schedule with times params" do
|
@@ -683,4 +712,4 @@ context "a daily schedule with a time 4:29am" do
|
|
683
712
|
specify "should not include times other than 4:29am" do
|
684
713
|
@rs.should_not_include Time.utc(2004,5,17,1,39)
|
685
714
|
end
|
686
|
-
end
|
715
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: recurring
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.2
|
7
|
-
date: 2006-12-
|
6
|
+
version: 0.3.2
|
7
|
+
date: 2006-12-14 00:00:00 -08:00
|
8
8
|
summary: A scheduling library for recurring events
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -12,7 +12,7 @@ email: jchris@mfdz.com
|
|
12
12
|
homepage: http://jchris.mfdz.com
|
13
13
|
rubyforge_project: recurring
|
14
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:
|
15
|
+
autorequire:
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
18
18
|
has_rdoc: true
|
@@ -48,11 +48,11 @@ requirements: []
|
|
48
48
|
|
49
49
|
dependencies:
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
51
|
+
name: hoe
|
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: 1.1.6
|
58
58
|
version:
|