recurring 0.2.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/History.txt +11 -0
  2. data/Rakefile +47 -43
  3. data/lib/recurring.rb +65 -10
  4. data/spec/recurring_spec.rb +36 -7
  5. metadata +5 -5
data/History.txt CHANGED
@@ -1,3 +1,14 @@
1
+ == 0.3.2 / 2006-12-13
2
+
3
+ * initialization bugfixes
4
+
5
+ == 0.2.0 / 2006-12-11
6
+
7
+ * Documentation
8
+ * find_before
9
+ * bugfixes
10
+
11
+
1
12
  == 0.1.0 / 2006-12-09
2
13
 
3
14
  * Initial releast
data/Rakefile CHANGED
@@ -1,12 +1,13 @@
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
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
- # 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
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
- # desc "Run the specs"
57
- # task :spec do
58
- # system "spec spec/recurring_spec.rb -f specdoc"
49
+ #
50
+ # Rake::GemPackageTask.new specification do |pkg|
51
+ # pkg.need_zip = true
52
+ # pkg.need_tar = true
59
53
  # end
60
- #Spec::Rake::SpecTask.new('spec') do |t|
61
- # t.spec_files = FileList['examples/**/*.rb']
62
- #end
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.1.1'
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
- unless string
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
- time[:hour], time[:minute], time[:second] = st.split(':').collect {|n| n.to_i}
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
- parsed
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
@@ -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.find_next(Time.utc(2006,1,1,16))}.should_raise RangeError
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.0
7
- date: 2006-12-10 00:00:00 -08:00
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: recurring
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: rspec
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: 0.7.4
57
+ version: 1.1.6
58
58
  version: