reservation 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -36,15 +36,21 @@ This will create a set of events, with the given subjects, within the given cons
36
36
  { "role" => "place", "subject" => here, "status" => "tentative" }
37
37
  ]
38
38
 
39
- pattern = [ { "day" => "wed", "start" => "0930", "finish" => "1030"},
40
- { "day" => "wed", "start" => "18", "finish" => "20" },
41
- { "day" => "tue", "start" => "7", "finish" => "830" } ]
39
+ patterns = [ { "day" => "wed", "start" => "0930", "finish" => "1030"},
40
+ { "day" => "wed", "start" => "18", "finish" => "20" },
41
+ { "day" => "tue", "start" => "7", "finish" => "830" } ]
42
42
 
43
43
  Reservation::Event.create_weekly "the_title", "2013-09-03", "2013-10-13", subjects, pattern
44
44
 
45
+ A 'pattern' may contain an 'nth_of_month' key, in which case the gem will generate events only on days which
46
+ are the given nth day of the month (for example, "2nd wednesday of each month", "last saturday of the month").
47
+
45
48
  You can use Reservation::Event#build_weekly instead in order to instantiate the object graph without
46
49
  persisting it.
47
50
 
51
+ ## History
52
+
53
+ v0.0.5 Add "nth day of month" feature
48
54
 
49
55
  ## Contributing
50
56
 
@@ -0,0 +1,29 @@
1
+ class Date
2
+ def nth_day_of_month
3
+ 1 + ( (self.mday - 1) / 7 )
4
+ end
5
+
6
+ def nth_last_day_of_month
7
+ last_day = self.end_of_month.mday
8
+ - 1 - (last_day - self.mday) / 7
9
+ end
10
+
11
+ #
12
+ # return true if this is the nth of this day within the month,
13
+ # for example, if n is 2, and this is the second wednesday of the month,
14
+ # return true. If n is -1, and this is the last saturday of the month,
15
+ # return true. It doesn't matter which *day* it is, it matters whether
16
+ # it's the first, second, third, etc, or if it's the last, second last,
17
+ # third last, etc
18
+ #
19
+ def nth_day_of_month? n
20
+ case n <=> 0
21
+ when -1
22
+ nth_last_day_of_month == n
23
+ when 0
24
+ raise ArgumentError.new("must be non-zero integer")
25
+ when 1
26
+ nth_day_of_month == n
27
+ end
28
+ end
29
+ end
@@ -17,6 +17,6 @@ class Reservation::MigrationGenerator < Rails::Generators::Base
17
17
  end
18
18
 
19
19
  def create_migration_file
20
- migration_template 'migration.rb', 'db/migrate/create_reservations_tables.rb'
20
+ migration_template 'migration.rb', 'db/migrate/create_reservations.rb'
21
21
  end
22
22
  end
data/lib/reservation.rb CHANGED
@@ -5,3 +5,9 @@ require "reservation/event_filter"
5
5
  require "reservation/event"
6
6
  require "reservation/reservation"
7
7
 
8
+
9
+ module Reservation
10
+ def self.table_name_prefix
11
+ 'reservation_'
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ require 'core_ext/date'
2
+
3
+ module Reservation
4
+ module Schedule
5
+ #
6
+ # a utility class to represent an interval starting on a day
7
+ #
8
+ class Daily
9
+ attr_accessor :wday, :nth_of_month, :interval
10
+
11
+ def initialize wday, nth_of_month, interval
12
+ @wday = wday
13
+ @nth_of_month = nth_of_month
14
+ @interval = interval
15
+ end
16
+
17
+ def matches? event
18
+ good_day?(event.start.to_date) && interval.matches?(event)
19
+ end
20
+
21
+ def generate date, list
22
+ return list unless good_day?(date)
23
+ list << interval.generate(date)
24
+ list
25
+ end
26
+
27
+ def good_day? date
28
+ (date.wday == self.wday) && ((self.nth_of_month == :all) || date.nth_day_of_month?(self.nth_of_month))
29
+ end
30
+
31
+ def to_s
32
+ "#{MAP_DAY[wday]} => #{interval}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,7 +4,7 @@ class Reservation::Event < ActiveRecord::Base
4
4
  extend Reservation::TimeOffset
5
5
  extend Reservation::EventFilter
6
6
 
7
- has_many :reservations, :class_name => "Reservation::Reservation"
7
+ has_many :reservations, :class_name => "Reservation::Reservation", :inverse_of => :event
8
8
  attr_accessible :finish, :start, :title
9
9
 
10
10
  scope :since, lambda { |time| where("reservation_events.finish > ?", time) }
