business_time 0.11.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/lib/business_time/business_days.rb +15 -15
- data/lib/business_time/business_hours.rb +17 -17
- data/lib/business_time/config.rb +2 -2
- data/lib/business_time/core_ext/date.rb +5 -5
- data/lib/business_time/core_ext/integer.rb +4 -4
- data/lib/business_time/time_extensions.rb +33 -26
- data/lib/business_time/version.rb +1 -1
- data/lib/business_time.rb +4 -3
- data/lib/generators/business_time/config_generator.rb +5 -5
- metadata +3 -18
- data/README.rdoc +0 -229
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3006a4d1708551ad12693457ba051e16b0b8f94439cc8cca54999519d20a5a63
|
4
|
+
data.tar.gz: 785c54ba3652f861532d1963dff4afbcb8194b78ffb7a46de3932778aafb6703
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dac78848ef4878c0891b30f3eb8842578047eb3ae559681e25f7f5dbac885643719fa28ebab37af36daa26d5278cc88f9e40c8919bfbb9b26aa8d38e625c25b2
|
7
|
+
data.tar.gz: 73529a47adeb15c99c0cc06143ee70bb5f2eeb6ee4f0da6c2cff79970082f742aeb412f4e0d201643bb60b9c024dea51c96b0db67ff4220f9a0ca51049264ca8
|
data/LICENSE
CHANGED
@@ -5,7 +5,7 @@ module BusinessTime
|
|
5
5
|
include Comparable
|
6
6
|
attr_reader :days
|
7
7
|
|
8
|
-
def initialize(days)
|
8
|
+
def initialize(days, options={})
|
9
9
|
@days = days
|
10
10
|
end
|
11
11
|
|
@@ -16,15 +16,15 @@ module BusinessTime
|
|
16
16
|
self.days <=> other.days
|
17
17
|
end
|
18
18
|
|
19
|
-
def after(time = Time.current)
|
20
|
-
non_negative_days? ? calculate_after(time, @days) : calculate_before(time, -@days)
|
19
|
+
def after(time = Time.current, options={})
|
20
|
+
non_negative_days? ? calculate_after(time, @days, options) : calculate_before(time, -@days, options)
|
21
21
|
end
|
22
22
|
|
23
23
|
alias_method :from_now, :after
|
24
24
|
alias_method :since, :after
|
25
25
|
|
26
|
-
def before(time = Time.current)
|
27
|
-
non_negative_days? ? calculate_before(time, @days) : calculate_after(time, -@days)
|
26
|
+
def before(time = Time.current, options={})
|
27
|
+
non_negative_days? ? calculate_before(time, @days, options) : calculate_after(time, -@days, options)
|
28
28
|
end
|
29
29
|
|
30
30
|
alias_method :ago, :before
|
@@ -36,35 +36,35 @@ module BusinessTime
|
|
36
36
|
@days >= 0
|
37
37
|
end
|
38
38
|
|
39
|
-
def calculate_after(time, days)
|
40
|
-
if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?
|
39
|
+
def calculate_after(time, days, options={})
|
40
|
+
if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?(options)
|
41
41
|
time = Time.beginning_of_workday(time)
|
42
42
|
end
|
43
|
-
while days > 0 || !time.workday?
|
44
|
-
days -= 1 if time.workday?
|
43
|
+
while days > 0 || !time.workday?(options)
|
44
|
+
days -= 1 if time.workday?(options)
|
45
45
|
time += 1.day
|
46
46
|
end
|
47
47
|
# If we have a Time or DateTime object, we can roll_forward to the
|
48
48
|
# beginning of the next business day
|
49
49
|
if time.is_a?(Time) || time.is_a?(DateTime)
|
50
|
-
time = Time.roll_forward(time) unless time.during_business_hours?
|
50
|
+
time = Time.roll_forward(time, options) unless time.during_business_hours?
|
51
51
|
end
|
52
52
|
time
|
53
53
|
end
|
54
54
|
|
55
|
-
def calculate_before(time, days)
|
56
|
-
if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?
|
55
|
+
def calculate_before(time, days, options={})
|
56
|
+
if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?(options)
|
57
57
|
time = Time.beginning_of_workday(time)
|
58
58
|
end
|
59
|
-
while days > 0 || !time.workday?
|
60
|
-
days -= 1 if time.workday?
|
59
|
+
while days > 0 || !time.workday?(options)
|
60
|
+
days -= 1 if time.workday?(options)
|
61
61
|
time -= 1.day
|
62
62
|
end
|
63
63
|
# If we have a Time or DateTime object, we can roll_backward to the
|
64
64
|
# beginning of the previous business day
|
65
65
|
if time.is_a?(Time) || time.is_a?(DateTime)
|
66
66
|
unless time.during_business_hours?
|
67
|
-
time = Time.beginning_of_workday(Time.roll_backward(time))
|
67
|
+
time = Time.beginning_of_workday(Time.roll_backward(time, options))
|
68
68
|
end
|
69
69
|
end
|
70
70
|
time
|
@@ -3,8 +3,8 @@ module BusinessTime
|
|
3
3
|
class BusinessHours
|
4
4
|
include Comparable
|
5
5
|
attr_reader :hours
|
6
|
-
|
7
|
-
def initialize(hours)
|
6
|
+
|
7
|
+
def initialize(hours, options={})
|
8
8
|
@hours = hours
|
9
9
|
end
|
10
10
|
|
@@ -15,21 +15,21 @@ module BusinessTime
|
|
15
15
|
self.hours <=> other.hours
|
16
16
|
end
|
17
17
|
|
18
|
-
def ago
|
19
|
-
Time.zone ? before(Time.zone.now) : before(Time.now)
|
18
|
+
def ago(options={})
|
19
|
+
Time.zone ? before(Time.zone.now, options) : before(Time.now, options)
|
20
20
|
end
|
21
21
|
|
22
|
-
def from_now
|
23
|
-
Time.zone ? after(Time.zone.now) : after(Time.now)
|
22
|
+
def from_now(options={})
|
23
|
+
Time.zone ? after(Time.zone.now, options) : after(Time.now, options)
|
24
24
|
end
|
25
25
|
|
26
|
-
def after(time)
|
27
|
-
non_negative_hours? ? calculate_after(time, @hours) : calculate_before(time, -@hours)
|
26
|
+
def after(time, options={})
|
27
|
+
non_negative_hours? ? calculate_after(time, @hours, options) : calculate_before(time, -@hours, options)
|
28
28
|
end
|
29
29
|
alias_method :since, :after
|
30
30
|
|
31
|
-
def before(time)
|
32
|
-
non_negative_hours? ? calculate_before(time, @hours) : calculate_after(time, -@hours)
|
31
|
+
def before(time, options={})
|
32
|
+
non_negative_hours? ? calculate_before(time, @hours, options) : calculate_after(time, -@hours, options)
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
@@ -38,18 +38,18 @@ module BusinessTime
|
|
38
38
|
@hours >= 0
|
39
39
|
end
|
40
40
|
|
41
|
-
def calculate_after(time, hours)
|
42
|
-
after_time = Time.roll_forward(time)
|
41
|
+
def calculate_after(time, hours, options={})
|
42
|
+
after_time = Time.roll_forward(time, options)
|
43
43
|
# Step through the hours, skipping over non-business hours
|
44
44
|
hours.times do
|
45
45
|
after_time = after_time + 1.hour
|
46
46
|
|
47
47
|
if after_time.hour == 0 && after_time.min == 0 && after_time.sec == 0
|
48
|
-
after_time = Time.roll_forward(after_time)
|
48
|
+
after_time = Time.roll_forward(after_time, options)
|
49
49
|
elsif (after_time > Time.end_of_workday(after_time))
|
50
50
|
# Ignore hours before opening and after closing
|
51
51
|
delta = after_time - Time.end_of_workday(after_time)
|
52
|
-
after_time = Time.roll_forward(after_time) + delta
|
52
|
+
after_time = Time.roll_forward(after_time, options) + delta
|
53
53
|
end
|
54
54
|
|
55
55
|
# Ignore weekends and holidays
|
@@ -60,20 +60,20 @@ module BusinessTime
|
|
60
60
|
after_time
|
61
61
|
end
|
62
62
|
|
63
|
-
def calculate_before(time, hours)
|
63
|
+
def calculate_before(time, hours, options={})
|
64
64
|
before_time = Time.roll_backward(time)
|
65
65
|
# Step through the hours, skipping over non-business hours
|
66
66
|
hours.times do
|
67
67
|
before_time = before_time - 1.hour
|
68
68
|
|
69
69
|
if before_time.hour == 0 && before_time.min == 0 && before_time.sec == 0
|
70
|
-
before_time = Time.roll_backward(before_time - 1.second)
|
70
|
+
before_time = Time.roll_backward(before_time - 1.second, options)
|
71
71
|
elsif (before_time <= Time.beginning_of_workday(before_time))
|
72
72
|
# Ignore hours before opening and after closing
|
73
73
|
delta = Time.beginning_of_workday(before_time) - before_time
|
74
74
|
|
75
75
|
# Due to the 23:59:59 end-of-workday exception
|
76
|
-
time_roll_backward = Time.roll_backward(before_time)
|
76
|
+
time_roll_backward = Time.roll_backward(before_time, options)
|
77
77
|
time_roll_backward += 1.second if time_roll_backward.iso8601 =~ /23:59:59/
|
78
78
|
|
79
79
|
before_time = time_roll_backward - delta
|
data/lib/business_time/config.rb
CHANGED
@@ -7,7 +7,7 @@ module BusinessTime
|
|
7
7
|
# manually, or with a yaml file and the load method.
|
8
8
|
class Config
|
9
9
|
DEFAULT_CONFIG = {
|
10
|
-
holidays:
|
10
|
+
holidays: Set.new,
|
11
11
|
beginning_of_workday: ParsedTime.parse('9:00 am'),
|
12
12
|
end_of_workday: ParsedTime.parse('5:00 pm'),
|
13
13
|
work_week: %w(mon tue wed thu fri),
|
@@ -144,7 +144,7 @@ module BusinessTime
|
|
144
144
|
wday_to_int(day_name)
|
145
145
|
end.compact
|
146
146
|
|
147
|
-
self._weekdays =
|
147
|
+
self._weekdays = days.sort.to_set
|
148
148
|
end
|
149
149
|
|
150
150
|
# loads the config data from a yaml file written as:
|
@@ -2,15 +2,15 @@
|
|
2
2
|
class Date
|
3
3
|
include BusinessTime::TimeExtensions
|
4
4
|
|
5
|
-
def business_days_until(to_date, inclusive = false)
|
6
|
-
business_dates_until(to_date, inclusive).size
|
5
|
+
def business_days_until(to_date, inclusive = false, options={})
|
6
|
+
business_dates_until(to_date, inclusive, options).size
|
7
7
|
end
|
8
8
|
|
9
|
-
def business_dates_until(to_date, inclusive = false)
|
9
|
+
def business_dates_until(to_date, inclusive = false, options={})
|
10
10
|
if inclusive
|
11
|
-
(self..to_date).select
|
11
|
+
(self..to_date).select{|this_date| this_date.workday?(options)}
|
12
12
|
else
|
13
|
-
(self...to_date).select
|
13
|
+
(self...to_date).select{|this_date| this_date.workday?(options)}
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -4,13 +4,13 @@
|
|
4
4
|
# 3.business_days.after(some_date)
|
5
5
|
# 4.business_hours.before(some_date_time)
|
6
6
|
class Integer
|
7
|
-
def business_hours
|
8
|
-
BusinessTime::BusinessHours.new(self)
|
7
|
+
def business_hours(options={})
|
8
|
+
BusinessTime::BusinessHours.new(self, options)
|
9
9
|
end
|
10
10
|
alias_method :business_hour, :business_hours
|
11
11
|
|
12
|
-
def business_days
|
13
|
-
BusinessTime::BusinessDays.new(self)
|
12
|
+
def business_days(options={})
|
13
|
+
BusinessTime::BusinessDays.new(self, options)
|
14
14
|
end
|
15
15
|
alias_method :business_day, :business_days
|
16
16
|
end
|
@@ -2,8 +2,12 @@ module BusinessTime
|
|
2
2
|
module TimeExtensions
|
3
3
|
# True if this time is on a workday (between 00:00:00 and 23:59:59), even if
|
4
4
|
# this time falls outside of normal business hours.
|
5
|
-
|
6
|
-
|
5
|
+
# holidays option allows you to pass in a different Array of holiday dates on
|
6
|
+
# each call vs the BusinessTime::Config.holidays which is always static.
|
7
|
+
def workday?(options={})
|
8
|
+
weekday? &&
|
9
|
+
!BusinessTime::Config.holidays.include?(to_date) &&
|
10
|
+
!to_array_of_dates(options[:holidays]).include?(to_date)
|
7
11
|
end
|
8
12
|
|
9
13
|
# True if this time falls on a weekday.
|
@@ -32,9 +36,9 @@ module BusinessTime
|
|
32
36
|
|
33
37
|
# True if this time is on a workday (between 00:00:00 and 23:59:59), even if
|
34
38
|
# this time falls outside of normal business hours.
|
35
|
-
def workday?(day)
|
39
|
+
def workday?(day, options={})
|
36
40
|
ActiveSupport::Deprecation.warn("`Time.workday?(time)` is deprecated. Please use `time.workday?`")
|
37
|
-
day.workday?
|
41
|
+
day.workday?(options)
|
38
42
|
end
|
39
43
|
|
40
44
|
# True if this time falls on a weekday.
|
@@ -53,9 +57,8 @@ module BusinessTime
|
|
53
57
|
|
54
58
|
# Rolls forward to the next beginning_of_workday
|
55
59
|
# when the time is outside of business hours
|
56
|
-
def roll_forward(time)
|
57
|
-
|
58
|
-
if Time.before_business_hours?(time) || !time.workday?
|
60
|
+
def roll_forward(time, options={})
|
61
|
+
if Time.before_business_hours?(time) || !time.workday?(options)
|
59
62
|
next_business_time = Time.beginning_of_workday(time)
|
60
63
|
elsif Time.after_business_hours?(time) || Time.end_of_workday(time) == time
|
61
64
|
next_business_time = Time.beginning_of_workday(time + 1.day)
|
@@ -63,7 +66,7 @@ module BusinessTime
|
|
63
66
|
next_business_time = time.clone
|
64
67
|
end
|
65
68
|
|
66
|
-
while !next_business_time.workday?
|
69
|
+
while !next_business_time.workday?(options)
|
67
70
|
next_business_time = Time.beginning_of_workday(next_business_time + 1.day)
|
68
71
|
end
|
69
72
|
|
@@ -72,8 +75,8 @@ module BusinessTime
|
|
72
75
|
|
73
76
|
# Returns the time parameter itself if it is a business day
|
74
77
|
# or else returns the next business day
|
75
|
-
def first_business_day(time)
|
76
|
-
while !time.workday?
|
78
|
+
def first_business_day(time, options={})
|
79
|
+
while !time.workday?(options)
|
77
80
|
time = time + 1.day
|
78
81
|
end
|
79
82
|
|
@@ -82,8 +85,8 @@ module BusinessTime
|
|
82
85
|
|
83
86
|
# Rolls backwards to the previous end_of_workday when the time is outside
|
84
87
|
# of business hours
|
85
|
-
def roll_backward(time)
|
86
|
-
prev_business_time = if (Time.before_business_hours?(time) || !time.workday?)
|
88
|
+
def roll_backward(time, options={})
|
89
|
+
prev_business_time = if (Time.before_business_hours?(time) || !time.workday?(options))
|
87
90
|
Time.end_of_workday(time - 1.day)
|
88
91
|
elsif Time.after_business_hours?(time)
|
89
92
|
Time.end_of_workday(time)
|
@@ -91,7 +94,7 @@ module BusinessTime
|
|
91
94
|
time.clone
|
92
95
|
end
|
93
96
|
|
94
|
-
while !prev_business_time.workday?
|
97
|
+
while !prev_business_time.workday?(options)
|
95
98
|
prev_business_time = Time.end_of_workday(prev_business_time - 1.day)
|
96
99
|
end
|
97
100
|
|
@@ -100,16 +103,16 @@ module BusinessTime
|
|
100
103
|
|
101
104
|
# Returns the time parameter itself if it is a business day
|
102
105
|
# or else returns the previous business day
|
103
|
-
def previous_business_day(time)
|
104
|
-
while !time.workday?
|
106
|
+
def previous_business_day(time, options={})
|
107
|
+
while !time.workday?(options)
|
105
108
|
time = time - 1.day
|
106
109
|
end
|
107
110
|
|
108
111
|
time
|
109
112
|
end
|
110
113
|
|
111
|
-
def work_hours_total(day)
|
112
|
-
return 0 unless day.workday?
|
114
|
+
def work_hours_total(day, options={})
|
115
|
+
return 0 unless day.workday?(options)
|
113
116
|
|
114
117
|
day = day.strftime('%a').downcase.to_sym
|
115
118
|
|
@@ -136,7 +139,7 @@ module BusinessTime
|
|
136
139
|
end
|
137
140
|
end
|
138
141
|
|
139
|
-
def business_time_until(to_time)
|
142
|
+
def business_time_until(to_time, options={})
|
140
143
|
# Make sure that we will calculate time from A to B "clockwise"
|
141
144
|
if self < to_time
|
142
145
|
time_a = self
|
@@ -149,8 +152,8 @@ module BusinessTime
|
|
149
152
|
end
|
150
153
|
|
151
154
|
# Align both times to the closest business hours
|
152
|
-
time_a = Time::roll_forward(time_a)
|
153
|
-
time_b = Time::roll_forward(time_b)
|
155
|
+
time_a = Time::roll_forward(time_a, options)
|
156
|
+
time_b = Time::roll_forward(time_b, options)
|
154
157
|
|
155
158
|
if time_a.to_date == time_b.to_date
|
156
159
|
time_b - time_a
|
@@ -166,16 +169,16 @@ module BusinessTime
|
|
166
169
|
end * direction
|
167
170
|
end
|
168
171
|
|
169
|
-
def during_business_hours?
|
170
|
-
self.workday? && self.to_i.between?(Time.beginning_of_workday(self).to_i, Time.end_of_workday(self).to_i)
|
172
|
+
def during_business_hours?(options={})
|
173
|
+
self.workday?(options) && self.to_i.between?(Time.beginning_of_workday(self).to_i, Time.end_of_workday(self).to_i)
|
171
174
|
end
|
172
175
|
|
173
|
-
def consecutive_workdays
|
174
|
-
workday? ? consecutive_days { |date| date.workday? } : []
|
176
|
+
def consecutive_workdays(options={})
|
177
|
+
workday?(options) ? consecutive_days { |date| date.workday?(options) } : []
|
175
178
|
end
|
176
179
|
|
177
|
-
def consecutive_non_working_days
|
178
|
-
!workday? ? consecutive_days { |date| !date.workday? } : []
|
180
|
+
def consecutive_non_working_days(options={})
|
181
|
+
!workday?(options) ? consecutive_days { |date| !date.workday?(options) } : []
|
179
182
|
end
|
180
183
|
|
181
184
|
private
|
@@ -194,5 +197,9 @@ module BusinessTime
|
|
194
197
|
end
|
195
198
|
(days << self).sort
|
196
199
|
end
|
200
|
+
|
201
|
+
def to_array_of_dates(values)
|
202
|
+
Array.wrap(values).map(&:to_date)
|
203
|
+
end
|
197
204
|
end
|
198
205
|
end
|
data/lib/business_time.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
require '
|
2
|
-
require 'active_support'
|
3
|
-
require 'active_support/time'
|
1
|
+
require 'set'
|
4
2
|
require 'time'
|
5
3
|
require 'yaml'
|
6
4
|
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/time'
|
7
|
+
|
7
8
|
require 'business_time/parsed_time'
|
8
9
|
require 'business_time/version'
|
9
10
|
require 'business_time/config'
|
@@ -1,21 +1,21 @@
|
|
1
1
|
module BusinessTime
|
2
2
|
module Generators
|
3
3
|
class ConfigGenerator < Rails::Generators::Base # :nodoc:
|
4
|
-
|
4
|
+
|
5
5
|
def self.gem_root
|
6
|
-
File.expand_path("
|
6
|
+
File.expand_path("../../..", __dir__)
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.source_root
|
10
10
|
# Use the templates from the 2.3.x generator
|
11
11
|
File.join(gem_root, 'rails_generators', 'business_time_config', 'templates')
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def generate
|
15
15
|
template 'business_time.rb', File.join('config', 'initializers', 'business_time.rb')
|
16
16
|
template 'business_time.yml', File.join('config', 'business_time.yml')
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
end
|
20
20
|
end
|
21
|
-
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: business_time
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- bokmann
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: sorted_set
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rake
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -116,7 +102,6 @@ extensions: []
|
|
116
102
|
extra_rdoc_files: []
|
117
103
|
files:
|
118
104
|
- LICENSE
|
119
|
-
- README.rdoc
|
120
105
|
- lib/business_time.rb
|
121
106
|
- lib/business_time/business_days.rb
|
122
107
|
- lib/business_time/business_hours.rb
|
@@ -151,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
136
|
- !ruby/object:Gem::Version
|
152
137
|
version: '0'
|
153
138
|
requirements: []
|
154
|
-
rubygems_version: 3.
|
139
|
+
rubygems_version: 3.3.12
|
155
140
|
signing_key:
|
156
141
|
specification_version: 4
|
157
142
|
summary: Support for doing time math in business hours and days
|
data/README.rdoc
DELETED
@@ -1,229 +0,0 @@
|
|
1
|
-
= business_time
|
2
|
-
|
3
|
-
{<img src="https://github.com/bokmann/business_time/workflows/CI/badge.svg" />}[https://github.com/bokmann/business_time/actions?query=workflow%3ACI]
|
4
|
-
|
5
|
-
ActiveSupport gives us some great helpers so we can do things like:
|
6
|
-
|
7
|
-
5.days.ago
|
8
|
-
|
9
|
-
and
|
10
|
-
|
11
|
-
8.hours.from_now
|
12
|
-
|
13
|
-
as well as helpers to do that from any provided date or time.
|
14
|
-
|
15
|
-
I needed this, but taking into account business hours/days and holidays.
|
16
|
-
|
17
|
-
== Usage
|
18
|
-
=== install the gem
|
19
|
-
|
20
|
-
gem install business_time
|
21
|
-
|
22
|
-
=== open up your console
|
23
|
-
|
24
|
-
# if in irb, add these lines:
|
25
|
-
|
26
|
-
require 'business_time'
|
27
|
-
|
28
|
-
# try these examples, using the current time:
|
29
|
-
|
30
|
-
1.business_hour.from_now
|
31
|
-
4.business_hours.from_now
|
32
|
-
8.business_hours.from_now
|
33
|
-
|
34
|
-
1.business_hour.ago
|
35
|
-
4.business_hours.ago
|
36
|
-
8.business_hours.ago
|
37
|
-
|
38
|
-
1.business_day.from_now
|
39
|
-
4.business_days.from_now
|
40
|
-
8.business_days.from_now
|
41
|
-
|
42
|
-
1.business_day.ago
|
43
|
-
4.business_days.ago
|
44
|
-
8.business_days.ago
|
45
|
-
|
46
|
-
Date.today.workday?
|
47
|
-
Date.parse("2015-12-09").workday?
|
48
|
-
Date.parse("2015-12-12").workday?
|
49
|
-
|
50
|
-
And we can do it from any Date or Time object.
|
51
|
-
my_birthday = Date.parse("August 4th, 1969")
|
52
|
-
8.business_days.after(my_birthday)
|
53
|
-
8.business_days.before(my_birthday)
|
54
|
-
|
55
|
-
my_birthday = Time.parse("August 4th, 1969, 8:32 am")
|
56
|
-
8.business_days.after(my_birthday)
|
57
|
-
8.business_days.before(my_birthday)
|
58
|
-
|
59
|
-
|
60
|
-
We can adjust the start and end time of our business hours
|
61
|
-
BusinessTime::Config.beginning_of_workday = "8:30 am"
|
62
|
-
BusinessTime::Config.end_of_workday = "5:30 pm"
|
63
|
-
|
64
|
-
Or we can temporarily override the configured values
|
65
|
-
BusinessTime::Config.with(beginning_of_workday: "8 am", end_of_workday: "6 pm") do
|
66
|
-
1.business_hour.from_now
|
67
|
-
end
|
68
|
-
|
69
|
-
and we can add holidays that don't count as business days
|
70
|
-
July 5 in 2010 is a monday that the U.S. takes off because our independence day falls on that Sunday.
|
71
|
-
three_day_weekend = Date.parse("July 5th, 2010")
|
72
|
-
BusinessTime::Config.holidays << three_day_weekend
|
73
|
-
friday_afternoon = Time.parse("July 2nd, 2010, 4:50 pm")
|
74
|
-
tuesday_morning = 1.business_hour.after(friday_afternoon)
|
75
|
-
|
76
|
-
plus, we can change the work week:
|
77
|
-
# July 9th in 2010 is a Friday.
|
78
|
-
BusinessTime::Config.work_week = [:sun, :mon, :tue, :wed, :thu]
|
79
|
-
thursday_afternoon = Time.parse("July 8th, 2010, 4:50 pm")
|
80
|
-
sunday_morning = 1.business_hour.after(thursday_afternoon)
|
81
|
-
|
82
|
-
As alternative we also can change the business hours for each work day:
|
83
|
-
BusinessTime::Config.work_hours = {
|
84
|
-
:mon=>["9:00","17:00"],
|
85
|
-
:fri=>["9:00","17:00"],
|
86
|
-
:sat=>["10:00","15:00"]
|
87
|
-
}
|
88
|
-
friday = Time.parse("December 24, 2010 15:00")
|
89
|
-
monday = Time.parse("December 27, 2010 11:00")
|
90
|
-
working_hours = friday.business_time_until(monday) # 9.hours
|
91
|
-
|
92
|
-
You can also calculate business duration between two dates
|
93
|
-
friday = Date.parse("December 24, 2010")
|
94
|
-
monday = Date.parse("December 27, 2010")
|
95
|
-
friday.business_days_until(monday) #=> 1
|
96
|
-
|
97
|
-
Or you can calculate business duration between two Time objects
|
98
|
-
ticket_reported = Time.parse("February 3, 2012, 10:40 am")
|
99
|
-
ticket_resolved = Time.parse("February 4, 2012, 10:50 am")
|
100
|
-
ticket_reported.business_time_until(ticket_resolved) #=> 8.hours + 10.minutes
|
101
|
-
|
102
|
-
You can also determine if a given time is within business hours
|
103
|
-
Time.parse("February 3, 2012, 10:00 am").during_business_hours?
|
104
|
-
|
105
|
-
Note that counterintuitively, durations might not be quite what you expect when involving weekends.
|
106
|
-
Consider the following example:
|
107
|
-
ticket_reported = Time.parse("February 3, 2012, 10:40 am")
|
108
|
-
ticket_resolved = Time.parse("February 4, 2012, 10:40 am")
|
109
|
-
ticket_reported.business_time_until(ticket_resolved) # will equal 6 hours and 20 minutes!
|
110
|
-
|
111
|
-
Why does this happen? Feb 4 2012 is a Saturday. That time will roll over to
|
112
|
-
Monday, Feb 6th 2012, 9:00am. The business time between 10:40am friday and 9am monday is
|
113
|
-
6 hours and 20 minutes. From a quick inspection of the code, it looks like it should be 8 hours.
|
114
|
-
|
115
|
-
Or you can calculate business dates between two dates
|
116
|
-
monday = Date.parse("December 20, 2010")
|
117
|
-
wednesday = Date.parse("December 22, 2010")
|
118
|
-
monday.business_dates_until(wednesday) #=> [Mon, 20 Dec 2010, Tue, 21 Dec 2010]
|
119
|
-
|
120
|
-
You can get the first workday after a time or return itself if it is a workday
|
121
|
-
saturday = Time.parse("Sat Aug 9, 18:00:00, 2014")
|
122
|
-
monday = Time.parse("Mon Aug 11, 18:00:00, 2014")
|
123
|
-
Time.first_business_day(saturday) #=> "Mon Aug 11, 18:00:00, 2014"
|
124
|
-
Time.first_business_day(monday) #=> "Mon Aug 11, 18:00:00, 2014"
|
125
|
-
|
126
|
-
# similar to Time#first_business_day Time#previous_business_day only cares about
|
127
|
-
# workdays:
|
128
|
-
saturday = Time.parse("Sat Aug 9, 18:00:00, 2014")
|
129
|
-
monday = Time.parse("Mon Aug 11, 18:00:00, 2014")
|
130
|
-
Time.previous_business_day(saturday) #=> "Fri Aug 8, 18:00:00, 2014"
|
131
|
-
Time.previous_business_day(monday) #=> "Mon Aug 11, 18:00:00, 2014"
|
132
|
-
== Rails generator
|
133
|
-
|
134
|
-
rails generate business_time:config
|
135
|
-
|
136
|
-
The generator will add a ./config/business_time.yml and a ./config/initializers/business_time.rb
|
137
|
-
file that will cause the start of business day, the end of business day, and your holidays to be loaded from the yaml file.
|
138
|
-
|
139
|
-
You might want to programatically load your holidays from a database table,
|
140
|
-
but you will want to pay attention to how the initializer works -
|
141
|
-
you will want to make sure that the initializer sets stuff up appropriately so
|
142
|
-
rails instances on mongrels or passenger will have the appropriate data as they come up and down.
|
143
|
-
|
144
|
-
== Timezone support
|
145
|
-
This gem strives to be timezone-agnostic.
|
146
|
-
Due to some complications in the handling of timezones in the built in Time class,
|
147
|
-
and some complexities (bugs?) in the timeWithZone class, this was harder than expected... but here's the idea:
|
148
|
-
|
149
|
-
* When you configure the gem with something like 9:00am as the start time,
|
150
|
-
this is agnostic of time zone.
|
151
|
-
* When you are dealing with a Time or TimeWithZone class, the timezone is
|
152
|
-
preserved and the beginning and end of times for the business day are
|
153
|
-
referenced in that time zone.
|
154
|
-
|
155
|
-
This can lead to some weird looking effects if, say, you are in the Eastern time zone but doing everything in UTC times...
|
156
|
-
Your business day will appear to start and end at 9:00 and 5:00 UTC.
|
157
|
-
If this seems perplexing to you, I can almost guarantee you are in over your head with timezones in other ways too,
|
158
|
-
this is just the first place you encountered it.
|
159
|
-
Timezone relative date handling gets more and more complicated every time you look at it and takes a long time before it starts to seem simple again.
|
160
|
-
|
161
|
-
== Integration with the Holidays gem
|
162
|
-
|
163
|
-
Chris Wise wrote up a great article[http://murmurinfo.wordpress.com/2012/01/11/handling-holidays-and-business-hours/]
|
164
|
-
on using the business_time gem with the holidays[https://github.com/alexdunae/holidays] gem. It boils down to this:
|
165
|
-
|
166
|
-
Holidays.between(Date.civil(2013, 1, 1), 2.years.from_now, :ca_on, :observed).map do |holiday|
|
167
|
-
BusinessTime::Config.holidays << holiday[:date]
|
168
|
-
# Implement long weekends if they apply to the region, eg:
|
169
|
-
# BusinessTime::Config.holidays << holiday[:date].next_week if !holiday[:date].weekday?
|
170
|
-
end
|
171
|
-
|
172
|
-
== Contributors
|
173
|
-
* David Bock http://github.com/bokmann
|
174
|
-
* Ryan McGeary http://github.com/rmm5t
|
175
|
-
* Enrico Bianco http://github.com/enricob
|
176
|
-
* Arild Shirazi http://github.com/ashirazi
|
177
|
-
* Piotr Jakubowski http://github.com/piotrj
|
178
|
-
* Glenn Vanderburg http://github.com/glv
|
179
|
-
* Michael Grosser http://github.com/grosser
|
180
|
-
* Michael Curtis http://github.com/mcurtis
|
181
|
-
* Brian Ewins http://github.com/bazzargh
|
182
|
-
|
183
|
-
(Special thanks for Arild on the complexities of dealing with TimeWithZone)
|
184
|
-
|
185
|
-
== Note on Patches/Pull Requests
|
186
|
-
|
187
|
-
* Fork the project.
|
188
|
-
* Make your feature addition or bug fix.
|
189
|
-
* Add tests for it. This is important so I don't break it in a
|
190
|
-
future version unintentionally.
|
191
|
-
* Commit, do not mess with rakefile, version, or history.
|
192
|
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
193
|
-
* Send me a pull request. Bonus points for topic branches.
|
194
|
-
|
195
|
-
== TODO
|
196
|
-
|
197
|
-
* Arild has pointed out that there may be some logical inconsistencies
|
198
|
-
regarding the beginning_of_workday and end_of workday times not actually
|
199
|
-
being considered inside of the workday. I'd like to make sure that they
|
200
|
-
work as if the beginning_of_workday is included and the end_of_workday is
|
201
|
-
not included, just like the '...' range operator in Ruby.
|
202
|
-
|
203
|
-
== NOT TODO
|
204
|
-
|
205
|
-
* I spent way too much time in my previous java-programmer life building frameworks that worshipped complexity,
|
206
|
-
always trying to give the developer-user ultimate flexibility at the expense of the 'surface area' of the api.
|
207
|
-
Never again - I will sooner limit functionality to 80% so that something stays usable and let people fork.
|
208
|
-
* While there have been requests to add 'business minutes' and even 'business seconds' to this gem, I won't
|
209
|
-
entertain a pull request with such things. If you find it useful, great. Most users won't, and they don't
|
210
|
-
need the baggage.
|
211
|
-
|
212
|
-
|
213
|
-
== A note on stability and change
|
214
|
-
|
215
|
-
Sometimes people ask me why this gem doesn't release more often. My opinions on that are best discussed in person in a friendly discussion, but I'll attempt some of that here.
|
216
|
-
|
217
|
-
First, a big part of the reason is that the projects I do use this gem on are happy with it's current functionality. It is 'suitable for the purpose' for which I released it, and as such, maintenance I do on this gem is a gift to the community.
|
218
|
-
|
219
|
-
Second, out of the ~1.3 million downloads (according to rubygems.org), the number of real 'issues' with this gem have been minimal. Most of the issues that are opened are really people with slightly different requirements than I have regarding whether 'off hours' work counts as the previous or the next business day, a disagreement on the semantics of days vs. hours, etc. I take care to try to explain these choices in the open issues, but to my mind, they aren't true issues if it's just a difference of opinion. Even so, I'll gladly accept pull requests that resolve this difference of opinion as a configuration option... just don't expect me to do your job for you. I've already given you 90% of what you need.
|
220
|
-
|
221
|
-
Third, a business time gem is, well, relevant to businesses. Many businesses don't move quickly. My government clients move even more slowly. Stability is favored in these environments.
|
222
|
-
|
223
|
-
Fourth, new features can wait. To the person that adds them they can be mission critical, but with modern packaging processes, they can use their version without waiting for their changes to be included in the upstream version. Their changes don't break your code.
|
224
|
-
|
225
|
-
I'm proud of the work in this gem; the stability is a big part of that. This gem has lived longer than many others that have attempted to do the same thing. I expect it to be here chugging away when Ruby has become the next COBOL.
|
226
|
-
|
227
|
-
== Copyright
|
228
|
-
|
229
|
-
Copyright (c) 2010-2021 bokmann. See LICENSE for details.
|