reservation 0.0.4 → 0.0.5

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