@@ -0,0 +1,51 @@
1
+ module Reservation
2
+ module Schedule
3
+ #
4
+ # a utility class to match the hour and minute elements of a Time instance, without considering any other values
5
+ #
6
+ class HourMinute
7
+ attr_accessor :hour, :minute
8
+
9
+ def initialize hour, minute
10
+ @hour, @minute = hour, minute
11
+ end
12
+
13
+ def matches_time? time
14
+ time.hour == self.hour && time.min == self.minute
15
+ end
16
+
17
+ #
18
+ # hhmm is a string containg an hour-and-minute value
19
+ #
20
+ # #parse will remove all nondigit characters, pad the result to 4 digits,
21
+ # and interpret the first two as an hour value, the last two as a minute value
22
+ #
23
+ # padding takes place as follows :
24
+ # * one digit becomes 0d00 (assumes "7" means "0700")
25
+ # * two digits become dd00 (assumes "11" means "1100")
26
+ # * three digits become 0ddd (assumes "830" means "0830")
27
+ #
28
+ def self.parse hhmm
29
+ orig = hhmm
30
+ hhmm = hhmm.gsub(/[^\d]/, "")
31
+ hhmm = "0#{hhmm}00" if hhmm.length == 1
32
+ hhmm = "#{hhmm}00" if hhmm.length == 2
33
+ hhmm = "0#{hhmm}" if hhmm.length == 3
34
+ raise "Can't parse #{orig.inspect}" unless hhmm.match(/^\d\d\d\d$/)
35
+
36
+ hh = hhmm[0,2].to_i
37
+ mm = hhmm[2,4].to_i
38
+
39
+ new hh, mm
40
+ end
41
+
42
+ def change date
43
+ date.to_time.change :hour => hour, :min => minute
44
+ end
45
+
46
+ def to_s
47
+ "#{hour.to_s.rjust 2, "0"}#{minute.to_s.rjust 2, "0"}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ module Reservation
2
+ module Schedule
3
+ #
4
+ # a utility class to match the start and end times of an Event instance, without considering the event date
5
+ #
6
+ class Interval
7
+ attr_accessor :start, :finish
8
+
9
+ def initialize start, finish
10
+ @start, @finish = start, finish
11
+ end
12
+
13
+ # true if the start time and finish time of the given event matches start and finish times of this Interval
14
+ def matches? event
15
+ start.matches_time?(event.start) && finish.matches_time?(event.finish)
16
+ end
17
+
18
+ # build a new Event with this Interval's start and finish times, on the given date
19
+ def generate date
20
+ Event.new :start => start.change(date), :finish => finish.change(date)
21
+ end
22
+
23
+ def self.from start, finish
24
+ new HourMinute.parse(start), HourMinute.parse(finish)
25
+ end
26
+
27
+ def to_s
28
+ "#{start}-#{finish}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,6 +1,6 @@
1
1
  module Reservation
2
2
  class Reservation < ActiveRecord::Base
3
- belongs_to :event, :class_name => "Reservation::Event"
3
+ belongs_to :event, :class_name => "Reservation::Event", :inverse_of => :reservations
4
4
  belongs_to :subject, :polymorphic => true
5
5
  attr_accessible :reservation_status, :role, :subject_id, :subject_type, :event_id, :event, :subject
6
6
  end
@@ -1,191 +1,9 @@
1
+ require 'reservation/hour_minute'
2
+ require 'reservation/interval'
3
+ require 'reservation/daily'
4
+ require 'reservation/weekly'
5
+
1
6
  module Reservation
2
7
  DAY_MAP = { "sun" => 0, "mon" => 1, "tue" => 2, "wed" => 3, "thu" => 4, "fri" => 5, "sat" => 6 }
3
8
  MAP_DAY = %w{ sun mon tue wed thu fri sat }
4
-
5
- module Schedule
6
- #
7
- # a utility class to match the hour and minute elements of a Time instance, without considering any other values
8
- #
9
- class HourMinute
10
- attr_accessor :hour, :minute
11
- def initialize hour, minute
12
- @hour, @minute = hour, minute
13
- end
14
-
15
- def matches_time? time
16
- time.hour == self.hour && time.min == self.minute
17
- end
18
-
19
- #
20
- # hhmm is a string containg an hour-and-minute value
21
- #
22
- # #parse will remove all nondigit characters, pad the result to 4 digits,
23
- # and interpret the first two as an hour value, the last two as a minute value
24
- #
25
- # padding takes place as follows :
26
- # * one digit becomes 0d00 (assumes "7" means "0700")
27
- # * two digits become dd00 (assumes "11" means "1100")
28
- # * three digits become 0ddd (assumes "830" means "0830")
29
- #
30
- def self.parse hhmm
31
- orig = hhmm
32
- hhmm = hhmm.gsub /[^\d]/, ""
33
- hhmm = "0#{hhmm}00" if hhmm.length == 1
34
- hhmm = "#{hhmm}00" if hhmm.length == 2
35
- hhmm = "0#{hhmm}" if hhmm.length == 3
36
- raise "Can't parse #{orig.inspect}" unless hhmm.match(/^\d\d\d\d$/)
37
-
38
- hh = hhmm[0,2].to_i
39
- mm = hhmm[2,4].to_i
40
-
41
- new hh, mm
42
- end
43
-
44
- def change date
45
- date.to_time.change :hour => hour, :min => minute
46
- end
47
-
48
- def to_s
49
- "#{hour.to_s.rjust 2, "0"}#{minute.to_s.rjust 2, "0"}"
50
- end
51
- end
52
-
53
- #
54
- # a utility class to match the start and end times of an Event instance, without considering the event date
55
- #
56
- class Interval
57
- attr_accessor :start, :finish
58
-
59
- def initialize start, finish
60
- @start, @finish = start, finish
61
- end
62
-
63
- # true if the start time and finish time of the given event matches start and finish times of this Interval
64
- def matches? event
65
- start.matches_time?(event.start) && finish.matches_time?(event.finish)
66
- end
67
-
68
- # build a new Event with this Interval's start and finish times, on the given date
69
- def generate date
70
- Event.new :start => start.change(date), :finish => finish.change(date)
71
- end
72
-
73
- def self.from start, finish
74
- new HourMinute.parse(start), HourMinute.parse(finish)
75
- end
76
-
77
- def to_s
78
- "#{start}-#{finish}"
79
- end
80
- end
81
-
82
- #
83
- # a utility class to represent a set of intervals on a single day
84
- #
85
- class Daily
86
- attr_accessor :wday, :intervals
87
-
88
- def initialize wday
89
- @wday = wday
90
- @intervals = []
91
- end
92
-
93
- def add interval
94
- intervals << interval
95
- end
96
-
97
- def matches? event
98
- return false if event.start.wday != self.wday
99
- intervals.each { |interval|
100
- return true if interval.matches? event
101
- }
102
- false
103
- end
104
-
105
- def generate date, list
106
- return list if date.wday != wday
107
- intervals.inject(list) { |list, interval|
108
- list << interval.generate(date)
109
- list
110
- }
111
- end
112
-
113
- def to_s
114
- "#{MAP_DAY[wday]} => #{intervals}"
115
- end
116
- end
117
-
118
- #
119
- # Weekly defines a set of intervals that recur weekly
120
- #
121
- # This class maintains an array of intervals for each weekday
122
- #
123
- # This class will never match multi-day events, as it assumes each event starts and ends within the same day
124
- #
125
- class Weekly
126
-
127
- # wdays is a 7-element array of Daily instances
128
- #
129
- # the zeroth element corresponds to Sunday, because Time#wday behaves that way.
130
- #
131
- attr_accessor :wdays
132
-
133
- # pattern is an array of the form
134
- #
135
- # [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
136
- # { "day" => "wed", "start" => "18", "finish" => "20" },
137
- # { "day" => "tue", "start" => "7", "finish" => "8h30" } ]
138
- #
139
- # see HourMinute#parse for how "start" and "finish" values are read
140
- #
141
- def initialize pattern
142
- self.wdays = (0..6).map { |i| Daily.new(i) }
143
-
144
- pattern.each { |interval_spec|
145
- wday = DAY_MAP[interval_spec["day"]]
146
- raise "unrecognised day #{spec["day"].inspect} in #{pattern.inspect}" if wday.nil?
147
-
148
- start = HourMinute.parse interval_spec["start"]
149
- finish = HourMinute.parse interval_spec["finish"]
150
- wdays[wday].add Interval.new(start, finish)
151
- }
152
- end
153
-
154
- # true if there exists a Daily corresponding to this event's weekday, with
155
- # an Interval corresponding to this event's start and finish times;
156
- # false otherwise
157
- #
158
- def matches? event
159
- return false if event.start.day != event.finish.day
160
- wdays[event.start.wday].matches? event
161
- end
162
-
163
- #
164
- # generate a set of Events according to this Weekly schedule, starting
165
- # on #from at the earliest, ending on #upto at the latest.
166
- #
167
- # Note: #upto is *inclusive*, this will generate events on #upto,
168
- # if the schedule allows
169
- #
170
- def generate from, upto
171
- events = []
172
- from.upto(upto).map { |date|
173
- wdays.each { |day| day.generate date, events }
174
- }
175
- events
176
- end
177
-
178
- #
179
- # return a new list including only those events in the given list
180
- # that match this weekly schedule
181
- #
182
- def filter events
183
- events.select { |e| matches? e }
184
- end
185
-
186
- def to_s
187
- wdays.map(&:to_s).join "\n"
188
- end
189
- end
190
- end
191
9
  end
