biz 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +30 -5
  3. data/lib/biz.rb +2 -0
  4. data/lib/biz/calculation.rb +2 -0
  5. data/lib/biz/calculation/active.rb +15 -4
  6. data/lib/biz/calculation/duration_within.rb +2 -0
  7. data/lib/biz/calculation/for_duration.rb +4 -4
  8. data/lib/biz/calculation/on_break.rb +3 -1
  9. data/lib/biz/calculation/on_holiday.rb +3 -1
  10. data/lib/biz/configuration.rb +27 -10
  11. data/lib/biz/core_ext.rb +2 -0
  12. data/lib/biz/core_ext/date.rb +2 -0
  13. data/lib/biz/core_ext/integer.rb +2 -0
  14. data/lib/biz/core_ext/time.rb +2 -0
  15. data/lib/biz/date.rb +2 -0
  16. data/lib/biz/dates.rb +2 -0
  17. data/lib/biz/day_of_week.rb +4 -0
  18. data/lib/biz/day_time.rb +9 -7
  19. data/lib/biz/duration.rb +9 -5
  20. data/lib/biz/error.rb +2 -0
  21. data/lib/biz/holiday.rb +11 -7
  22. data/lib/biz/interval.rb +4 -0
  23. data/lib/biz/periods.rb +3 -0
  24. data/lib/biz/periods/abstract.rb +22 -14
  25. data/lib/biz/periods/after.rb +14 -8
  26. data/lib/biz/periods/before.rb +14 -8
  27. data/lib/biz/periods/linear.rb +41 -0
  28. data/lib/biz/periods/proxy.rb +3 -1
  29. data/lib/biz/schedule.rb +3 -0
  30. data/lib/biz/time.rb +3 -1
  31. data/lib/biz/time_segment.rb +7 -1
  32. data/lib/biz/timeline.rb +2 -0
  33. data/lib/biz/timeline/abstract.rb +3 -1
  34. data/lib/biz/timeline/backward.rb +2 -0
  35. data/lib/biz/timeline/forward.rb +2 -0
  36. data/lib/biz/timeline/proxy.rb +3 -1
  37. data/lib/biz/validation.rb +4 -2
  38. data/lib/biz/version.rb +3 -1
  39. data/lib/biz/week.rb +8 -4
  40. data/lib/biz/week_time.rb +2 -0
  41. data/lib/biz/week_time/abstract.rb +8 -4
  42. data/lib/biz/week_time/end.rb +2 -0
  43. data/lib/biz/week_time/start.rb +2 -0
  44. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: dc2d88c632e4f3c395997448b3d864883700c8c9
4
- data.tar.gz: 999e147f312f256325ddf28928d143b863961b80
2
+ SHA256:
3
+ metadata.gz: a7b79c92f3c6e05644da996c8c90a6cf7785a7dadd8bbb78c550c79f632091c5
4
+ data.tar.gz: 896b6de71bc1c79a919cb7c9086caf5f22cfbd8ecf0a8274489e7afd3812ebb7
5
5
  SHA512:
