biz 1.4.0 → 1.5.0

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