@@ -1,3 +1,3 @@
1
1
  module Reservation
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -0,0 +1,83 @@
1
+ module Reservation
2
+ module Schedule
3
+ #
4
+ # Weekly defines a set of intervals that recur weekly
5
+ #
6
+ # This class maintains an array of weekdays with an interval for each
7
+ #
8
+ # This class will never match multi-day events, as it assumes each event starts and ends within the same day
9
+ #
10
+ class Weekly
11
+
12
+ # wdays is an array of Daily instances
13
+ attr_accessor :wdays
14
+
15
+ # pattern is an array of the form
16
+ #
17
+ # [ { "nth_of_month" => "all", "day" => "wed", "start" => "0930", "finish" => "10:30"},
18
+ # { "nth_of_month" => "1", "day" => "wed", "start" => "18", "finish" => "20" },
19
+ # { "nth_of_month" => "-1", "day" => "tue", "start" => "7", "finish" => "8h30" } ]
20
+ #
21
+ # see HourMinute#parse for how "start" and "finish" values are read
22
+ #
23
+ # "nth_of_month" => "all", "day" => "mon" :: all mondays in the month are considered
24
+ # "nth_of_month" => "1", "day" => "fri" :: only the first friday of the month is considered
25
+ # "nth_of_month" => "-1", "day" => "tue" :: only the last tuesday of the month is considered
26
+ # "nth_of_month" => "-2", "day" => "sat" :: only the second-last saturday of the month is considered
27
+ #
28
+ def initialize patterns
29
+ self.wdays = []
30
+
31
+ patterns.each { |pattern|
32
+ day = pattern["day"]
33
+ nth_of_month = parse_nth_of_month pattern["nth_of_month"]
34
+
35
+ start = HourMinute.parse pattern["start"]
36
+ finish = HourMinute.parse pattern["finish"]
37
+ self.wdays << Daily.new(DAY_MAP[day], nth_of_month, Interval.new(start, finish))
38
+ }
39
+ end
40
+
41
+ # true if there exists a Daily corresponding to this event's weekday, with
42
+ # an Interval corresponding to this event's start and finish times;
43
+ # false otherwise
44
+ #
45
+ def matches? event
46
+ return false if event.start.day != event.finish.day
47
+ self.wdays.each { |wday| return true if wday.matches?(event) }
48
+ false
49
+ end
50
+
51
+ #
52
+ # generate a set of Events according to this Weekly schedule, starting
53
+ # on #from at the earliest, ending on #upto at the latest.
54
+ #
55
+ # Note: #upto is *inclusive*, this will generate events on #upto,
56
+ # if the schedule allows
57
+ #
58
+ def generate from, upto
59
+ events = []
60
+ from.upto(upto).map { |date|
61
+ wdays.each { |day| day.generate date, events }
62
+ }
63
+ events
64
+ end
65
+
66
+ #
67
+ # return a new list including only those events in the given list
68
+ # that match this weekly schedule
69
+ #
70
+ def filter events
71
+ events.select { |e| matches? e }
72
+ end
73
+
74
+ def to_s
75
+ wdays.map(&:to_s).join "\n"
76
+ end
77
+
78
+ def parse_nth_of_month txt
79
+ (txt.to_s == '' || txt.to_s == 'all') ? :all : txt.to_i
80
+ end
81
+ end
82
+ end
83
+ end
data/reservation.gemspec CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.add_development_dependency 'rspec', '~> 2.9'
23
23
  gem.add_development_dependency "sqlite3"
24
+ gem.add_development_dependency 'rspec_numbering_formatter'
24
25
 
25
26
  gem.files = `git ls-files`.split($/)
26
27
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -0,0 +1,239 @@
1
+ require 'core_ext/date'
2
+
3
+ describe Date do
4
+ describe :nth_day_of_month do
5
+ it "should return the index of this week-day in the month" do
6
+ expect(Date.new(2014, 1, 1).nth_day_of_month).to eq 1 # this is the first wednesday of the month
7
+ expect(Date.new(2014, 1, 2).nth_day_of_month).to eq 1
8
+ expect(Date.new(2014, 1, 3).nth_day_of_month).to eq 1
9
+ expect(Date.new(2014, 1, 4).nth_day_of_month).to eq 1
10
+ expect(Date.new(2014, 1, 5).nth_day_of_month).to eq 1
11
+ expect(Date.new(2014, 1, 6).nth_day_of_month).to eq 1
12
+ expect(Date.new(2014, 1, 7).nth_day_of_month).to eq 1
13
+ expect(Date.new(2014, 1, 8).nth_day_of_month).to eq 2 # this is the second wednesday of the month
14
+ expect(Date.new(2014, 1, 9).nth_day_of_month).to eq 2
15
+ expect(Date.new(2014, 1, 10).nth_day_of_month).to eq 2
16
+ expect(Date.new(2014, 1, 11).nth_day_of_month).to eq 2
17
+ expect(Date.new(2014, 1, 12).nth_day_of_month).to eq 2
18
+ expect(Date.new(2014, 1, 13).nth_day_of_month).to eq 2
19
+ expect(Date.new(2014, 1, 14).nth_day_of_month).to eq 2
20
+ expect(Date.new(2014, 1, 15).nth_day_of_month).to eq 3
21
+ expect(Date.new(2014, 1, 16).nth_day_of_month).to eq 3
22
+ expect(Date.new(2014, 1, 17).nth_day_of_month).to eq 3
23
+ expect(Date.new(2014, 1, 18).nth_day_of_month).to eq 3
24
+ expect(Date.new(2014, 1, 19).nth_day_of_month).to eq 3
25
+ expect(Date.new(2014, 1, 20).nth_day_of_month).to eq 3
26
+ expect(Date.new(2014, 1, 21).nth_day_of_month).to eq 3
27
+ expect(Date.new(2014, 1, 22).nth_day_of_month).to eq 4
28
+ expect(Date.new(2014, 1, 23).nth_day_of_month).to eq 4
29
+ expect(Date.new(2014, 1, 24).nth_day_of_month).to eq 4
30
+ expect(Date.new(2014, 1, 25).nth_day_of_month).to eq 4
31
+ expect(Date.new(2014, 1, 26).nth_day_of_month).to eq 4
32
+ expect(Date.new(2014, 1, 27).nth_day_of_month).to eq 4
33
+ expect(Date.new(2014, 1, 28).nth_day_of_month).to eq 4
34
+ expect(Date.new(2014, 1, 29).nth_day_of_month).to eq 5 # this is the 5th wednesday of the month
35
+ expect(Date.new(2014, 1, 30).nth_day_of_month).to eq 5
36
+ expect(Date.new(2014, 1, 31).nth_day_of_month).to eq 5
37
+
38
+ expect(Date.new(2014, 2, 1).nth_day_of_month).to eq 1 # this is the first saturday of the month
39
+ expect(Date.new(2014, 2, 2).nth_day_of_month).to eq 1
40
+ expect(Date.new(2014, 2, 3).nth_day_of_month).to eq 1
41
+ expect(Date.new(2014, 2, 4).nth_day_of_month).to eq 1
42
+ expect(Date.new(2014, 2, 5).nth_day_of_month).to eq 1
43
+ expect(Date.new(2014, 2, 6).nth_day_of_month).to eq 1
44
+ expect(Date.new(2014, 2, 7).nth_day_of_month).to eq 1
45
+ expect(Date.new(2014, 2, 8).nth_day_of_month).to eq 2 # this is the second saturday of the month
46
+ expect(Date.new(2014, 2, 9).nth_day_of_month).to eq 2
47
+ expect(Date.new(2014, 2, 10).nth_day_of_month).to eq 2
48
+ expect(Date.new(2014, 2, 11).nth_day_of_month).to eq 2
49
+ expect(Date.new(2014, 2, 12).nth_day_of_month).to eq 2
50
+ expect(Date.new(2014, 2, 13).nth_day_of_month).to eq 2
51
+ expect(Date.new(2014, 2, 14).nth_day_of_month).to eq 2
52
+ expect(Date.new(2014, 2, 15).nth_day_of_month).to eq 3
53
+ expect(Date.new(2014, 2, 16).nth_day_of_month).to eq 3
54
+ expect(Date.new(2014, 2, 17).nth_day_of_month).to eq 3
55
+ expect(Date.new(2014, 2, 18).nth_day_of_month).to eq 3
56
+ expect(Date.new(2014, 2, 19).nth_day_of_month).to eq 3
57
+ expect(Date.new(2014, 2, 20).nth_day_of_month).to eq 3
58
+ expect(Date.new(2014, 2, 21).nth_day_of_month).to eq 3
59
+ expect(Date.new(2014, 2, 22).nth_day_of_month).to eq 4
60
+ expect(Date.new(2014, 2, 23).nth_day_of_month).to eq 4
61
+ expect(Date.new(2014, 2, 24).nth_day_of_month).to eq 4
62
+ expect(Date.new(2014, 2, 25).nth_day_of_month).to eq 4
63
+ expect(Date.new(2014, 2, 26).nth_day_of_month).to eq 4
64
+ expect(Date.new(2014, 2, 27).nth_day_of_month).to eq 4
65
+ expect(Date.new(2014, 2, 28).nth_day_of_month).to eq 4 # this is the fourth friday of the month
66
+
67
+ expect(Date.new(2014, 3, 1).nth_day_of_month).to eq 1
68
+ expect(Date.new(2014, 3, 2).nth_day_of_month).to eq 1
69
+ expect(Date.new(2014, 3, 3).nth_day_of_month).to eq 1
70
+ expect(Date.new(2014, 3, 4).nth_day_of_month).to eq 1
71
+ expect(Date.new(2014, 3, 5).nth_day_of_month).to eq 1
72
+ expect(Date.new(2014, 3, 6).nth_day_of_month).to eq 1
73
+ expect(Date.new(2014, 3, 7).nth_day_of_month).to eq 1
74
+ expect(Date.new(2014, 3, 8).nth_day_of_month).to eq 2
75
+ expect(Date.new(2014, 3, 9).nth_day_of_month).to eq 2
76
+ expect(Date.new(2014, 3, 10).nth_day_of_month).to eq 2
77
+ expect(Date.new(2014, 3, 11).nth_day_of_month).to eq 2
78
+ expect(Date.new(2014, 3, 12).nth_day_of_month).to eq 2
79
+ expect(Date.new(2014, 3, 13).nth_day_of_month).to eq 2
80
+ expect(Date.new(2014, 3, 14).nth_day_of_month).to eq 2
81
+ expect(Date.new(2014, 3, 15).nth_day_of_month).to eq 3
82
+ expect(Date.new(2014, 3, 16).nth_day_of_month).to eq 3
83
+ expect(Date.new(2014, 3, 17).nth_day_of_month).to eq 3
84
+ expect(Date.new(2014, 3, 18).nth_day_of_month).to eq 3
85
+ expect(Date.new(2014, 3, 19).nth_day_of_month).to eq 3
86
+ expect(Date.new(2014, 3, 20).nth_day_of_month).to eq 3
87
+ expect(Date.new(2014, 3, 21).nth_day_of_month).to eq 3
88
+ expect(Date.new(2014, 3, 22).nth_day_of_month).to eq 4
89
+ expect(Date.new(2014, 3, 23).nth_day_of_month).to eq 4
90
+ expect(Date.new(2014, 3, 24).nth_day_of_month).to eq 4
91
+ expect(Date.new(2014, 3, 25).nth_day_of_month).to eq 4
92
+ expect(Date.new(2014, 3, 26).nth_day_of_month).to eq 4
93
+ expect(Date.new(2014, 3, 27).nth_day_of_month).to eq 4
94
+ expect(Date.new(2014, 3, 28).nth_day_of_month).to eq 4
95
+ expect(Date.new(2014, 3, 29).nth_day_of_month).to eq 5
96
+ expect(Date.new(2014, 3, 30).nth_day_of_month).to eq 5
97
+ expect(Date.new(2014, 3, 31).nth_day_of_month).to eq 5
98
+ end
99
+ end
100
+
101
+ describe :nth_last_day_of_month do
102
+ it "should return the index of this week-day in the month" do
103
+ expect(Date.new(2014, 1, 1).nth_last_day_of_month).to eq(-5) # this is the fifth last wednesday of the month
104
+ expect(Date.new(2014, 1, 2).nth_last_day_of_month).to eq(-5)
105
+ expect(Date.new(2014, 1, 3).nth_last_day_of_month).to eq(-5)
106
+ expect(Date.new(2014, 1, 4).nth_last_day_of_month).to eq(-4)
107
+ expect(Date.new(2014, 1, 5).nth_last_day_of_month).to eq(-4)
108
+ expect(Date.new(2014, 1, 6).nth_last_day_of_month).to eq(-4)
109
+ expect(Date.new(2014, 1, 7).nth_last_day_of_month).to eq(-4)
110
+ expect(Date.new(2014, 1, 8).nth_last_day_of_month).to eq(-4) # this is the fourth last wednesday of the month
111
+ expect(Date.new(2014, 1, 9).nth_last_day_of_month).to eq(-4)
112
+ expect(Date.new(2014, 1, 10).nth_last_day_of_month).to eq(-4)
113
+ expect(Date.new(2014, 1, 11).nth_last_day_of_month).to eq(-3)
114
+ expect(Date.new(2014, 1, 12).nth_last_day_of_month).to eq(-3)
115
+ expect(Date.new(2014, 1, 13).nth_last_day_of_month).to eq(-3)
116
+ expect(Date.new(2014, 1, 14).nth_last_day_of_month).to eq(-3)
117
+ expect(Date.new(2014, 1, 15).nth_last_day_of_month).to eq(-3)
118
+ expect(Date.new(2014, 1, 16).nth_last_day_of_month).to eq(-3)
119
+ expect(Date.new(2014, 1, 17).nth_last_day_of_month).to eq(-3)
120
+ expect(Date.new(2014, 1, 18).nth_last_day_of_month).to eq(-2)
121
+ expect(Date.new(2014, 1, 19).nth_last_day_of_month).to eq(-2)
122
+ expect(Date.new(2014, 1, 20).nth_last_day_of_month).to eq(-2)
123
+ expect(Date.new(2014, 1, 21).nth_last_day_of_month).to eq(-2)
124
+ expect(Date.new(2014, 1, 22).nth_last_day_of_month).to eq(-2)
125
+ expect(Date.new(2014, 1, 23).nth_last_day_of_month).to eq(-2)
126
+ expect(Date.new(2014, 1, 24).nth_last_day_of_month).to eq(-2)
127
+ expect(Date.new(2014, 1, 25).nth_last_day_of_month).to eq(-1)
128
+ expect(Date.new(2014, 1, 26).nth_last_day_of_month).to eq(-1)
129
+ expect(Date.new(2014, 1, 27).nth_last_day_of_month).to eq(-1)
130
+ expect(Date.new(2014, 1, 28).nth_last_day_of_month).to eq(-1)
131
+ expect(Date.new(2014, 1, 29).nth_last_day_of_month).to eq(-1) # this is the last wednesday of the month
132
+ expect(Date.new(2014, 1, 30).nth_last_day_of_month).to eq(-1)
133
+ expect(Date.new(2014, 1, 31).nth_last_day_of_month).to eq(-1)
134
+
135
+ expect(Date.new(2014, 2, 1).nth_last_day_of_month).to eq(-4) # this is the fourth last saturday of the month
136
+ expect(Date.new(2014, 2, 2).nth_last_day_of_month).to eq(-4)
137
+ expect(Date.new(2014, 2, 3).nth_last_day_of_month).to eq(-4)
138
+ expect(Date.new(2014, 2, 4).nth_last_day_of_month).to eq(-4)
139
+ expect(Date.new(2014, 2, 5).nth_last_day_of_month).to eq(-4)
140
+ expect(Date.new(2014, 2, 6).nth_last_day_of_month).to eq(-4)
141
+ expect(Date.new(2014, 2, 7).nth_last_day_of_month).to eq(-4)
142
+ expect(Date.new(2014, 2, 8).nth_last_day_of_month).to eq(-3) # this is the third last saturday of the month
143
+ expect(Date.new(2014, 2, 9).nth_last_day_of_month).to eq(-3)
144
+ expect(Date.new(2014, 2, 10).nth_last_day_of_month).to eq(-3)
145
+ expect(Date.new(2014, 2, 11).nth_last_day_of_month).to eq(-3)
146
+ expect(Date.new(2014, 2, 12).nth_last_day_of_month).to eq(-3)
147
+ expect(Date.new(2014, 2, 13).nth_last_day_of_month).to eq(-3)
148
+ expect(Date.new(2014, 2, 14).nth_last_day_of_month).to eq(-3)
149
+ expect(Date.new(2014, 2, 15).nth_last_day_of_month).to eq(-2)
150
+ expect(Date.new(2014, 2, 16).nth_last_day_of_month).to eq(-2)
151
+ expect(Date.new(2014, 2, 17).nth_last_day_of_month).to eq(-2)
152
+ expect(Date.new(2014, 2, 18).nth_last_day_of_month).to eq(-2)
153
+ expect(Date.new(2014, 2, 19).nth_last_day_of_month).to eq(-2)
154
+ expect(Date.new(2014, 2, 20).nth_last_day_of_month).to eq(-2)
155
+ expect(Date.new(2014, 2, 21).nth_last_day_of_month).to eq(-2)
156
+ expect(Date.new(2014, 2, 22).nth_last_day_of_month).to eq(-1)
157
+ expect(Date.new(2014, 2, 23).nth_last_day_of_month).to eq(-1)
158
+ expect(Date.new(2014, 2, 24).nth_last_day_of_month).to eq(-1)
159
+ expect(Date.new(2014, 2, 25).nth_last_day_of_month).to eq(-1)
160
+ expect(Date.new(2014, 2, 26).nth_last_day_of_month).to eq(-1)
161
+ expect(Date.new(2014, 2, 27).nth_last_day_of_month).to eq(-1)
162
+ expect(Date.new(2014, 2, 28).nth_last_day_of_month).to eq(-1) # this is the last friday of the month
163
+
164
+ expect(Date.new(2014, 3, 1).nth_last_day_of_month).to eq(-5)
165
+ expect(Date.new(2014, 3, 2).nth_last_day_of_month).to eq(-5)
166
+ expect(Date.new(2014, 3, 3).nth_last_day_of_month).to eq(-5)
167
+ expect(Date.new(2014, 3, 4).nth_last_day_of_month).to eq(-4)
168
+ expect(Date.new(2014, 3, 5).nth_last_day_of_month).to eq(-4)
169
+ expect(Date.new(2014, 3, 6).nth_last_day_of_month).to eq(-4)
170
+ expect(Date.new(2014, 3, 7).nth_last_day_of_month).to eq(-4)
171
+ expect(Date.new(2014, 3, 8).nth_last_day_of_month).to eq(-4)
172
+ expect(Date.new(2014, 3, 9).nth_last_day_of_month).to eq(-4)
173
+ expect(Date.new(2014, 3, 10).nth_last_day_of_month).to eq(-4)
174
+ expect(Date.new(2014, 3, 11).nth_last_day_of_month).to eq(-3)
175
+ expect(Date.new(2014, 3, 12).nth_last_day_of_month).to eq(-3)
176
+ expect(Date.new(2014, 3, 13).nth_last_day_of_month).to eq(-3)
177
+ expect(Date.new(2014, 3, 14).nth_last_day_of_month).to eq(-3)
178
+ expect(Date.new(2014, 3, 15).nth_last_day_of_month).to eq(-3)
179
+ expect(Date.new(2014, 3, 16).nth_last_day_of_month).to eq(-3)
180
+ expect(Date.new(2014, 3, 17).nth_last_day_of_month).to eq(-3)
181
+ expect(Date.new(2014, 3, 18).nth_last_day_of_month).to eq(-2)
182
+ expect(Date.new(2014, 3, 19).nth_last_day_of_month).to eq(-2)
183
+ expect(Date.new(2014, 3, 20).nth_last_day_of_month).to eq(-2)
184
+ expect(Date.new(2014, 3, 21).nth_last_day_of_month).to eq(-2)
185
+ expect(Date.new(2014, 3, 22).nth_last_day_of_month).to eq(-2)
186
+ expect(Date.new(2014, 3, 23).nth_last_day_of_month).to eq(-2)
187
+ expect(Date.new(2014, 3, 24).nth_last_day_of_month).to eq(-2)
188
+ expect(Date.new(2014, 3, 25).nth_last_day_of_month).to eq(-1)
189
+ expect(Date.new(2014, 3, 26).nth_last_day_of_month).to eq(-1)
190
+ expect(Date.new(2014, 3, 27).nth_last_day_of_month).to eq(-1)
191
+ expect(Date.new(2014, 3, 28).nth_last_day_of_month).to eq(-1)
192
+ expect(Date.new(2014, 3, 29).nth_last_day_of_month).to eq(-1)
193
+ expect(Date.new(2014, 3, 30).nth_last_day_of_month).to eq(-1)
194
+ expect(Date.new(2014, 3, 31).nth_last_day_of_month).to eq(-1)
195
+ end
196
+ end
197
+
198
+ describe :nth_day_of_month? do
199
+ it "should return true or false depnding whether the date is the given nth day of the month" do
200
+ d = Date.new(2014, 3, 1)
201
+ expect(d.nth_day_of_month?( 1)).to be_true
202
+ expect(d.nth_day_of_month?( 2)).to be_false
203
+ expect(d.nth_day_of_month?( 3)).to be_false
204
+ expect(d.nth_day_of_month?( 4)).to be_false
205
+ expect(d.nth_day_of_month?( 5)).to be_false
206
+ expect(d.nth_day_of_month?(-5)).to be_true
207
+ expect(d.nth_day_of_month?(-4)).to be_false
208
+ expect(d.nth_day_of_month?(-3)).to be_false
209
+ expect(d.nth_day_of_month?(-2)).to be_false
210
+ expect(d.nth_day_of_month?(-1)).to be_false
211
+
212
+ d = Date.new(2014, 3, 15)
213
+ expect(d.nth_day_of_month?( 1)).to be_false
214
+ expect(d.nth_day_of_month?( 2)).to be_false
215
+ expect(d.nth_day_of_month?( 3)).to be_true
216
+ expect(d.nth_day_of_month?( 4)).to be_false
217
+ expect(d.nth_day_of_month?( 5)).to be_false
218
+ expect(d.nth_day_of_month?(-5)).to be_false
219
+ expect(d.nth_day_of_month?(-4)).to be_false
220
+ expect(d.nth_day_of_month?(-3)).to be_true
221
+ expect(d.nth_day_of_month?(-2)).to be_false
222
+ expect(d.nth_day_of_month?(-1)).to be_false
223
+
224
+ d = Date.new(2014, 3, 31)
225
+ expect(d.nth_day_of_month?( 1)).to be_false
226
+ expect(d.nth_day_of_month?( 2)).to be_false
227
+ expect(d.nth_day_of_month?( 3)).to be_false
228
+ expect(d.nth_day_of_month?( 4)).to be_false
229
+ expect(d.nth_day_of_month?( 5)).to be_true
230
+ expect(d.nth_day_of_month?(-5)).to be_false
231
+ expect(d.nth_day_of_month?(-4)).to be_false
232
+ expect(d.nth_day_of_month?(-3)).to be_false
233
+ expect(d.nth_day_of_month?(-2)).to be_false
234
+ expect(d.nth_day_of_month?(-1)).to be_true
235
+
236
+ expect { d.nth_day_of_month?(0) }.to raise_error(ArgumentError)
237
+ end
238
+ end
239
+ end
@@ -13,45 +13,85 @@ describe Reservation::Schedule::Daily do
13
13
  end