6
- metadata.gz: 9d9f6d4c2dc29cda4b2a4d589400840effcaec6a6ec104e5ee80744db56fdecd6d799a28a82ce83ceca948afb93685892e1141c737507a80f43c76f3bb00b104
7
- data.tar.gz: 450b3f8f0d3052980e9cf815f63159c5e3a6a83a38bd553622c129d66a81cb1ed454962b878fe3d362859b1b191592c6263c23f65e529de3b0835cbef3c82b7a
6
+ metadata.gz: 8bbd2b8b8a835d68d4249a2545abdd43b82ba44decdc3cc9045ff70445e908a5fa72f4ce2ee9abb8aab341d30b1c9a191bf780a69045cc70361c5f810de478b8
7
+ data.tar.gz: 0caa36b7677937f5d7b1fc92d58bc92310cbf7996976cca7a22afd1be976d9d62d78337d783d73aabf0baa2d161b1c3c77fa00fb2ae80822cb53e7f0c0810d26
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  ![biz](http://d26a57ydsghvgx.cloudfront.net/www/public/assets/images/bizlogo.png)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/biz.svg)](http://badge.fury.io/rb/biz)
4
- [![Build Status](https://travis-ci.org/zendesk/biz.svg?branch=master)](https://travis-ci.org/zendesk/biz)
4
+ [![Build Status](https://travis-ci.com/zendesk/biz.svg?branch=master)](https://travis-ci.com/zendesk/biz)
5
5
  [![Code Climate](https://codeclimate.com/github/zendesk/biz/badges/gpa.svg)](https://codeclimate.com/github/zendesk/biz)
6
6
  [![Test Coverage](https://codeclimate.com/github/zendesk/biz/badges/coverage.svg)](https://codeclimate.com/github/zendesk/biz)
7
- [![Dependency Status](https://gemnasium.com/zendesk/biz.svg)](https://gemnasium.com/zendesk/biz)
8
7
 
9
8
  Time calculations using business hours.
10
9
 
@@ -16,6 +15,7 @@ Time calculations using business hours.
16
15
  - Intervals spanning the entire day.
17
16
  - Holidays.
18
17
  - Breaks (time-segment holidays).
18
+ - Shifts (date-based intervals).
19
19
  * Second-level calculation precision.
20
20
  * Seamless Daylight Saving Time handling.
21
21
  * Schedule intersection.
@@ -52,6 +52,11 @@ Biz.configure do |config|
52
52
  sat: {'10:00' => '14:00'}
53
53
  }
54
54
 
55
+ config.shifts = {
56
+ Date.new(2006, 1, 1) => {'09:00' => '12:00'},
57
+ Date.new(2006, 1, 7) => {'08:00' => '10:00', '12:00' => '14:00'}
58
+ }
59
+
55
60
  config.breaks = {
56
61
  Date.new(2006, 1, 2) => {'10:00' => '11:30'},
57
62
  Date.new(2006, 1, 3) => {'14:15' => '14:30', '15:40' => '15:50'}
@@ -63,6 +68,10 @@ Biz.configure do |config|
63
68
  end
64
69
  ```
65
70
 
71
+ Shifts act as exceptions to the hours configured for a particular date; that is,
72
+ if a date is configured with both hours-based intervals and shifts, the shifts
73
+ are in force and the intervals are disregarded.
74
+
66
75
  Periods occurring on holidays are disregarded. Similarly, any segment of a
67
76
  period that overlaps with a break is treated as inactive.
68
77
 
@@ -194,6 +203,11 @@ schedule_1 = Biz::Schedule.new do |config|
194
203
  sat: {'11:00' => '14:30'}
195
204
  }
196
205
 
206
+ config.shifts = {
207
+ Date.new(2016, 7, 1) => {'10:00' => '13:00', '15:00' => '16:00'},
208
+ Date.new(2016, 7, 2) => {'14:00' => '17:00'}
209
+ }
210
+
197
211
  config.breaks = {
198
212
  Date.new(2016, 6, 2) => {'09:00' => '10:30', '16:00' => '16:30'},
199
213
  Date.new(2016, 6, 3) => {'12:15' => '12:45'}
@@ -213,6 +227,11 @@ schedule_2 = Biz::Schedule.new do |config|
213
227
  thu: {'11:00' => '12:00', '13:00' => '14:00'}
214
228
  }
215
229
 
230
+ config.shifts = {
231
+ Date.new(2016, 7, 1) => {'15:30' => '16:30'},
232
+ Date.new(2016, 7, 5) => {'14:00' => '18:00'}
233
+ }
234
+
216
235
  config.breaks = {
217
236
  Date.new(2016, 6, 3) => {'13:30' => '14:00'},
218
237
  Date.new(2016, 6, 4) => {'11:00' => '12:00'}
@@ -231,8 +250,9 @@ schedule_1 & schedule_2
231
250
  ```
232
251
 
233
252
  The resulting schedule will be a combination of the two schedules: an
234
- intersection of the intervals, a union of the breaks and holidays, and the time
235
- zone of the first schedule.
253
+ intersection of the intervals, a union of the breaks and holidays,
254
+ and the time zone of the first schedule. Any configured shifts will be
255
+ disregarded.
236
256
 
237
257
  For the above example, the resulting schedule would be equivalent to one with
238
258
  the following configuration:
@@ -246,6 +266,11 @@ Biz::Schedule.new do |config|
246
266
  thu: {'11:00' => '12:00', '13:00' => '14:00'}
247
267
  }
248
268
 
269
+ config.shifts = {
270
+ Date.new(2016, 7, 1) => {'15:30' => '16:00'},
271
+ Date.new(2016, 7, 5) => {'14:00' => '16:00'}
272
+ }
273
+
249
274
  config.breaks = {
250
275
  Date.new(2016, 6, 2) => {'09:00' => '10:30', '16:00' => '16:30'},
251
276
  Date.new(2016, 6, 3) => {'12:15' => '12:45', '13:30' => '14:00'},
@@ -316,7 +341,7 @@ To open a console with the gem and sample schedule loaded:
316
341
 
317
342
  ## Copyright and license
318
343
 
319
- Copyright 2015-17 Zendesk
344
+ Copyright 2015-18 Zendesk
320
345
 
321
346
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
322
347
  this gem except in compliance with the License.
data/lib/biz.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
  require 'delegate'
3
5
  require 'forwardable'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Calculation
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Calculation
3
5
  class Active
@@ -8,16 +10,25 @@ module Biz
8
10
  end
9
11
 
10
12
  def result
11
- schedule.intervals.any? { |interval| interval.contains?(time) } \
12
- && schedule.breaks.none? { |brake| brake.contains?(time) } \
13
- && schedule.holidays.none? { |holiday| holiday.contains?(time) }
13
+ return in_hours? && active? if schedule.shifts.none?
14
+
15
+ schedule.periods.after(time).first.contains?(time)
14
16
  end
15
17
 
16
- protected
18
+ private
17
19
 
18
20
  attr_reader :schedule,
19
21
  :time
20
22
 
23
+ def in_hours?
24
+ schedule.intervals.any? { |interval| interval.contains?(time) }
25
+ end
26
+
27
+ def active?
28
+ schedule.holidays.none? { |holiday| holiday.contains?(time) } &&
29
+ schedule.breaks.none? { |brake| brake.contains?(time) }
30
+ end
31
+
21
32
  end
22
33
  end
23
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Calculation
3
5
  class DurationWithin < SimpleDelegator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Calculation
3
5
  class ForDuration
@@ -86,16 +88,14 @@ module Biz
86
88
  @schedule = schedule
87
89
  @scalar = Integer(scalar)
88
90
 
89
- fail ArgumentError, 'negative scalar' if @scalar < 0
91
+ fail ArgumentError, 'negative scalar' if @scalar.negative?
90
92
  end
91
93
 
92
- protected
94
+ private
93
95
 
94
96
  attr_reader :schedule,
95
97
  :scalar
96
98
 
97
- private
98
-
99
99
  def unit
100
100
  self.class.unit
101
101
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Calculation
3
5
  class OnBreak
@@ -11,7 +13,7 @@ module Biz
11
13
  schedule.breaks.any? { |brake| brake.contains?(time) }
12
14
  end
13
15
 
14
- protected
16
+ private
15
17
 
16
18
  attr_reader :schedule,
17
19
  :time
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Calculation
3
5
  class OnHoliday
@@ -11,7 +13,7 @@ module Biz
11
13
  schedule.holidays.any? { |holiday| holiday.contains?(time) }
12
14
  end
13
15
 
14
- protected
16
+ private
15
17
 
16
18
  attr_reader :schedule,
17
19
  :time
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Configuration
3
5
 
@@ -21,11 +23,25 @@ module Biz
21
23
  end
22
24
  end
23
25
 
26
+ def shifts
27
+ @shifts ||= begin
28
+ raw
29
+ .shifts
30
+ .flat_map { |date, hours|
31
+ hours.map { |timestamps| date_period(date, timestamps) }
32
+ }
33
+ .sort
34
+ .freeze
35
+ end
36
+ end
37
+
24
38
  def breaks
25
39
  @breaks ||= begin
26
40
  raw
27
41
  .breaks
28
- .flat_map { |date, hours| break_periods(date, hours) }
42
+ .flat_map { |date, hours|
43
+ hours.map { |timestamps| date_period(date, timestamps) }
44
+ }
29
45
  .sort
30
46
  .freeze
31
47
  end
@@ -69,6 +85,7 @@ module Biz
69
85
  def to_proc
70
86
  proc do |config|
71
87
  config.hours = raw.hours
88
+ config.shifts = raw.shifts
72
89
  config.breaks = raw.breaks
73
90
  config.holidays = raw.holidays
74
91
  config.time_zone = raw.time_zone
@@ -95,13 +112,11 @@ module Biz
95
112
  }
96
113
  end
97
114
 
98
- def break_periods(date, hours)
99
- hours.map { |start_timestamp, end_timestamp|
100
- TimeSegment.new(
101
- time.on_date(date, DayTime.from_timestamp(start_timestamp)),
102
- time.on_date(date, DayTime.from_timestamp(end_timestamp))
103
- )
104
- }
115
+ def date_period(date, timestamps)
116
+ TimeSegment.new(
117
+ time.on_date(date, DayTime.from_timestamp(timestamps.first)),
118
+ time.on_date(date, DayTime.from_timestamp(timestamps.last))
119
+ )
105
120
  end
106
121
 
107
122
  def intersected_intervals(other)
@@ -121,7 +136,7 @@ module Biz
121
136
  end
122
137
  end
123
138
 
124
- Raw = Struct.new(:hours, :breaks, :holidays, :time_zone) do
139
+ Raw = Struct.new(:hours, :shifts, :breaks, :holidays, :time_zone) do
125
140
  module Default
126
141
  HOURS = {
127
142
  mon: {'09:00' => '17:00'},
@@ -131,15 +146,17 @@ module Biz
131
146
  fri: {'09:00' => '17:00'}
132
147
  }.freeze
133
148
 
149
+ SHIFTS = [].freeze
134
150
  BREAKS = [].freeze
135
151
  HOLIDAYS = [].freeze
136
- TIME_ZONE = 'Etc/UTC'.freeze
152
+ TIME_ZONE = 'Etc/UTC'
137
153
  end
138
154
 
139
155
  def initialize(*)
140
156
  super
141
157
 
142
158
  self.hours ||= Default::HOURS
159
+ self.shifts ||= Default::SHIFTS
143
160
  self.breaks ||= Default::BREAKS
144
161
  self.holidays ||= Default::HOLIDAYS
145
162
  self.time_zone ||= Default::TIME_ZONE
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module CoreExt
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module CoreExt
3
5
  module Date
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module CoreExt
3
5
  module Integer
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module CoreExt
3
5
  module Time
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Date
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Dates < SimpleDelegator
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class DayOfWeek
3
5
 
@@ -51,6 +53,8 @@ module Biz
51
53
  wday == other_wday
52
54
  end
53
55
 
56
+ private
57
+
54
58
  def <=>(other)
55
59
  return unless other.is_a?(self.class)
56
60
 
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class DayTime
3
5
 
4
6
  VALID_SECONDS = (0..Time.day_seconds).freeze
5
7
 
6
8
  module Timestamp
7
- FORMAT = '%02d:%02d'.freeze
9
+ FORMAT = '%02d:%02d'
8
10
  PATTERN = /\A(?<hour>\d{2}):(?<minute>\d{2})(:?(?<second>\d{2}))?\Z/
9
11
  end
10
12
 
@@ -80,18 +82,18 @@ module Biz
80
82
  format(Timestamp::FORMAT, hour, minute)
81
83
  end
82
84
 
83
- def <=>(other)
84
- return unless other.is_a?(self.class)
85
-
86
- day_second <=> other.day_second
87
- end
88
-
89
85
  private
90
86
 
91
87
  def valid_second?
92
88
  VALID_SECONDS.cover?(day_second)
93
89
  end
94
90
 
91
+ def <=>(other)
92
+ return unless other.is_a?(self.class)
93
+
94
+ day_second <=> other.day_second
95
+ end
96
+
95
97
  MIDNIGHT = from_hour(0)
96
98
  ENDNIGHT = from_hour(24)
97
99
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Duration
3
5
 
@@ -50,22 +52,24 @@ module Biz
50
52
  end
51
53
 
52
54
  def positive?
53
- seconds > 0
55
+ seconds.positive?
54
56
  end
55
57
 
56
58
  def abs
57
59
  self.class.new(seconds.abs)
58
60
  end
59
61
 
62
+ protected
63
+
64
+ attr_reader :seconds
65
+
66
+ private
67
+
60
68
  def <=>(other)
61
69
  return unless other.is_a?(self.class)
62
70
 
63
71
  seconds <=> other.seconds
64
72
  end
65
73
 
66
- protected
67
-
68
- attr_reader :seconds
69
-
70
74
  end
71
75
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Error < StandardError
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Holiday
3
5
 
@@ -13,7 +15,7 @@ module Biz
13
15
  delegate contains?: :to_time_segment
14
16
 
15
17
  def to_time_segment
16
- @time_segment ||= begin
18
+ @to_time_segment ||= begin
17
19
  TimeSegment.new(
18
20
  Time.new(time_zone).on_date(date, DayTime.midnight),
19
21
  Time.new(time_zone).on_date(date, DayTime.endnight)
@@ -21,12 +23,6 @@ module Biz
21
23
  end
22
24
  end
23
25
 
24
- def <=>(other)
25
- return unless other.is_a?(self.class)
26
-
27
- [date, time_zone] <=> [other.date, other.time_zone]
28
- end
29
-
30
26
  protected
31
27
 
32
28
  attr_reader :date,
@@ -38,5 +34,13 @@ module Biz
38
34
  date
39
35
  end
40
36
 
37
+ private
38
+
39
+ def <=>(other)
40
+ return unless other.is_a?(self.class)
41
+
42
+ [date, time_zone] <=> [other.date, other.time_zone]
43
+ end
44
+
41
45
  end
42
46
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Interval
3
5
 
@@ -54,6 +56,8 @@ module Biz
54
56
  self.class.new(lower_bound, [lower_bound, upper_bound].max, time_zone)
55
57
  end
56
58
 
59
+ private
60
+
57
61
  def <=>(other)
58
62
  return unless other.is_a?(self.class)
59
63
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Periods
3
5
  end
@@ -7,4 +9,5 @@ require 'biz/periods/abstract'
7
9
 
8
10
  require 'biz/periods/after'
9
11
  require 'biz/periods/before'
12
+ require 'biz/periods/linear'
10
13
  require 'biz/periods/proxy'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Periods
3
5
  class Abstract < SimpleDelegator
@@ -13,17 +15,17 @@ module Biz
13
15
  Timeline::Proxy.new(self)
14
16
  end
15
17
 
16
- protected
18
+ private
17
19
 
18
20
  attr_reader :schedule,
19
- :origin
20
-
21
- private
21
+ :origin,
22
+ :boundary,
23
+ :intervals,
24
+ :shifts
22
25
 
23
26
  def periods
24
- weeks
27
+ Linear.new(week_periods, shifts, selector)
25
28
  .lazy
26
- .flat_map { |week| business_periods(week) }
27
29
  .select { |period| relevant?(period) }
28
30
  .map { |period| period & boundary }
29
31
  .flat_map { |period| active_periods(period) }
@@ -31,20 +33,26 @@ module Biz
31
33
  .reject(&:empty?)
32
34
  end
33
35
 
34
- def business_periods(week)
35
- intervals.lazy.map { |interval| interval.to_time_segment(week) }
36
+ def week_periods
37
+ weeks
38
+ .lazy
39
+ .flat_map { |week|
40
+ intervals.map { |interval| interval.to_time_segment(week) }
41
+ }
36
42
  end
37
43
 
38
44
  def active_periods(period)
39
- schedule.breaks.reduce([period]) { |periods, break_period|
40
- periods.flat_map { |active_period| active_period / break_period }
41
- }
45
+ schedule
46
+ .breaks
47
+ .reduce([period]) { |periods, break_period|
48
+ periods.flat_map { |active_period| active_period / break_period }
49
+ }
42
50
  end
43
51
 
44
52
  def on_holiday?(period)
45
- schedule.holidays.any? { |holiday|
46
- holiday.contains?(period.start_time)
47
- }
53
+ schedule
54
+ .holidays
55
+ .any? { |holiday| holiday.contains?(period.start_time) }
48
56
  end
49
57
 
50
58
  end
@@ -1,13 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Periods
3
5
  class After < Abstract
4
6
 
7
+ def initialize(schedule, origin)
8
+ @boundary = TimeSegment.after(origin)
9
+ @intervals = schedule.intervals
10
+ @shifts = schedule.shifts
11
+
12
+ super
13
+ end
14
+
5
15
  def timeline
6
16
  super.forward
7
17
  end
8
18
 
9
19
  private
10
20
 
21
+ def selector
22
+ :min_by
23
+ end
24
+
11
25
  def weeks
12
26
  Range.new(
13
27
  Week.since_epoch(schedule.in_zone.local(origin)),
@@ -19,14 +33,6 @@ module Biz
19
33
  origin < period.end_time
20
34
  end
21
35
 
22
- def boundary
23
- @boundary ||= TimeSegment.after(origin)
24
- end
25
-
26
- def intervals
27
- @intervals ||= schedule.intervals
28
- end
29
-
30
36
  end
31
37
  end
32
38
  end
@@ -1,13 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Periods
3
5
  class Before < Abstract
4
6
 
7
+ def initialize(schedule, origin)
8
+ @boundary = TimeSegment.before(origin)
9
+ @intervals = schedule.intervals.reverse
10
+ @shifts = schedule.shifts.reverse
11
+
12
+ super
13
+ end
14
+
5
15
  def timeline
6
16
  super.backward
7
17
  end
8
18
 
9
19
  private
10
20
 
21
+ def selector
22
+ :max_by
23
+ end
24
+
11
25
  def weeks
12
26
  Week
13
27
  .since_epoch(schedule.in_zone.local(origin))
@@ -22,14 +36,6 @@ module Biz
22
36
  super.reverse
23
37
  end
24
38
 
25
- def boundary
26
- @boundary ||= TimeSegment.before(origin)
27
- end
28
-
29
- def intervals
30
- @intervals ||= schedule.intervals.reverse
31
- end
32
-
33
39
  end
34
40
  end
35
41
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Biz
4
+ module Periods
5
+ class Linear < SimpleDelegator
6
+
7
+ def initialize(periods, shifts, selector)
8
+ @periods = periods.to_enum
9
+ @shifts = shifts.to_enum
10
+ @selector = selector
11
+ @sequences = [@periods, @shifts]
12
+
13
+ super(linear_periods)
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :periods,
19
+ :shifts,
20
+ :selector,
21
+ :sequences
22
+
23
+ def linear_periods
24
+ Enumerator.new do |yielder|
25
+ loop do
26
+ periods.next and next if periods.peek.date == shifts.peek.date
27
+
28
+ yielder << begin
29
+ sequences
30
+ .public_send(selector) { |sequence| sequence.peek.date }
31
+ .next
32
+ end
33
+ end
34
+
35
+ loop do yielder << periods.next end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Periods
3
5
  class Proxy
@@ -22,7 +24,7 @@ module Biz
22
24
  .until(schedule.in_zone.on_date(date, DayTime.endnight))
23
25
  end
24
26
 
25
- protected
27
+ private
26
28
 
27
29
  attr_reader :schedule
28
30
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Schedule
3
5
 
@@ -9,6 +11,7 @@ module Biz
9
11
 
10
12
  delegate %i[
11
13
  intervals
14
+ shifts
12
15
  breaks
13
16
  holidays
14
17
  time_zone
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Time
3
5
 
@@ -94,7 +96,7 @@ module Biz
94
96
  on_date(week.start_date + week_time.wday, week_time.day_time)
95
97
  end
96
98
 
97
- protected
99
+ private
98
100
 
99
101
  attr_reader :time_zone
100
102
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class TimeSegment
3
5
 
@@ -14,10 +16,12 @@ module Biz
14
16
  def initialize(start_time, end_time)
15
17
  @start_time = start_time
16
18
  @end_time = end_time
19
+ @date = start_time.to_date
17
20
  end
18
21
 
19
22
  attr_reader :start_time,
20
- :end_time
23
+ :end_time,
24
+ :date
21
25
 
22
26
  def duration
23
27
  Duration.new(end_time - start_time)
@@ -49,6 +53,8 @@ module Biz
49
53
  ].reject(&:empty?).map { |potential| self & potential }
50
54
  end
51
55
 
56
+ private
57
+
52
58
  def <=>(other)
53
59
  return unless other.is_a?(self.class)
54
60
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Timeline
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Timeline
3
5
  class Abstract
@@ -34,7 +36,7 @@ module Biz
34
36
  end
35
37
  end
36
38
 
37
- protected
39
+ private
38
40
 
39
41
  attr_reader :periods
40
42
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Timeline
3
5
  class Backward < Abstract
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Timeline
3
5
  class Forward < Abstract
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module Timeline
3
5
  class Proxy
@@ -14,7 +16,7 @@ module Biz
14
16
  Backward.new(periods)
15
17
  end
16
18
 
17
- protected
19
+ private
18
20
 
19
21
  attr_reader :periods
20
22
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Validation
3
5
 
@@ -15,7 +17,7 @@ module Biz
15
17
  self
16
18
  end
17
19
 
18
- protected
20
+ private
19
21
 
20
22
  attr_reader :raw
21
23
 
@@ -30,7 +32,7 @@ module Biz
30
32
  fail Error::Configuration, message unless condition.call(raw)
31
33
  end
32
34
 
33
- protected
35
+ private
34
36
 
35
37
  attr_reader :message,
36
38
  :condition
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
- VERSION = '1.7.0'.freeze
4
+ VERSION = '1.8.0'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  class Week
3
5
 
@@ -41,15 +43,17 @@ module Biz
41
43
  self.class.new(week + other.week)
42
44
  end
43
45
 
46
+ protected
47
+
48
+ attr_reader :week
49
+
50
+ private
51
+
44
52
  def <=>(other)
45
53
  return unless other.is_a?(self.class)
46
54
 
47
55
  week <=> other.week
48
56
  end
49
57
 
50
- protected
51
-
52
- attr_reader :week
53
-
54
58
  end
55
59
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module WeekTime
3
5
  class << self
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module WeekTime
3
5
  class Abstract
@@ -33,16 +35,18 @@ module Biz
33
35
  timestamp
34
36
  ] => :day_time
35
37
 
38
+ protected
39
+
40
+ attr_reader :week_minute
41
+
42
+ private
43
+
36
44
  def <=>(other)
37
45
  return unless other.is_a?(WeekTime::Abstract)
38
46
 
39
47
  week_minute <=> other.week_minute
40
48
  end
41
49
 
42
- protected
43
-
44
- attr_reader :week_minute
45
-
46
50
  end
47
51
  end
48
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module WeekTime
3
5
  class End < Abstract
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Biz
2
4
  module WeekTime
3
5
  class Start < Abstract
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.7.0
4
+ version: 1.8.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: 2017-06-14 00:00:00.000000000 Z
12
+ date: 2018-09-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: clavius
@@ -72,6 +72,7 @@ files:
72
72
  - lib/biz/periods/abstract.rb
73
73
  - lib/biz/periods/after.rb
74
74
  - lib/biz/periods/before.rb
75
+ - lib/biz/periods/linear.rb
75
76
  - lib/biz/periods/proxy.rb
76
77
  - lib/biz/schedule.rb
77
78
  - lib/biz/time.rb
@@ -100,7 +101,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
101
  requirements:
101
102
  - - ">="
102
103
  - !ruby/object:Gem::Version
103
- version: '2.2'
104
+ version: '2.3'
104
105
  required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  requirements:
106
107
  - - ">="
@@ -108,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
109
  version: '0'
109
110
  requirements: []
110
111
  rubyforge_project:
111
- rubygems_version: 2.6.8
112
+ rubygems_version: 2.7.6
112
113
  signing_key:
113
114
  specification_version: 4
114
115
  summary: Business hours calculations