working_hours 1.1.1 → 1.3.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
- SHA1:
3
- metadata.gz: fdc5713f20da1316a29bf18573095ac9e92ccb6c
4
- data.tar.gz: bf54ee89ba3f855fcdc0d791965cf23fd5c29d3c
2
+ SHA256:
3
+ metadata.gz: 4bdb747e39a652368403adb9ad97f3776a8d8ab28f841d70912bcab6141118ea
4
+ data.tar.gz: 74e1447b590b829f95e52fe3c5bac18e7a690dcc8b21683402982f5810b94d8c
5
5
  SHA512:
6
- metadata.gz: 99cd033ae02a2c9c25fbced88e1c265a891210c7a232e12bb32191fd3c9bcea652cf3f35bd6c50c3215277b190d3af56e73fbde20cff1bb6cdfb24e2a205e08f
7
- data.tar.gz: 50f2ca92979404a58af287abd33dd7af125ad9366a43160804bbbf09ce2b7648d474c5bf598e583fef4c7394e28965c31a91cc26a3ebba35d71e12cba0cdff95
6
+ metadata.gz: ee953af27c8d6622d8089feec9cd5b966613baff8239c11e488668ad07a48023efbd98d28599f9723c913b7e295ced2ac2b4cb5ef70711c4950ed8afea2aa107
7
+ data.tar.gz: 5b632e842d20ff83173c83a3e34c4bf166237f3689ba130db2cfd3031e027078f1efa1acbddac588198d78b07f3775c94e88f6d83fba328a9400629e92fab107
data/.gitignore CHANGED
@@ -3,6 +3,8 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .ruby-gemset
7
+ .ruby-version
6
8
  Gemfile.lock
7
9
  InstalledFiles
8
10
  _yardoc
@@ -1,20 +1,28 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0
4
- - 2.1.6
5
- - 2.2.2
6
- - ruby-head
7
- - jruby-1.7.19
8
- - jruby-head
9
- env: JRUBY_OPTS=--2.0
3
+ - 2.4.9
4
+ - 2.5.7
5
+ - 2.6.5
6
+ - 2.7.0
7
+ - 3.0.0
8
+ - jruby-9.2.14.0
10
9
  gemfile:
11
- - gemfiles/Gemfile.activesupport-3.2.x
12
- - gemfiles/Gemfile.activesupport-4.0.x
13
- - gemfiles/Gemfile.activesupport-4.1.x
14
- - gemfiles/Gemfile.activesupport-4.2.x
15
- - gemfiles/Gemfile.activesupport-edge
16
- matrix:
17
- allow_failures:
18
- - gemfile: gemfiles/Gemfile.activesupport-edge
10
+ - gemfiles/Gemfile.activesupport-4.x
11
+ - gemfiles/Gemfile.activesupport-5.x
12
+ - gemfiles/Gemfile.activesupport-6.x
13
+ jobs:
14
+ exclude:
15
+ - rvm: 2.4.9
16
+ gemfile: gemfiles/Gemfile.activesupport-6.x
17
+ - rvm: 2.7.0
18
+ gemfile: gemfiles/Gemfile.activesupport-4.x
19
+ - rvm: 3.0.0
20
+ gemfile: gemfiles/Gemfile.activesupport-4.x
21
+ include:
19
22
  - rvm: ruby-head
23
+ gemfile: gemfiles/Gemfile.activesupport-edge
20
24
  - rvm: jruby-head
25
+ gemfile: gemfiles/Gemfile.activesupport-edge
26
+ allow_failures:
27
+ - gemfile: gemfiles/Gemfile.activesupport-edge
28
+ fast_finish: true
@@ -1,6 +1,33 @@
1
1
  # Unreleased
2
2
 
3
- [Compare master with v1.1.1](https://github.com/intrepidd/working_hours/compare/v1.1.1...master)
3
+ [Compare master with v1.3.0](https://github.com/intrepidd/working_hours/compare/v1.3.0...master)
4
+
5
+ # v1.3.0
6
+ * Improve supports for fractional seconds in input times by only rounding results at the end - [#42](https://github.com/Intrepidd/working_hours/issues/42) [#43](https://github.com/Intrepidd/working_hours/pull/43)
7
+ * Increase code safety by always initializing an empty hash for each day of the week in the precompiled config (inspired by [#35](https://github.com/Intrepidd/working_hours/pull/35)
8
+
9
+ # v1.2.0
10
+ * Drop support for ruby 2.0, 2.1, 2.2 and 2.3
11
+ * Drop support for jruby 1.7 and 9.0
12
+ * Drop support for ActiveSupport 3.x
13
+ * Add support for jruby 9.2
14
+ * Add support for ruby 2.5, 2.6 and 2.7
15
+ * Add support for ActiveSupport 5.x and 6.x
16
+ * Fix day computations when origin is a holiday or a non worked day - [#39](https://github.com/Intrepidd/working_hours/pull/39)
17
+
18
+
19
+ # v1.1.4
20
+ * Fix thread safety - [#36](https://github.com/Intrepidd/working_hours/pull/36)
21
+
22
+ # v1.1.3
23
+ * Fixed warnings with Ruby 2.4.0+ - [#32](https://github.com/Intrepidd/working_hours/pull/32)
24
+ * Fix install bug with jruby 1.7.20
25
+
26
+ # v1.1.2
27
+ * Fixed an issue of float imprecision causing infinite loop - [#27](https://github.com/Intrepidd/working_hours/pull/27)
28
+ * Added #next_working_time and #advance_to_closing_time - [#23](https://github.com/Intrepidd/working_hours/pull/23)
29
+
30
+ _06/12/2015_
4
31
 
5
32
  # v1.1.1
6
33
  * Fix infinite loop happening when rewinding seconds and crossing through midgnight
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # WorkingHours
2
2
 
3
- [![Build Status](https://travis-ci.org/Intrepidd/working_hours.svg?branch=master)](https://travis-ci.org/Intrepidd/working_hours)
3
+ [![Build Status](https://travis-ci.com/Intrepidd/working_hours.svg?branch=master)](https://travis-ci.com/Intrepidd/working_hours)
4
4
 
5
5
  A modern ruby gem allowing to do time calculation with working hours.
6
6
 
7
7
  Compatible and tested with:
8
- - Ruby `2.0`, `2.1`, JRuby `1.7` ( with ruby 2 syntax support enabled )
9
- - ActiveSupport `3.2.x`, `4.0.x` and `4.1.x`
8
+ - Ruby `2.4`, `2.5`, `2.6`, `2.7`, `3.0`, JRuby `9.2`
9
+ - ActiveSupport `4.x`, `5.x`, `6.x`
10
10
 
11
11
  ## Installation
12
12
 
@@ -57,6 +57,16 @@ Time.utc(2014, 8, 4, 7, 16).in_working_hours? # => false
57
57
  # Advance to next working time
58
58
  WorkingHours.advance_to_working_time(Time.utc(2014, 8, 4, 7, 16)) # => Mon, 04 Aug 2014 09:00:00 UTC +00:00
59
59
 
60
+ # Advance to next closing time
61
+ WorkingHours.advance_to_closing_time(Time.utc(2014, 8, 4, 7, 16)) # => Mon, 04 Aug 2014 17:00:00 UTC +00:00
62
+ WorkingHours.advance_to_closing_time(Time.utc(2014, 8, 4, 10, 16)) # => Mon, 04 Aug 2014 17:00:00 UTC +00:00
63
+ WorkingHours.advance_to_closing_time(Time.utc(2014, 8, 4, 18, 16)) # => Tue, 05 Aug 2014 17:00:00 UTC +00:00
64
+
65
+ # Next working time
66
+ sunday = Time.utc(2014, 8, 3)
67
+ monday = WorkingHours.next_working_time(sunday) # => Mon, 04 Aug 2014 09:00:00 UTC +00:00
68
+ tuesday = WorkingHours.next_working_time(monday) # => Tue, 05 Aug 2014 09:00:00 UTC +00:00
69
+
60
70
  # Return to previous working time
61
71
  WorkingHours.return_to_working_time(Time.utc(2014, 8, 4, 7, 16)) # => Fri, 01 Aug 2014 17:00:00 UTC +00:00
62
72
  ```
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem 'activesupport', '~> 3.2'
5
+ gem 'activesupport', '~> 5.2'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem 'activesupport', '~> 4.0'
5
+ gem 'activesupport', '~> 6.0'
@@ -1,3 +1,3 @@
1
1
  require "working_hours/module"
2
- require "working_hours/core_ext/fixnum"
3
- require "working_hours/core_ext/date_and_time"
2
+ require "working_hours/core_ext/integer"
3
+ require "working_hours/core_ext/date_and_time"
@@ -5,8 +5,12 @@ module WorkingHours
5
5
  module Computation
6
6
 
7
7
  def add_days origin, days, config: nil
8
+ return origin if days.zero?
9
+
8
10
  config ||= wh_config
9
11
  time = in_config_zone(origin, config: config)
12
+ time += (days <=> 0).day until working_day?(time, config: config)
13
+
10
14
  while days > 0
11
15
  time += 1.day
12
16
  days -= 1 if working_day?(time, config: config)
@@ -30,7 +34,7 @@ module WorkingHours
30
34
 
31
35
  def add_seconds origin, seconds, config: nil
32
36
  config ||= wh_config
33
- time = in_config_zone(origin, config: config).round
37
+ time = in_config_zone(origin, config: config)
34
38
  while seconds > 0
35
39
  # roll to next business period
36
40
  time = advance_to_working_time(time, config: config)
@@ -39,7 +43,7 @@ module WorkingHours
39
43
  config[:working_hours][time.wday].each do |from, to|
40
44
  if time_in_day >= from and time_in_day < to
41
45
  # take all we can
42
- take = [to - time_in_day, seconds].min.round
46
+ take = [to - time_in_day, seconds].min
43
47
  # advance time
44
48
  time += take
45
49
  # decrease seconds
@@ -55,7 +59,7 @@ module WorkingHours
55
59
  config[:working_hours][time.wday].reverse_each do |from, to|
56
60
  if time_in_day > from and time_in_day <= to
57
61
  # take all we can
58
- take = [time_in_day - from, -seconds].min.round
62
+ take = [time_in_day - from, -seconds].min
59
63
  # advance time
60
64
  time -= take
61
65
  # decrease seconds
@@ -68,7 +72,7 @@ module WorkingHours
68
72
 
69
73
  def advance_to_working_time time, config: nil
70
74
  config ||= wh_config
71
- time = in_config_zone(time, config: config).round
75
+ time = in_config_zone(time, config: config)
72
76
  loop do
73
77
  # skip holidays and weekends
74
78
  while not working_day?(time, config: config)
@@ -76,7 +80,7 @@ module WorkingHours
76
80
  end
77
81
  # find first working range after time
78
82
  time_in_day = time.seconds_since_midnight
79
- (config[:working_hours][time.wday] || {}).each do |from, to|
83
+ config[:working_hours][time.wday].each do |from, to|
80
84
  return time if time_in_day >= from and time_in_day < to
81
85
  return time + (from - time_in_day) if from >= time_in_day
82
86
  end
@@ -85,6 +89,31 @@ module WorkingHours
85
89
  end
86
90
  end
87
91
 
92
+ def advance_to_closing_time time, config: nil
93
+ config ||= wh_config
94
+ time = in_config_zone(time, config: config)
95
+ loop do
96
+ # skip holidays and weekends
97
+ while not working_day?(time, config: config)
98
+ time = (time + 1.day).beginning_of_day
99
+ end
100
+ # find next working range after time
101
+ time_in_day = time.seconds_since_midnight
102
+ time = time.beginning_of_day
103
+ config[:working_hours][time.wday].each do |from, to|
104
+ return time + to if time_in_day >= from and time_in_day < to
105
+ return time + to if from >= time_in_day
106
+ end
107
+ # if none is found, go to next day and loop
108
+ time = time + 1.day
109
+ end
110
+ end
111
+
112
+ def next_working_time(time, config: nil)
113
+ time = advance_to_closing_time(time, config: config) if in_working_hours?(time, config: config)
114
+ advance_to_working_time(time, config: config)
115
+ end
116
+
88
117
  def return_to_working_time(time, config: nil)
89
118
  # return_to_exact_working_time may return values with a high number of milliseconds,
90
119
  # this is necessary for the end of day hack, here we return a rounded value for the
@@ -94,7 +123,7 @@ module WorkingHours
94
123
 
95
124
  def return_to_exact_working_time time, config: nil
96
125
  config ||= wh_config
97
- time = in_config_zone(time, config: config).round
126
+ time = in_config_zone(time, config: config)
98
127
  loop do
99
128
  # skip holidays and weekends
100
129
  while not working_day?(time, config: config)
@@ -102,7 +131,7 @@ module WorkingHours
102
131
  end
103
132
  # find last working range before time
104
133
  time_in_day = time.seconds_since_midnight
105
- (config[:working_hours][time.wday] || {}).reverse_each do |from, to|
134
+ config[:working_hours][time.wday].reverse_each do |from, to|
106
135
  # round is used to suppress miliseconds hack from `end_of_day`
107
136
  return time if time_in_day > from and time_in_day <= to
108
137
  return (time - (time_in_day - to)) if to <= time_in_day
@@ -151,7 +180,7 @@ module WorkingHours
151
180
  -working_time_between(to, from, config: config)
152
181
  else
153
182
  from = advance_to_working_time(in_config_zone(from, config: config))
154
- to = in_config_zone(to, config: config).round
183
+ to = in_config_zone(to, config: config)
155
184
  distance = 0
156
185
  while from < to
157
186
  # look at working ranges
@@ -44,9 +44,8 @@ module WorkingHours
44
44
  validate_working_hours! config[:working_hours]
45
45
  validate_holidays! config[:holidays]
46
46
  validate_time_zone! config[:time_zone]
47
- compiled = {working_hours: []}
47
+ compiled = { working_hours: Array.new(7) { Hash.new } }
48
48
  working_hours.each do |day, hours|
49
- compiled[:working_hours][DAYS_OF_WEEK.index(day)] = {}
50
49
  hours.each do |start, finish|
51
50
  compiled[:working_hours][DAYS_OF_WEEK.index(day)][compile_time(start)] = compile_time(finish)
52
51
  end
@@ -89,7 +88,7 @@ module WorkingHours
89
88
  private
90
89
 
91
90
  def config
92
- Thread.current[:working_hours] ||= global_config
91
+ Thread.current[:working_hours] ||= global_config.dup
93
92
  end
94
93
 
95
94
  def global_config
@@ -2,7 +2,7 @@ require "working_hours/duration_proxy"
2
2
 
3
3
  module WorkingHours
4
4
  module CoreExt
5
- module Fixnum
5
+ module Integer
6
6
 
7
7
  def working
8
8
  WorkingHours::DurationProxy.new(self)
@@ -12,4 +12,4 @@ module WorkingHours
12
12
  end
13
13
  end
14
14
 
15
- Fixnum.send(:include, WorkingHours::CoreExt::Fixnum)
15
+ Integer.send(:include, WorkingHours::CoreExt::Integer)
@@ -1,3 +1,3 @@
1
1
  module WorkingHours
2
- VERSION = "1.1.1"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -32,12 +32,26 @@ describe WorkingHours::Computation do
32
32
  expect(add_days(time, 1)).to eq(Date.new(2014, 4, 9)) # Wednesday
33
33
  end
34
34
 
35
+ it 'skips non worked days when origin is not worked' do
36
+ time = Date.new(2014, 4, 8) # Tuesday
37
+ WorkingHours::Config.working_hours = {mon: {'09:00' => '17:00'}, wed: {'09:00' => '17:00'}, thu: {'09:00' => '17:00'}, sun: {'09:00' => '17:00'}}
38
+ expect(add_days(time, 1)).to eq(Date.new(2014, 4, 10)) # Thursday
39
+ expect(add_days(time, -1)).to eq(Date.new(2014, 4, 6)) # Sunday
40
+ end
41
+
35
42
  it 'skips holidays' do
36
43
  time = Date.new(2014, 4, 7) # Monday
37
44
  WorkingHours::Config.holidays = [Date.new(2014, 4, 8)] # Tuesday
38
45
  expect(add_days(time, 1)).to eq(Date.new(2014, 4, 9)) # Wednesday
39
46
  end
40
47
 
48
+ it 'skips holidays when origin is holiday' do
49
+ time = Date.new(2014, 4, 9) # Wednesday
50
+ WorkingHours::Config.holidays = [time] # Wednesday
51
+ expect(add_days(time, 1)).to eq(Date.new(2014, 4, 11)) # Friday
52
+ expect(add_days(time, -1)).to eq(Date.new(2014, 4, 7)) # Monday
53
+ end
54
+
41
55
  it 'skips holidays and non worked days' do
42
56
  time = Date.new(2014, 4, 7) # Monday
43
57
  WorkingHours::Config.holidays = [Date.new(2014, 4, 9)] # Wednesday
@@ -45,6 +59,12 @@ describe WorkingHours::Computation do
45
59
  expect(add_days(time, 3)).to eq(Date.new(2014, 4, 21))
46
60
  end
47
61
 
62
+ it 'returns the original value when adding 0 days' do
63
+ time = Date.new(2014, 4, 7)
64
+ WorkingHours::Config.holidays = [time]
65
+ expect(add_days(time, 0)).to eq(time)
66
+ end
67
+
48
68
  it 'accepts time given from any time zone' do
49
69
  time = Time.utc(1991, 11, 14, 21, 0, 0) # Thursday 21 pm UTC
50
70
  WorkingHours::Config.time_zone = 'Tokyo' # But we are at tokyo, so it's already Friday 6 am
@@ -109,6 +129,13 @@ describe WorkingHours::Computation do
109
129
  time = Time.utc(2014, 4, 8, 0, 0, 30) # Tuesday
110
130
  expect(add_seconds(time, -60)).to eq(Time.utc(2014, 4, 7, 23, 59, 00))
111
131
  end
132
+
133
+ it 'honors miliseconds in the base time and increment (but return rounded result)' do
134
+ # Rounding the base time or increments before the end would yield a wrong result
135
+ time = Time.utc(1991, 11, 15, 16, 59, 42.25) # +250ms
136
+ expect(add_seconds(time, 120.4)).to eq(Time.utc(1991, 11, 18, 9, 1, 43))
137
+ end
138
+
112
139
  end
113
140
 
114
141
  describe '#advance_to_working_time' do
@@ -148,6 +175,173 @@ describe WorkingHours::Computation do
148
175
  end
149
176
  end
150
177
 
178
+ describe '#advance_to_closing_time' do
179
+
180
+ it 'jumps non-working day' do
181
+ WorkingHours::Config.holidays = [Date.new(2014, 5, 1)]
182
+ holiday = Time.utc(2014, 5, 1, 12, 0)
183
+ friday_closing = Time.utc(2014, 5, 2, 17, 0)
184
+ sunday = Time.utc(2014, 6, 1, 12, 0)
185
+ monday_closing = Time.utc(2014, 6, 2, 17, 0)
186
+ expect(advance_to_closing_time(holiday)).to eq(friday_closing)
187
+ expect(advance_to_closing_time(sunday)).to eq(monday_closing)
188
+ end
189
+
190
+ it 'moves to the closing time during working hours' do
191
+ in_open_time = Time.utc(2014, 4, 7, 12, 0)
192
+ closing_time = Time.utc(2014, 4, 7, 17, 0)
193
+ expect(advance_to_closing_time(in_open_time)).to eq(closing_time)
194
+ end
195
+
196
+ it 'jumps outside working hours' do
197
+ monday_before_opening = Time.utc(2014, 4, 7, 8, 59)
198
+ monday_closing = Time.utc(2014, 4, 7, 17, 0)
199
+ tuesday_closing = Time.utc(2014, 4, 8, 17, 0)
200
+ expect(advance_to_closing_time(monday_before_opening)).to eq(monday_closing)
201
+ expect(advance_to_closing_time(monday_closing)).to eq(tuesday_closing)
202
+ end
203
+
204
+ context 'moving between timespans' do
205
+ before do
206
+ WorkingHours::Config.working_hours = {
207
+ mon: {'07:00' => '12:00', '13:00' => '18:00'},
208
+ tue: {'09:00' => '17:00'},
209
+ wed: {'09:00' => '17:00'},
210
+ thu: {'09:00' => '17:00'},
211
+ fri: {'09:00' => '17:00'}
212
+ }
213
+ end
214
+
215
+ let(:monday_morning) { Time.utc(2014, 4, 7, 10) }
216
+ let(:morning_closing) { Time.utc(2014, 4, 7, 12) }
217
+ let(:afternoon_closing) { Time.utc(2014, 4, 7, 18) }
218
+ let(:monday_break) { Time.utc(2014, 4, 7, 12) }
219
+ let(:tuesday_closing) { Time.utc(2014, 4, 8, 17) }
220
+
221
+ it 'moves from morning to end of morning slot' do
222
+ expect(advance_to_closing_time(monday_morning)).to eq(morning_closing)
223
+ end
224
+
225
+ it 'moves from break time to end of afternoon slot' do
226
+ expect(advance_to_closing_time(monday_break)).to eq(afternoon_closing)
227
+ end
228
+
229
+ it 'moves from afternoon closing slot to next day' do
230
+ expect(advance_to_closing_time(afternoon_closing)).to eq(tuesday_closing)
231
+ end
232
+ end
233
+
234
+ context 'supporting midnight' do
235
+ before do
236
+ WorkingHours::Config.working_hours = {
237
+ mon: {'00:00' => '24:00'},
238
+ tue: {'09:00' => '17:00'}
239
+ }
240
+ end
241
+
242
+ let(:monday_morning) { Time.utc(2014, 4, 7, 0) }
243
+ let(:monday_closing) { Time.utc(2014, 4, 7) + 86399.999999 }
244
+ let(:tuesday_closing) { Time.utc(2014, 4, 8, 17) }
245
+ let(:sunday) { Time.utc(2014, 4, 6, 17) }
246
+
247
+ it 'moves from morning to midnight' do
248
+ expect(advance_to_closing_time(monday_morning)).to eq(monday_closing)
249
+ end
250
+
251
+ it 'moves from midnight to end of next slot' do
252
+ expect(advance_to_closing_time(monday_closing)).to eq(tuesday_closing)
253
+ end
254
+
255
+ it 'moves over midnight' do
256
+ expect(advance_to_closing_time(sunday)).to eq(monday_closing)
257
+ end
258
+ end
259
+
260
+ it 'works with any input timezone (converts to config)' do
261
+ # Monday 0 am (-09:00) is 9am in UTC time, working time!
262
+ monday_morning = Time.new(2014, 4, 7, 0, 0, 0 , "-09:00")
263
+ monday_closing = Time.new(2014, 4, 7, 12, 0, 0 , "-05:00")
264
+ monday_night = Time.new(2014, 4, 7, 22, 0, 0, "+02:00")
265
+ tuesday_evening = Time.utc(2014, 4, 8, 17)
266
+ expect(advance_to_closing_time(monday_morning)).to eq(monday_closing)
267
+ expect(advance_to_closing_time(monday_night)).to eq(tuesday_evening)
268
+ end
269
+
270
+ it 'returns time in config zone' do
271
+ WorkingHours::Config.time_zone = 'Tokyo'
272
+ expect(advance_to_closing_time(Time.new(2014, 4, 7, 0, 0, 0)).zone).to eq('JST')
273
+ end
274
+ end
275
+
276
+ describe '#next_working_time' do
277
+
278
+ it 'jumps non-working day' do
279
+ WorkingHours::Config.holidays = [Date.new(2014, 5, 1)]
280
+ holiday = Time.utc(2014, 5, 1, 12, 0)
281
+ sunday = Time.utc(2014, 6, 1, 12, 0)
282
+ expect(next_working_time(holiday)).to eq(Time.utc(2014, 5, 2, 9, 0))
283
+ expect(next_working_time(sunday)).to eq(Time.utc(2014, 6, 2, 9, 0))
284
+ end
285
+
286
+ it 'moves to the following timespan during working hours' do
287
+ monday = Time.utc(2014, 4, 7, 12, 0)
288
+ tuesday = Time.utc(2014, 4, 8, 9, 0)
289
+ expect(next_working_time(monday)).to eq(tuesday)
290
+ end
291
+
292
+ it 'jumps outside working hours' do
293
+ monday_before_opening = Time.utc(2014, 4, 7, 8, 59)
294
+ monday_opening = Time.utc(2014, 4, 7, 9, 0)
295
+ monday_closing = Time.utc(2014, 4, 7, 17, 0)
296
+ tuesday_opening = Time.utc(2014, 4, 8, 9, 0)
297
+ expect(next_working_time(monday_before_opening)).to eq(monday_opening)
298
+ expect(next_working_time(monday_closing)).to eq(tuesday_opening)
299
+ end
300
+
301
+ context 'move between timespans' do
302
+ before do
303
+ WorkingHours::Config.working_hours = {
304
+ mon: {'07:00' => '12:00', '13:00' => '18:00'},
305
+ tue: {'09:00' => '17:00'},
306
+ wed: {'09:00' => '17:00'},
307
+ thu: {'09:00' => '17:00'},
308
+ fri: {'09:00' => '17:00'}
309
+ }
310
+ end
311
+
312
+ let(:monday_morning) { Time.utc(2014, 4, 7, 10) }
313
+ let(:monday_afternoon) { Time.utc(2014, 4, 7, 13) }
314
+ let(:monday_break) { Time.utc(2014, 4, 7, 12) }
315
+ let(:tuesday_morning) { Time.utc(2014, 4, 8, 9) }
316
+
317
+ it 'moves from morning to afternoon slot' do
318
+ expect(next_working_time(monday_morning)).to eq(monday_afternoon)
319
+ end
320
+
321
+ it 'moves from break time to afternoon slot' do
322
+ expect(next_working_time(monday_break)).to eq(monday_afternoon)
323
+ end
324
+
325
+ it 'moves from afternoon slot to next day' do
326
+ expect(next_working_time(monday_afternoon)).to eq(tuesday_morning)
327
+ end
328
+ end
329
+
330
+ it 'works with any input timezone (converts to config)' do
331
+ # Monday 0 am (-09:00) is 9am in UTC time, working time!
332
+ monday_morning = Time.new(2014, 4, 7, 0, 0, 0 , "-09:00")
333
+ monday_night = Time.new(2014, 4, 7, 22, 0, 0, "+02:00")
334
+ tuesday_morning = Time.utc(2014, 4, 8, 9)
335
+ expect(next_working_time(monday_morning)).to eq(tuesday_morning)
336
+ expect(next_working_time(monday_night)).to eq(tuesday_morning)
337
+ end
338
+
339
+ it 'returns time in config zone' do
340
+ WorkingHours::Config.time_zone = 'Tokyo'
341
+ expect(next_working_time(Time.new(2014, 4, 7, 0, 0, 0)).zone).to eq('JST')
342
+ end
343
+ end
344
+
151
345
  describe '#return_to_working_time' do
152
346
  it 'jumps non-working day' do
153
347
  WorkingHours::Config.holidays = [Date.new(2014, 5, 1)]
@@ -349,5 +543,20 @@ describe WorkingHours::Computation do
349
543
  Time.new(2014, 4, 7, 15, 0, 0, "-04:00"), # Monday 7pm in UTC
350
544
  )).to eq(7.hours)
351
545
  end
546
+
547
+ # generates two times with +0ms, +250ms, +500ms, +750ms and +1s
548
+ # then for each combination compare the result with a ruby diff
549
+ context 'with precise miliseconds timings' do
550
+ reference = Time.utc(2014, 4, 7, 10)
551
+ 0.step(1.0, 0.25) do |offset1|
552
+ 0.step(1.0, 0.25) do |offset2|
553
+ from = reference + offset1
554
+ to = reference + offset2
555
+ it "returns expected value (#{(to - from).round}) for #{offset1} — #{offset2} interval" do
556
+ expect(working_time_between(from, to)).to eq((to - from).round)
557
+ end
558
+ end
559
+ end
560
+ end
352
561
  end
353
562
  end
@@ -5,25 +5,58 @@ describe WorkingHours::Config do
5
5
  describe '.working_hours' do
6
6
 
7
7
  let(:config) { WorkingHours::Config.working_hours }
8
+ let(:config2) { { :mon => { '08:00' => '14:00' } } }
9
+ let(:config3) { { :tue => { '10:00' => '16:00' } } }
8
10
 
9
11
  it 'has a default config' do
10
12
  expect(config).to be_kind_of(Hash)
11
13
  end
12
14
 
13
15
  it 'is thread safe' do
16
+ expect(WorkingHours::Config.working_hours).to eq(config)
17
+
18
+ thread = Thread.new do
19
+ WorkingHours::Config.working_hours = config2
20
+ expect(WorkingHours::Config.working_hours).to eq(config2)
21
+ Thread.stop
22
+ expect(WorkingHours::Config.working_hours).to eq(config2)
23
+ end
24
+
25
+ expect {
26
+ sleep 0.1 # let the thread begin its execution
27
+ }.not_to change { WorkingHours::Config.working_hours }.from(config)
28
+
14
29
  expect {
15
- Thread.new {
16
- WorkingHours::Config.working_hours = {:mon => {'08:00' => '14:00'}}
17
- }.join
18
- }.not_to change { WorkingHours::Config.working_hours }
30
+ WorkingHours::Config.working_hours = config3
31
+ }.to change { WorkingHours::Config.working_hours }.from(config).to(config3)
32
+
33
+ expect {
34
+ thread.run
35
+ thread.join
36
+ }.not_to change { WorkingHours::Config.working_hours }.from(config3)
19
37
  end
20
38
 
21
39
  it 'is fiber safe' do
40
+ expect(WorkingHours::Config.working_hours).to eq(config)
41
+
42
+ fiber = Fiber.new do
43
+ WorkingHours::Config.working_hours = config2
44
+ expect(WorkingHours::Config.working_hours).to eq(config2)
45
+ Fiber.yield
46
+ expect(WorkingHours::Config.working_hours).to eq(config2)
47
+ end
48
+
49
+ expect {
50
+ fiber.resume
51
+ }.not_to change { WorkingHours::Config.working_hours }.from(config)
52
+
53
+ expect {
54
+ WorkingHours::Config.working_hours = config3
55
+ }.to change { WorkingHours::Config.working_hours }.from(config).to(config3)
56
+
22
57
  expect {
23
- Fiber.new {
24
- WorkingHours::Config.working_hours = {:mon => {'08:00' => '14:00'}}
25
- }.resume
26
- }.not_to change { WorkingHours::Config.working_hours }
58
+ fiber.resume
59
+ }.not_to change { WorkingHours::Config.working_hours }.from(config3)
27
60
  end
28
61
 
29
62
  it 'is initialized from last known global config' do
@@ -233,16 +266,23 @@ describe WorkingHours::Config do
233
266
 
234
267
  it 'computes an optimized version' do
235
268
  expect(subject).to eq({
236
- :working_hours => [nil, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}],
269
+ :working_hours => [{}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {}],
237
270
  :holidays => Set.new([]),
238
271
  :time_zone => ActiveSupport::TimeZone['UTC']
239
272
  })
240
273
  end
241
274
 
275
+ it 'includes default values for each days so computation does not fail' do
276
+ WorkingHours::Config.working_hours = {:mon => {'08:00' => '14:00'}}
277
+ expect(subject[:working_hours]).to eq([{}, {28800=>50400}, {}, {}, {}, {}, {}])
278
+ expect(WorkingHours.working_time_between(Time.utc(2014, 4, 14, 0), Time.utc(2014, 4, 21, 0))).to eq(3600*6)
279
+ expect(WorkingHours.add_seconds(Time.utc(2014, 4, 14, 0), 3600*7)).to eq(Time.utc(2014, 4, 21, 9))
280
+ end
281
+
242
282
  it 'supports seconds' do
243
283
  WorkingHours::Config.working_hours = {:mon => {'20:32:59' => '22:59:59'}}
244
284
  expect(subject).to eq({
245
- :working_hours => [nil, {73979 => 82799}],
285
+ :working_hours => [{}, {73979 => 82799}, {}, {}, {}, {}, {}],
246
286
  :holidays => Set.new([]),
247
287
  :time_zone => ActiveSupport::TimeZone['UTC']
248
288
  })
@@ -251,7 +291,7 @@ describe WorkingHours::Config do
251
291
  it 'supports 24:00 (converts to 23:59:59.999999)' do
252
292
  WorkingHours::Config.working_hours = {:mon => {'20:00' => '24:00'}}
253
293
  expect(subject).to eq({
254
- :working_hours => [nil, {72000 => 86399.999999}],
294
+ :working_hours => [{}, {72000 => 86399.999999}, {}, {}, {}, {}, {}],
255
295
  :holidays => Set.new([]),
256
296
  :time_zone => ActiveSupport::TimeZone['UTC']
257
297
  })
@@ -263,9 +303,9 @@ describe WorkingHours::Config do
263
303
  }.to change {
264
304
  WorkingHours::Config.precompiled[:working_hours]
265
305
  }.from(
266
- [nil, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}]
306
+ [{}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {32400=>61200}, {}]
267
307
  ).to(
268
- [nil, {28800=>50400}]
308
+ [{}, {28800=>50400}, {}, {}, {}, {}, {}]
269
309
  )
270
310
  end
271
311
 
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe WorkingHours::CoreExt::Fixnum do
3
+ describe WorkingHours::CoreExt::Integer do
4
4
 
5
5
  describe '#working' do
6
6
  it 'returns a DurationProxy' do
@@ -62,6 +62,10 @@ describe WorkingHours::Duration do
62
62
  WorkingHours::Config.time_zone = 'Tokyo'
63
63
  expect(7.working.days.from_now.zone).to eq('JST')
64
64
  end
65
+
66
+ it 'should not hang with fractional hours' do
67
+ WorkingHours::Duration.new(4.1, :hours).since(Time.utc(1991, 11, 15, 21))
68
+ end
65
69
  end
66
70
 
67
71
  describe '#until' do
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency 'activesupport', '>= 3.2'
22
22
  spec.add_dependency 'tzinfo'
23
23
 
24
- spec.add_development_dependency 'bundler', '~> 1.5'
24
+ spec.add_development_dependency 'bundler', '>= 1.5'
25
25
  spec.add_development_dependency 'rake'
26
26
  spec.add_development_dependency 'rspec', '~> 3.2'
27
27
  spec.add_development_dependency 'timecop'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: working_hours
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrien Jarthon
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-08-18 00:00:00.000000000 Z
12
+ date: 2021-01-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -43,14 +43,14 @@ dependencies:
43
43
  name: bundler
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - "~>"
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
48
  version: '1.5'
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
55
  version: '1.5'
56
56
  - !ruby/object:Gem::Dependency
@@ -111,16 +111,15 @@ files:
111
111
  - LICENSE.txt
112
112
  - README.md
113
113
  - Rakefile
114
- - gemfiles/Gemfile.activesupport-3.2.x
115
- - gemfiles/Gemfile.activesupport-4.0.x
116
- - gemfiles/Gemfile.activesupport-4.1.x
117
- - gemfiles/Gemfile.activesupport-4.2.x
118
- - gemfiles/Gemfile.activesupport-head
114
+ - gemfiles/Gemfile.activesupport-4.x
115
+ - gemfiles/Gemfile.activesupport-5.x
116
+ - gemfiles/Gemfile.activesupport-6.x
117
+ - gemfiles/Gemfile.activesupport-edge
119
118
  - lib/working_hours.rb
120
119
  - lib/working_hours/computation.rb
121
120
  - lib/working_hours/config.rb
122
121
  - lib/working_hours/core_ext/date_and_time.rb
123
- - lib/working_hours/core_ext/fixnum.rb
122
+ - lib/working_hours/core_ext/integer.rb
124
123
  - lib/working_hours/duration.rb
125
124
  - lib/working_hours/duration_proxy.rb
126
125
  - lib/working_hours/module.rb
@@ -129,7 +128,7 @@ files:
129
128
  - spec/working_hours/computation_spec.rb
130
129
  - spec/working_hours/config_spec.rb
131
130
  - spec/working_hours/core_ext/date_and_time_spec.rb
132
- - spec/working_hours/core_ext/fixnum_spec.rb
131
+ - spec/working_hours/core_ext/integer_spec.rb
133
132
  - spec/working_hours/duration_proxy_spec.rb
134
133
  - spec/working_hours/duration_spec.rb
135
134
  - spec/working_hours_spec.rb
@@ -153,8 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
152
  - !ruby/object:Gem::Version
154
153
  version: '0'
155
154
  requirements: []
156
- rubyforge_project:
157
- rubygems_version: 2.2.3
155
+ rubygems_version: 3.0.3
158
156
  signing_key:
159
157
  specification_version: 4
160
158
  summary: time calculation with working hours
@@ -163,7 +161,7 @@ test_files:
163
161
  - spec/working_hours/computation_spec.rb
164
162
  - spec/working_hours/config_spec.rb
165
163
  - spec/working_hours/core_ext/date_and_time_spec.rb
166
- - spec/working_hours/core_ext/fixnum_spec.rb
164
+ - spec/working_hours/core_ext/integer_spec.rb
167
165
  - spec/working_hours/duration_proxy_spec.rb
168
166
  - spec/working_hours/duration_spec.rb
169
167
  - spec/working_hours_spec.rb
@@ -1,5 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec :path => '..'
4
-
5
- gem 'activesupport', '~> 4.1'