14
14
 
15
15
  it "should match when there exists a matching interval on the same day as the given event" do
16
- daily = Reservation::Schedule::Daily.new(5)
17
- daily.add interval("7h", "9:30")
18
- daily.add interval("18", "2230")
16
+ daily = Reservation::Schedule::Daily.new(5, :all, interval("7h", "9:30"))
17
+ daily.matches?(event).should be_true
18
+ end
19
+
20
+ it "should match when there exists a matching interval on the same nth day as the given event" do
21
+ daily = Reservation::Schedule::Daily.new(5, 2, interval("7h", "9:30"))
22
+ daily.matches?(event).should be_true
23
+ end
19
24
 
25
+ it "should match when there exists a matching interval on the same nth-last day as the given event" do
26
+ daily = Reservation::Schedule::Daily.new(5, -3, interval("7h", "9:30"))
20
27
  daily.matches?(event).should be_true
21
28
  end
22
29
 
23
30
  it "should not match when the day is different" do
24
- daily = Reservation::Schedule::Daily.new(3)
25
- daily.add interval("7h", "9:30")
26
- daily.add interval("18", "2230")
31
+ daily = Reservation::Schedule::Daily.new(3, :all, interval("7h", "9:30"))
32
+ daily.matches?(event).should be_false
33
+ end
27
34
 
