availability 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8eaf871af31feb52dcf53dcf209011faa140849a
4
- data.tar.gz: ea9daa4546d229bdd17b454d2ef51bac5e33bd1c
3
+ metadata.gz: 3be1734eee2d550422776a8d399236e4e9fdd2ca
4
+ data.tar.gz: 4c98f924c2a0a12daafb846886a8be06a4a85358
5
5
  SHA512:
6
- metadata.gz: cac1e269b14a79478b15db2c34c11cb2166762c3c50b4a6dec66953e574d3500461f0fb5353ab14a6a6717c1197f258b0ba5cff07f3fd3da9d36ce9ab75e03fe
7
- data.tar.gz: b9cfe1fc23f4b068a9244d65ef5f2c7543bbd17732429a856b8eeaa3e83008438a70cac2dc0b36cf21750b3af4ef9acc81c9edda7b098c7a7441037e841c3afc
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
- ## Primary API
23
+ ## Basic Usage
24
24
 
25
- ### [#occurs_at?/1][OCCURS_AT]
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
- [OCCURS_AT]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#occurs_at%3F-instance_method
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%3F-instance_method
96
- [NEXT_OCCUR]: http://www.rubydoc.info/gems/availability/Availability/AbstractAvailability#next_occurrence%3F-instance_method
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
@@ -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 requests that start in the first week and go through the end of the availability' do
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 occurs_at?(availability.start_time) && occurs_at?(availability.start_time + availability.duration)
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
- occurs_at?(that_last) &&
59
- occurs_at?(that_last + availability.duration) &&
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 Date, Time, or DateTime to test for coverage
70
+ # @param [Time] time the Time to test for coverage
70
71
  #
71
72
  # @return [Boolean] true or false
72
73
  #
73
- def occurs_at?(time)
74
- residue_for(time) == @residue && time_overlaps?(time, start_time, start_time + duration)
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.any? {|rule| rule.violated_by? time}
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.seconds_since_midnight.to_i
177
- this_start = start_time.seconds_since_midnight.to_i
178
- this_end = end_time.seconds_since_midnight.to_i
179
- (this_start..this_end).include?(that_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
- private_class_method :new # :nodoc:
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
@@ -10,7 +10,7 @@ module Availability
10
10
  Once.create **args
11
11
  end
12
12
 
13
- %w{day week month year}.each do |suffix|
13
+ %w{hour day week month year}.each do |suffix|
14
14
  frequency = suffix == 'day' ? :daily : :"#{suffix}ly"
15
15
  cls = Availability::subclass_for(frequency)
16
16
 
@@ -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
@@ -22,6 +22,10 @@ module Availability
22
22
  start_time
23
23
  end
24
24
 
25
+ def next_occurrence(time)
26
+ start_time
27
+ end
28
+
25
29
  def residue_for(time)
26
30
  0
27
31
  end
@@ -1,3 +1,3 @@
1
1
  module Availability
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
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.3
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-03 00:00:00.000000000 Z
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