biz 1.4.0 → 1.5.0

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: 44d8fc924a2b8a852ff1764be148922e4008590d
4
- data.tar.gz: 29ac9f7b0423c1b3d329b1d5e7485071fcf32ef3
3
+ metadata.gz: 1bfccc20f9a27336663959946a872593d35c53a3
4
+ data.tar.gz: 962e4f9927fb08f8f609e76d3f11b10949d81922
5
5
  SHA512:
6
- metadata.gz: a4a37ee33bd1d466bc7fd6bab13a36ccc6c1fad2ecad17f4ea3332aec2fd5233fd7bd8419354966eba261eee22a74fc2c137458fd22cf2d9cda5ace0d909db8a
7
- data.tar.gz: 54078121bab03914cbbeac6a03d2cc4c9dae945c8fbd36d387badf48bdafa5afbc7d3e40e76a818495cddc92b833144cb418905f836a4778655ea5559d6b93ed
6
+ metadata.gz: 63aeb93f5ce1299b4164ba9ec5d537032d6f8ff174ca5b08c0fd62d62a4afec902b4528ec4d1d606899764cf20aacddd08c8cf64f95dc81348f50bf6a1446968
7
+ data.tar.gz: d81c55c540298d454bab98a9d54243cb139f2aeed16ff4b1c817479e63798dab3909dfdb43c35339646d9139a508fa78bb9f9b84a43d09863d2c38b5b36c448c
data/README.md CHANGED
@@ -17,6 +17,7 @@ Time calculations using business hours.
17
17
  - Multiple schedule configurations.
18
18
  * Second-level precision on all calculations.
19
19
  * Accurate handling of Daylight Saving Time.
20
+ * Schedule intersection.
20
21
  * Thread-safe.
21
22
 
22
23
  ## Anti-Features
@@ -50,7 +51,7 @@ Biz.configure do |config|
50
51
  sat: {'10:00' => '14:00'}
51
52
  }
52
53
 
53
- config.holidays = [Date.new(2014, 1, 1), Date.new(2014, 12, 25)]
54
+ config.holidays = [Date.new(2016, 1, 1), Date.new(2016, 12, 25)]
54
55
 
55
56
  config.time_zone = 'America/Los_Angeles'
56
57
  end
@@ -120,6 +121,74 @@ Biz.periods
120
121
  # #<Biz::TimeSegment start_time=2015-04-27 20:36:57 UTC end_time=2015-04-28 00:00:00 UTC>]