35
+ it "should not match when the nth day is different" do
36
+ daily = Reservation::Schedule::Daily.new(3, 5, interval("7h", "9:30"))
28
37
  daily.matches?(event).should be_false
29
38
  end
30
39
 
31
- it "should not match when there is no corresponding interval" do
32
- daily = Reservation::Schedule::Daily.new(5)
33
- daily.add interval("7h", "9:45")
34
- daily.add interval("18", "2230")
40
+ it "should not match when the nth-last day is different" do
41
+ daily = Reservation::Schedule::Daily.new(3, -1, interval("7h", "9:30"))
42
+ daily.matches?(event).should be_false
43
+ end
35
44
 
45
+ it "should not match when there is no corresponding interval" do
46
+ daily = Reservation::Schedule::Daily.new(5, :all, interval("7h", "9:45"))
36
47
  daily.matches?(event).should be_false
37
48
  end
38
49
 
39
50
  it "should generate events corresponding to its intervals on the given matching date" do
40
- daily = Reservation::Schedule::Daily.new(5)
41
- daily.add interval("7h", "9:45")
42
- daily.add interval("18", "2230")
51
+ daily = Reservation::Schedule::Daily.new(5, :all, interval("7h", "9:45"))
52
+
53
+ events = []
54
+ daily.generate date("2013-07-12"), events
55
+ actual = events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n")
56
+ actual.should == "Fri,20130712T0700 20130712T0945"
57
+ end
58
+
59
+ it "should generate events corresponding to its intervals on the given matching nth day" do
60
+ daily = Reservation::Schedule::Daily.new(5, 2, interval("7h", "9:45"))
43
61
 
44
62
  events = []
45
63
  daily.generate date("2013-07-12"), events
46
- events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n").
47
- should == "Fri,20130712T0700 20130712T0945
48
- Fri,20130712T1800 20130712T2230"
64
+ actual = events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n")
65
+ actual.should == "Fri,20130712T0700 20130712T0945"
66
+ end
67
+
68
+ it "should generate events corresponding to its intervals on the given matching nth-last day" do
69
+ daily = Reservation::Schedule::Daily.new(5, -3, interval("7h", "9:45"))
70
+
71
+ events = []
72
+ daily.generate date("2013-07-12"), events
73
+ actual = events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n")
74
+ actual.should == "Fri,20130712T0700 20130712T0945"
49
75
  end
50
76
 
51
77
  it "should generate no events when the date does not match its weekday" do
52
- daily = Reservation::Schedule::Daily.new(2)
53
- daily.add interval("7h", "9:45")
54
- daily.add interval("18", "2230")
78
+ daily = Reservation::Schedule::Daily.new(2, :all, interval("7h", "9:45"))
79
+
80
+ events = []
81
+ daily.generate date("2013-07-12"), events
82
+ events.should == []
83
+ end
84
+
85
+ it "should generate no events when the date does not match its nth weekday" do
86
+ daily = Reservation::Schedule::Daily.new(2, 5, interval("7h", "9:45"))
87
+
88
+ events = []
89
+ daily.generate date("2013-07-12"), events
90
+ events.should == []
91
+ end
92
+
93
+ it "should generate no events when the date does not match its nth-last weekday" do
94
+ daily = Reservation::Schedule::Daily.new(2, -1, interval("7h", "9:45"))
55
95
 
