availability 0.0.3 → 0.0.4
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.
- checksums.yaml +4 -4
- data/README.md +50 -9
- data/availability.gemspec +2 -0
- data/examples/scheduler_spec.rb +1 -12
- data/lib/availability.rb +1 -0
- data/lib/availability/abstract_availability.rb +17 -13
- data/lib/availability/exclusion.rb +50 -1
- data/lib/availability/factory_methods.rb +1 -1
- data/lib/availability/hourly.rb +29 -0
- data/lib/availability/once.rb +4 -0
- data/lib/availability/version.rb +1 -1
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3be1734eee2d550422776a8d399236e4e9fdd2ca
|
4
|
+
data.tar.gz: 4c98f924c2a0a12daafb846886a8be06a4a85358
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d334f811b0745a5774ca32a5e0357694abf6b72973d67c59668b93c06b44bd8b640ae5dedeae529d164265273bb4307dc94418a59d405cd6b2cd523a18fbf13
|
7
|
+
data.tar.gz: 71ff6a5d558627ce59c4bfafdb6e81102ea60c7e8dda203ed30b9d52513559670b2f224dc7163202ffbc604b65cac33b75a9a3e1ecdcd58cf9dbd5bad5159629
|
data/README.md
CHANGED
@@ -20,9 +20,9 @@ There are a few convenience factory methods beyond the main factory methods:
|
|
20
20
|
- `Availability.every_two_{days,weeks,months,years}` and `Availability.every_other_{day,week,month,year}` will create the appropriate availability with an interval of 2
|
21
21
|
- There are other `every_*` factory methods for intervals of 3, 4, and 5 (e.g. `every_three_months`)
|
22
22
|
|
23
|
-
##
|
23
|
+
## Basic Usage
|
24
24
|
|
25
|
-
### [#
|
25
|
+
### [#includes?/1][INCLUDES]
|
26
26
|
This method takes a date or time and responds with a boolean indicating whether or not it is covered by the receiver.
|
27
27
|
|
28
28
|
### [#corresponds_to?/1][CORRESPONDS_TO]
|
@@ -40,14 +40,55 @@ This returns a single time object for the next occurrence on or after the given
|
|
40
40
|
## Examples
|
41
41
|
|
42
42
|
```ruby
|
43
|
-
# Every Monday from 9:00 AM to 10:00 AM starting on May 2, 2016
|
44
|
-
Availability.every_other_week(start_time: Time.new(2016, 5, 2, 9), duration: 1.hour)
|
43
|
+
# Every other Monday from 9:00 AM to 10:00 AM starting on May 2, 2016
|
44
|
+
every_other_monday = Availability.every_other_week(start_time: Time.new(2016, 5, 2, 9), duration: 1.hour)
|
45
|
+
every_other_monday.includes? Time.new(2016, 5, 30, 9) # => true
|
46
|
+
every_other_monday.includes? Time.new(2016, 5, 30, 10) # => false, because it lasts only an hour
|
47
|
+
every_other_monday.includes? Time.new(2016, 5, 23, 9) # => false, because it's not a covered Monday
|
48
|
+
every_other_monday.includes? Time.new(2016, 5, 18, 9) # => false, because it's not a Monday
|
45
49
|
|
46
50
|
# A business week starting on May 2, 2016 going from 1:30 PM until 2:00 PM every day
|
47
|
-
Availability.daily(start_time: Time.new(2016, 5, 2, 13, 30), stops_by: Time.new(2016, 5, 6), duration: 30.minutes)
|
51
|
+
biz_week = Availability.daily(start_time: Time.new(2016, 5, 2, 13, 30), stops_by: Time.new(2016, 5, 6), duration: 30.minutes)
|
52
|
+
|
53
|
+
biz_week.includes? Time.new(2016, 5, 3, 13, 30) #=> true
|
54
|
+
biz_week.includes? Time.new(2016, 5, 3, 14, 30) #=> false
|
55
|
+
biz_week.includes? Time.new(2016, 5, 6, 13, 30) #=> true
|
48
56
|
|
49
57
|
# A semi-monthly availability occurring all day, without an end
|
50
|
-
Availability.every_other_month(start_time: Time.new(2016, 1, 1), duration: 1.day)
|
58
|
+
every_other_month = Availability.every_other_month(start_time: Time.new(2016, 1, 1), duration: 1.day)
|
59
|
+
|
60
|
+
every_other_month.includes? Time.new(2016, 3, 1) #=> true
|
61
|
+
every_other_month.includes? Time.new(4037, 7, 1) #=> true
|
62
|
+
```
|
63
|
+
|
64
|
+
Exclusion rules can be added to an availability to further restrict it. For instance, if you wanted to create an availability for business days that spanned more than a single week you might do something like the following (note that exclusion rules need only to respond to `violated_by?(time)`).
|
65
|
+
```ruby
|
66
|
+
class BusinessDayRule
|
67
|
+
def initialize
|
68
|
+
@not_on_sunday = Availability::Exclusion.on_day_of_week(0)
|
69
|
+
@not_on_saturday = Availability::Exclusion.on_day_of_week(6)
|
70
|
+
@after_work_hours = Availability::Exclusion.after_time(Time.parse('18:00'))
|
71
|
+
@before_work_hours = Availability::Exclusion.before_time(Time.parse('08:00'))
|
72
|
+
end
|
73
|
+
|
74
|
+
def violated_by?(time)
|
75
|
+
@not_on_saturday.violated_by?(time) ||
|
76
|
+
@not_on_sunday.violated_by?(time) ||
|
77
|
+
@after_work_hours.violated_by?(time) ||
|
78
|
+
@before_work_hours.violated_by?(time)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
business_days = Availability.daily(
|
83
|
+
start_time: Time.new(2016, 1, 1, 8),
|
84
|
+
duration: 1.hour,
|
85
|
+
exclusions: [Availability::Exclusion.new(BusinessDayRule.new)]
|
86
|
+
)
|
87
|
+
|
88
|
+
business_days.includes? Time.new(2016, 5, 2, 8) #=> true
|
89
|
+
business_days.includes? Time.new(2016, 5, 2, 10) #=> true
|
90
|
+
business_days.includes? Time.new(2016, 5, 2, 7) #=> false
|
91
|
+
business_days.includes? Time.new(2016, 5, 2, 18) #=> false
|
51
92
|
```
|
52
93
|
|
53
94
|
## TODO
|
@@ -89,8 +130,8 @@ see <http://unlicense.org/> or the accompanying [UNLICENSE]{UNLICENSE} file.
|
|
89
130
|
[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
|
90
131
|
[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
|
91
132
|
[DOCS]: http://www.rubydoc.info/gems/availability
|
92
|
-
[
|
133
|
+
[INCLUDES]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#includes%3F-instance_method
|
93
134
|
[CORRESPONDS_TO]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#corresponds_to%3F-instance_method
|
94
135
|
[LAST_OCCURRENCE]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#last_occurrence-instance_method
|
95
|
-
[NEXT_N_OCCURS]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#next_n_occurrences
|
96
|
-
[NEXT_OCCUR]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#next_occurrence
|
136
|
+
[NEXT_N_OCCURS]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#next_n_occurrences-instance_method
|
137
|
+
[NEXT_OCCUR]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#next_occurrence-instance_method
|
data/availability.gemspec
CHANGED
@@ -26,4 +26,6 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
27
|
spec.add_development_dependency "rspec"
|
28
28
|
spec.add_development_dependency "rspec-its"
|
29
|
+
spec.add_development_dependency "pry"
|
30
|
+
spec.add_development_dependency "pry-byebug"
|
29
31
|
end
|
data/examples/scheduler_spec.rb
CHANGED
@@ -71,19 +71,8 @@ RSpec.describe Scheduler do
|
|
71
71
|
end
|
72
72
|
|
73
73
|
describe '#allow?' do
|
74
|
-
context 'enforces parameter values' do
|
75
|
-
it { expect{bobs_schedule.allow?}.to raise_error ArgumentError }
|
76
|
-
it { expect{bobs_schedule.allow? availability_request: nil}.to raise_error ArgumentError }
|
77
|
-
it { expect{bobs_schedule.allow? start_time: nil }.to raise_error ArgumentError }
|
78
|
-
it { expect{bobs_schedule.allow? start_time: Date.today }.to raise_error ArgumentError }
|
79
|
-
it { expect{bobs_schedule.allow? end_time: nil }.to raise_error ArgumentError }
|
80
|
-
it { expect{bobs_schedule.allow? end_time: Date.today }.to raise_error ArgumentError }
|
81
|
-
it { expect{bobs_schedule.allow? availability_request: one_hour_slot_per_week(start_time: T(0))}.not_to raise_error }
|
82
|
-
it { expect{bobs_schedule.allow? start_time: Date.today, end_time: Date.tomorrow}.not_to raise_error }
|
83
|
-
end
|
84
|
-
|
85
74
|
context 'with Availability objects' do
|
86
|
-
it 'allows an event
|
75
|
+
it 'allows an event request that starts in the first week and goes through the end of the availability' do
|
87
76
|
bob_availabilities.each do |a|
|
88
77
|
expect(bobs_schedule.allow? availability_request: a).to be_truthy, "at #{a.start_time}"
|
89
78
|
end
|
data/lib/availability.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative "availability/version"
|
|
4
4
|
require_relative 'availability/createable'
|
5
5
|
require_relative 'availability/exclusion'
|
6
6
|
require_relative 'availability/abstract_availability'
|
7
|
+
require_relative 'availability/hourly'
|
7
8
|
require_relative 'availability/daily'
|
8
9
|
require_relative 'availability/weekly'
|
9
10
|
require_relative 'availability/monthly'
|
@@ -51,13 +51,14 @@ module Availability
|
|
51
51
|
# @return [Boolean] true or false
|
52
52
|
#
|
53
53
|
def corresponds_to?(availability)
|
54
|
-
return false unless
|
54
|
+
return false unless includes?(availability.start_time) \
|
55
|
+
&& includes?(availability.start_time + availability.duration - 1.second)
|
55
56
|
if !!stops_by
|
56
57
|
that_last = availability.last_occurrence
|
57
|
-
!that_last.nil?
|
58
|
-
|
59
|
-
|
60
|
-
that_last.to_date <= self.last_occurrence.to_date
|
58
|
+
!that_last.nil? \
|
59
|
+
&& includes?(that_last) \
|
60
|
+
&& includes?(that_last + availability.duration - 1.second) \
|
61
|
+
&& that_last.to_date <= self.last_occurrence.to_date
|
61
62
|
else
|
62
63
|
true
|
63
64
|
end
|
@@ -66,12 +67,15 @@ module Availability
|
|
66
67
|
#
|
67
68
|
# Whether or not the given time is covered by the receiver
|
68
69
|
#
|
69
|
-
# @param [Time] time the
|
70
|
+
# @param [Time] time the Time to test for coverage
|
70
71
|
#
|
71
72
|
# @return [Boolean] true or false
|
72
73
|
#
|
73
|
-
def
|
74
|
-
|
74
|
+
def includes?(time)
|
75
|
+
next_occurrence = next_occurrence(time) || last_occurrence
|
76
|
+
residue_for(time) == @residue \
|
77
|
+
&& !next_occurrence.nil? \
|
78
|
+
&& time_overlaps?(time, next_occurrence, next_occurrence + duration)
|
75
79
|
end
|
76
80
|
|
77
81
|
# @!endgroup
|
@@ -105,7 +109,7 @@ module Availability
|
|
105
109
|
residue = @residue - residue_for(from_date)
|
106
110
|
date = move_by from_date, residue.modulo(interval)
|
107
111
|
time = Time.new(date.year, date.month, date.day, start_time.hour, start_time.min, start_time.sec)
|
108
|
-
if exclusions.
|
112
|
+
if (exx = exclusions.detect {|rule| rule.violated_by? time})
|
109
113
|
if stops_by && time > stops_by
|
110
114
|
nil
|
111
115
|
else
|
@@ -173,10 +177,10 @@ module Availability
|
|
173
177
|
# @!group Helpers
|
174
178
|
|
175
179
|
def time_overlaps?(time, start_time, end_time)
|
176
|
-
that_start = time.
|
177
|
-
this_start = start_time.
|
178
|
-
this_end = end_time.
|
179
|
-
(this_start
|
180
|
+
that_start = time.to_i
|
181
|
+
this_start = start_time.to_i
|
182
|
+
this_end = end_time.to_i
|
183
|
+
(this_start...this_end).include?(that_start)
|
180
184
|
end
|
181
185
|
|
182
186
|
# @!endgroup
|
@@ -1,12 +1,17 @@
|
|
1
1
|
module Availability
|
2
2
|
class Exclusion
|
3
|
-
|
3
|
+
attr_reader :rule
|
4
4
|
|
5
5
|
def self.after_day(date)
|
6
6
|
raise ArgumentError, "invalid date" if date.nil?
|
7
7
|
new Rule::AfterDate.new(date.to_date)
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.after_date_and_time(time)
|
11
|
+
raise ArgumentError, "invalid time" if time.nil?
|
12
|
+
new Rule::AfterDateAndTime.new(time.to_time)
|
13
|
+
end
|
14
|
+
|
10
15
|
def self.after_time(time)
|
11
16
|
raise ArgumentError, "invalid time" if time.nil?
|
12
17
|
new Rule::AfterTime.new(time.to_time)
|
@@ -22,11 +27,23 @@ module Availability
|
|
22
27
|
new Rule::BeforeDate.new(date.to_date)
|
23
28
|
end
|
24
29
|
|
30
|
+
def self.before_date_and_time(time)
|
31
|
+
raise ArgumentError, "invalid time" if time.nil?
|
32
|
+
new Rule::BeforeDateAndTime.new(time.to_time)
|
33
|
+
end
|
34
|
+
|
25
35
|
def self.before_time(time)
|
26
36
|
raise ArgumentError, "invalid time" if time.nil?
|
27
37
|
new Rule::BeforeTime.new(time.to_time)
|
28
38
|
end
|
29
39
|
|
40
|
+
def self.on_day_of_week(day_of_week) # 0=Sunday, 6=Saturday
|
41
|
+
unless day_of_week.is_a?(Fixnum) && (0..6).include?(day_of_week)
|
42
|
+
raise ArgumentError, "invalid day of week"
|
43
|
+
end
|
44
|
+
new Rule::OnDayOfWeek.new(day_of_week)
|
45
|
+
end
|
46
|
+
|
30
47
|
def initialize(rule)
|
31
48
|
@rule = rule
|
32
49
|
end
|
@@ -47,6 +64,17 @@ module Availability
|
|
47
64
|
end
|
48
65
|
end
|
49
66
|
|
67
|
+
class AfterDateAndTime
|
68
|
+
def initialize(time)
|
69
|
+
@after_date = AfterDate.new time.to_date
|
70
|
+
@after_time = AfterTime.new time
|
71
|
+
end
|
72
|
+
|
73
|
+
def violated_by?(time)
|
74
|
+
@after_date.violated_by?(time) || @after_time.violated_by?(time)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
50
78
|
class AfterTime
|
51
79
|
def initialize(date_or_time)
|
52
80
|
@compare_to = date_or_time.to_time
|
@@ -67,6 +95,17 @@ module Availability
|
|
67
95
|
end
|
68
96
|
end
|
69
97
|
|
98
|
+
class BeforeDateAndTime
|
99
|
+
def initialize(time)
|
100
|
+
@before_date = BeforeDate.new time.to_date
|
101
|
+
@before_time = BeforeTime.new time
|
102
|
+
end
|
103
|
+
|
104
|
+
def violated_by?(time)
|
105
|
+
@before_date.violated_by?(time) || @before_time.violated_by?(time)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
70
109
|
class BeforeTime
|
71
110
|
def initialize(date_or_time)
|
72
111
|
@compare_to = date_or_time.to_time
|
@@ -86,6 +125,16 @@ module Availability
|
|
86
125
|
time.to_date == @date
|
87
126
|
end
|
88
127
|
end
|
128
|
+
|
129
|
+
class OnDayOfWeek
|
130
|
+
def initialize(day_of_week)
|
131
|
+
@day_of_week = day_of_week
|
132
|
+
end
|
133
|
+
|
134
|
+
def violated_by?(time)
|
135
|
+
time.wday == @day_of_week
|
136
|
+
end
|
137
|
+
end
|
89
138
|
end
|
90
139
|
end
|
91
140
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'abstract_availability'
|
2
|
+
require_relative 'daily'
|
3
|
+
|
4
|
+
module Availability
|
5
|
+
class Hourly < Daily
|
6
|
+
extend Createable
|
7
|
+
|
8
|
+
def interval_difference(this, that)
|
9
|
+
first, second = [this, that].sort
|
10
|
+
(second.to_i - first.to_i) / 1.hour
|
11
|
+
end
|
12
|
+
|
13
|
+
def move_by(time, amount)
|
14
|
+
time + amount.hours
|
15
|
+
end
|
16
|
+
|
17
|
+
def includes?(time)
|
18
|
+
return true if super
|
19
|
+
return false if residue_for(time) != residue
|
20
|
+
hours_on_same_day = next_n_occurrences(24, time).select {|t| t.wday == time.wday && t <= time }
|
21
|
+
hours_on_same_day.none? {|hour| exclusions.any?{|excl| excl.violated_by? hour}} &&
|
22
|
+
hours_on_same_day.any?{|hour| time_overlaps? time, hour, hour + duration}
|
23
|
+
end
|
24
|
+
|
25
|
+
def residue_for(time)
|
26
|
+
interval_difference(time, beginning.to_time).modulo(@interval)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/availability/once.rb
CHANGED
data/lib/availability/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: availability
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Rogers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry-byebug
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
description: Use modular arithmetic and residue classes to calculate schedule availability
|
84
112
|
for dates (times handled separately).
|
85
113
|
email:
|
@@ -113,6 +141,7 @@ files:
|
|
113
141
|
- lib/availability/daily.rb
|
114
142
|
- lib/availability/exclusion.rb
|
115
143
|
- lib/availability/factory_methods.rb
|
144
|
+
- lib/availability/hourly.rb
|
116
145
|
- lib/availability/monthly.rb
|
117
146
|
- lib/availability/once.rb
|
118
147
|
- lib/availability/version.rb
|