121
122
  ```
122
123
 
124
+ ## Schedule Intersection
125
+
126
+ An intersection of two schedules can be found using `&`:
127
+
128
+ ```ruby
129
+ schedule_1 = Biz::Schedule.new do |config|
130
+ config.hours = {
131
+ mon: {'09:00' => '17:00'},
132
+ tue: {'10:00' => '16:00'},
133
+ wed: {'09:00' => '17:00'},
134
+ thu: {'10:00' => '16:00'},
135
+ fri: {'09:00' => '17:00'},
136
+ sat: {'11:00' => '14:30'}
137
+ }
138
+
139
+ config.holidays = [Date.new(2016, 1, 1), Date.new(2016, 12, 25)]
140
+
141
+ config.time_zone = 'Etc/UTC'
142
+ end
143
+
144
+ schedule_2 = Biz::Schedule.new do |config|
145
+ config.hours = {
146
+ sun: {'10:00' => '12:00'},
147
+ mon: {'08:00' => '10:00'},
148
+ tue: {'11:00' => '15:00'},
149
+ wed: {'16:00' => '18:00'},
150
+ thu: {'11:00' => '12:00', '13:00' => '14:00'}
151
+ }
152
+
153
+ config.holidays = [
154
+ Date.new(2016, 1, 1),
155
+ Date.new(2016, 7, 4),
156
+ Date.new(2016, 11, 24)
157
+ ]
158
+
159
+ config.time_zone = 'America/Los_Angeles'
160
+ end
161
+
162
+ schedule_1 & schedule_2
163
+ ```
164
+
165
+ The resulting schedule will be a combination of the two schedules: an
166
+ intersection of the intervals, a union of the holidays, and the time zone of the
167
+ first schedule.
168
+
169
+ For the above example, the resulting schedule would be equivalent to one with
170
+ the following configuration:
171
+
172
+ ```ruby
173
+ Biz::Schedule.new do |config|
174
+ config.hours = {
175
+ mon: {'09:00' => '10:00'},
176
+ tue: {'11:00' => '15:00'},
177
+ wed: {'16:00' => '17:00'},
178
+ thu: {'11:00' => '12:00', '13:00' => '14:00'}
179
+ }
180
+
181
+ config.holidays = [
182
+ Date.new(2016, 1, 1),
183
+ Date.new(2016, 7, 4),
184
+ Date.new(2016, 11, 24),
185
+ Date.new(2016, 12, 25)
186
+ ]
187
+
188
+ config.time_zone = 'Etc/UTC'
189
+ end
190
+ ```
191
+
123
192
  ## Core Extensions
124
193
 
125
194
  Optional extensions to core classes (`Date`, `Fixnum`, and `Time`) are available
data/lib/biz.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'date'
2
2
  require 'delegate'
3
3
  require 'forwardable'
4
- require 'set'
5
4
 
6
5
  require 'clavius'
7
6
  require 'tzinfo'
@@ -7,6 +7,8 @@ module Biz
7
7
  yield raw if block_given?
8
8
 
9
9
  Validation.perform(raw)
10
+
11
+ raw.freeze
10
12
  end
11
13
 
12
14
  def intervals
@@ -21,7 +23,12 @@ module Biz
21
23
 
22
24
  def holidays
23
25
  @holidays ||= begin
24
- raw.holidays.map { |date| Holiday.new(date, time_zone) }.freeze
26
+ raw
27
+ .holidays
28
+ .uniq
29
+ .map { |date| Holiday.new(date, time_zone) }
30
+ .sort
31
+ .freeze
25
32
  end
26
33
  end
27
34
 
@@ -30,7 +37,7 @@ module Biz
30
37
  end
31
38
 
32
39
  def weekdays
33
- @weekdays ||= raw.hours.keys.to_set.freeze
40
+ @weekdays ||= raw.hours.keys.uniq.freeze
34
41
  end
35
42
 
36
43
  protected
@@ -1,18 +1,38 @@
1
1
  module Biz
2
2
  class Interval
3
3
 
4
+ extend Forwardable
5
+
4
6
  include Comparable
5
7
 
8
+ def self.to_hours(intervals)
9
+ intervals.each_with_object(
10
+ Hash.new do |hours, wday| hours.store(wday, {}) end
11
+ ) do |interval, hours|
12
+ hours[interval.wday_symbol].store(*interval.endpoints.map(&:timestamp))
13
+ end
14
+ end
15
+
6
16
  def initialize(start_time, end_time, time_zone)
7
17
  @start_time = start_time
8
18
  @end_time = end_time
9
19
  @time_zone = time_zone
10
20
  end
11
21
 
22
+ attr_reader :start_time,
23
+ :end_time,
24
+ :time_zone
25
+
26
+ delegate wday_symbol: :start_time
27
+
12
28
  def endpoints
13
29
  [start_time, end_time]
14
30
  end
15
31
 
32
+ def empty?
33
+ start_time >= end_time
34
+ end
35
+
16
36
  def contains?(time)
17
37
  (start_time...end_time).cover?(
18
38
  WeekTime.from_time(Time.new(time_zone).local(time))
@@ -27,6 +47,13 @@ module Biz
27
47
  )
28
48
  end
29
49
 
50
+ def &(other)
51
+ lower_bound = [self, other].map(&:start_time).max
52
+ upper_bound = [self, other].map(&:end_time).min
53
+
54
+ self.class.new(lower_bound, [lower_bound, upper_bound].max, time_zone)
55
+ end
56
+
30
57
  def <=>(other)
31
58
  return unless other.is_a?(self.class)
32
59
 
@@ -34,11 +61,5 @@ module Biz
34
61
  [other.start_time, other.end_time, other.time_zone]
35
62
  end
36
63
 
37
- protected
38
-
39
- attr_reader :start_time,
40
- :end_time,
41
- :time_zone
42
-
43
64
  end
44
65
  end
@@ -46,6 +46,23 @@ module Biz
46
46
  Time.new(time_zone)
47
47
  end
48
48
 
49
+ def &(other)
50
+ self.class.new do |config|
51
+ config.hours = Interval.to_hours(
52
+ intervals.flat_map { |interval|
53
+ other
54
+ .intervals
55
+ .map { |other_interval| interval & other_interval }
56
+ .reject(&:empty?)
57
+ }
58
+ )
59
+
60
+ config.holidays = [*holidays, *other.holidays].map(&:to_date)
61
+
62
+ config.time_zone = time_zone.name
63
+ end
64
+ end
65
+
49
66
  protected
50
67
 
51
68
  attr_reader :configuration
@@ -36,10 +36,10 @@ module Biz
36
36
  end
37
37
 
38
38
  def &(other)
39
- self.class.new(
40
- lower_bound(other),
41
- [lower_bound(other), upper_bound(other)].max
42
- )
39
+ lower_bound = [self, other].map(&:start_time).max
40
+ upper_bound = [self, other].map(&:end_time).min
41
+
42
+ self.class.new(lower_bound, [lower_bound, upper_bound].max)
43
43
  end
44
44
 
45
45
  def <=>(other)
@@ -48,15 +48,5 @@ module Biz
48
48
  [start_time, end_time] <=> [other.start_time, other.end_time]
49
49
  end
50
50
 
51
- private
52
-
53
- def lower_bound(other)
54
- [self, other].map(&:start_time).max
55
- end
56
-
57
- def upper_bound(other)
58
- [self, other].map(&:end_time).min
59
- end
60
-
61
51
  end
62
52
  end
@@ -1,3 +1,3 @@
1
1
  module Biz
2
- VERSION = '1.4.0'.freeze
2
+ VERSION = '1.5.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: biz
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig Little
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-03-11 00:00:00.000000000 Z
12
+ date: 2016-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: clavius
@@ -45,14 +45,14 @@ dependencies:
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '10.0'
48
+ version: '11.0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '10.0'
55
+ version: '11.0'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: rspec
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -135,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
135
  version: '0'
136
136
  requirements: []
137
137
  rubyforge_project:
138
- rubygems_version: 2.5.1
138
+ rubygems_version: 2.2.2
139
139
  signing_key:
140
140
  specification_version: 4
141
141
  summary: Business hours calculations