56
96
  events = []
57
97
  daily.generate date("2013-07-12"), events
@@ -56,6 +56,38 @@ Fri,20130719T1000 20130719T1145
56
56
  Tue,20130723T0700 20130723T0830"
57
57
  end
58
58
 
59
+ it "should generate events for the fourth-last and second-last friday of each month" do
60
+ weekly = Reservation::Schedule::Weekly.new [ { "day" => "fri", "start" => "10h", "finish" => "11:45", "nth_of_month" => "-4" },
61
+ { "day" => "fri", "start" => "10h", "finish" => "11:45", "nth_of_month" => "-2" } ]
62
+
63
+ events = weekly.generate date("2013-01-01"), date("2013-12-31")
64
+ events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n").
65
+ should == "Fri,20130104T1000 20130104T1145
66
+ Fri,20130118T1000 20130118T1145
67
+ Fri,20130201T1000 20130201T1145
68
+ Fri,20130215T1000 20130215T1145
69
+ Fri,20130308T1000 20130308T1145
70
+ Fri,20130322T1000 20130322T1145
71
+ Fri,20130405T1000 20130405T1145
72
+ Fri,20130419T1000 20130419T1145
73
+ Fri,20130510T1000 20130510T1145
74
+ Fri,20130524T1000 20130524T1145
75
+ Fri,20130607T1000 20130607T1145
76
+ Fri,20130621T1000 20130621T1145
77
+ Fri,20130705T1000 20130705T1145
78
+ Fri,20130719T1000 20130719T1145
79
+ Fri,20130809T1000 20130809T1145
80
+ Fri,20130823T1000 20130823T1145
81
+ Fri,20130906T1000 20130906T1145
82
+ Fri,20130920T1000 20130920T1145
83
+ Fri,20131004T1000 20131004T1145
84
+ Fri,20131018T1000 20131018T1145
85
+ Fri,20131108T1000 20131108T1145
86
+ Fri,20131122T1000 20131122T1145
87
+ Fri,20131206T1000 20131206T1145
88
+ Fri,20131220T1000 20131220T1145"
89
+ end
90
+
59
91
  it "should filter a set of events" do
60
92
  weekly = Reservation::Schedule::Weekly.new [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
61
93
  { "day" => "wed", "start" => "18", "finish" => "20" },
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reservation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-16 00:00:00.000000000 Z
12
+ date: 2014-01-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -91,6 +91,22 @@ dependencies:
91
91
  - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec_numbering_formatter
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
94
110
  description: A very basic reservation system using ActiveRecord
95
111
  email:
96
112
  - conan@conandalton.net
@@ -105,17 +121,22 @@ files:
105
121
  - MIT-LICENSE
106
122
  - README.md
107
123
  - Rakefile
124
+ - lib/core_ext/date.rb
108
125
  - lib/generators/reservation/migration/USAGE
109
126
  - lib/generators/reservation/migration/migration_generator.rb
110
127
  - lib/generators/reservation/migration/templates/migration.rb
111
128
  - lib/reservation.rb
129
+ - lib/reservation/daily.rb
112
130
  - lib/reservation/event.rb
113
131
  - lib/reservation/event_filter.rb
132
+ - lib/reservation/hour_minute.rb
133
+ - lib/reservation/interval.rb
114
134
  - lib/reservation/reservation.rb
115
135
  - lib/reservation/schedule.rb
116
136
  - lib/reservation/scopes.rb
117
137
  - lib/reservation/time_offset.rb
118
138
  - lib/reservation/version.rb
139
+ - lib/reservation/weekly.rb
119
140
  - lib/tasks/reservation_tasks.rake
120
141
  - reservation.gemspec
121
142
  - spec/dummy/README.rdoc
@@ -172,6 +193,7 @@ files:
172
193
  - spec/dummy/test/unit/reservation/reservation_test.rb
173
194
  - spec/dummy/test/unit/reservation/time_slot_test.rb
174
195
  - spec/dummy/test/unit/thing_test.rb
196
+ - spec/models/date_spec.rb
175
197
  - spec/models/event_spec.rb
176
198
  - spec/models/schedule/daily_spec.rb
177
199
  - spec/models/schedule/hour_minute_spec.rb
@@ -258,6 +280,7 @@ test_files:
258
280
  - spec/dummy/test/unit/reservation/reservation_test.rb
259
281
  - spec/dummy/test/unit/reservation/time_slot_test.rb
260
282
  - spec/dummy/test/unit/thing_test.rb
283
+ - spec/models/date_spec.rb
261
284
  - spec/models/event_spec.rb
262
285
  - spec/models/schedule/daily_spec.rb
263
286
  - spec/models/schedule/hour_minute_spec.rb