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.
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: