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