business_time 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/business_time.rb +3 -1
- data/lib/business_time/business_hours.rb +15 -25
- data/lib/business_time/config.rb +4 -0
- data/lib/business_time/core_ext/active_support/time_with_zone.rb +3 -0
- data/lib/business_time/core_ext/time.rb +1 -137
- data/lib/business_time/time_extensions.rb +145 -0
- data/lib/business_time/version.rb +1 -1
- metadata +6 -4
data/lib/business_time.rb
CHANGED
@@ -7,6 +7,8 @@ require 'business_time/config'
|
|
7
7
|
require 'business_time/business_hours'
|
8
8
|
require 'business_time/business_days'
|
9
9
|
require 'business_time/core_ext/date'
|
10
|
-
require 'business_time/core_ext/time'
|
11
10
|
require 'business_time/core_ext/fixnum'
|
12
11
|
|
12
|
+
require 'business_time/time_extensions'
|
13
|
+
require 'business_time/core_ext/time'
|
14
|
+
require 'business_time/core_ext/active_support/time_with_zone'
|
@@ -19,13 +19,10 @@ module BusinessTime
|
|
19
19
|
@hours.times do
|
20
20
|
after_time = after_time + 1.hour
|
21
21
|
|
22
|
-
# Roll back a second if it is midnight so that we check end_of_workday for the right day
|
23
22
|
if after_time.hour == 0 && after_time.min == 0 && after_time.sec == 0
|
24
|
-
after_time = after_time
|
25
|
-
|
26
|
-
|
27
|
-
# Ignore hours before opening and after closing
|
28
|
-
if (after_time >= Time.end_of_workday(after_time))
|
23
|
+
after_time = Time.roll_forward(after_time)
|
24
|
+
elsif (after_time > Time.end_of_workday(after_time))
|
25
|
+
# Ignore hours before opening and after closing
|
29
26
|
delta = after_time - Time.end_of_workday(after_time)
|
30
27
|
after_time = Time.roll_forward(after_time) + delta
|
31
28
|
end
|
@@ -40,14 +37,22 @@ module BusinessTime
|
|
40
37
|
alias_method :since, :after
|
41
38
|
|
42
39
|
def before(time)
|
43
|
-
before_time = Time.
|
40
|
+
before_time = Time.roll_backward(time)
|
44
41
|
# Step through the hours, skipping over non-business hours
|
45
42
|
@hours.times do
|
46
43
|
before_time = before_time - 1.hour
|
47
44
|
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
if before_time.hour == 0 && before_time.min == 0 && before_time.sec == 0
|
46
|
+
before_time = Time.roll_backward(before_time - 1.second)
|
47
|
+
elsif (before_time <= Time.beginning_of_workday(before_time))
|
48
|
+
# Ignore hours before opening and after closing
|
49
|
+
delta = Time.beginning_of_workday(before_time) - before_time
|
50
|
+
|
51
|
+
# Due to the 23:59:59 end-of-workday exception
|
52
|
+
time_roll_backward = Time.roll_backward(before_time)
|
53
|
+
time_roll_backward += 1.second if time_roll_backward.to_s =~ /23:59:59/
|
54
|
+
|
55
|
+
before_time = time_roll_backward - delta
|
51
56
|
end
|
52
57
|
|
53
58
|
# Ignore weekends and holidays
|
@@ -57,20 +62,5 @@ module BusinessTime
|
|
57
62
|
end
|
58
63
|
before_time
|
59
64
|
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def off_hours
|
64
|
-
return @gap if @gap
|
65
|
-
if Time.zone
|
66
|
-
gap_end = Time.zone.parse(BusinessTime::Config.beginning_of_workday)
|
67
|
-
gap_begin = (Time.zone.parse(BusinessTime::Config.end_of_workday)-1.day)
|
68
|
-
else
|
69
|
-
gap_end = Time.parse(BusinessTime::Config.beginning_of_workday)
|
70
|
-
gap_begin = (Time.parse(BusinessTime::Config.end_of_workday) - 1.day)
|
71
|
-
end
|
72
|
-
@gap = gap_end - gap_begin
|
73
|
-
end
|
74
65
|
end
|
75
|
-
|
76
66
|
end
|
data/lib/business_time/config.rb
CHANGED
@@ -12,6 +12,7 @@ module BusinessTime
|
|
12
12
|
end_of_workday: '5:00 pm',
|
13
13
|
work_week: %w(mon tue wed thu fri),
|
14
14
|
work_hours: {},
|
15
|
+
work_hours_total: {},
|
15
16
|
_weekdays: nil,
|
16
17
|
}
|
17
18
|
|
@@ -66,6 +67,9 @@ module BusinessTime
|
|
66
67
|
# {:mon => ["9:00","17:00"],:tue => ["9:00","17:00"].....}
|
67
68
|
threadsafe_cattr_accessor :work_hours
|
68
69
|
|
70
|
+
# total work hours for a day. Never set, always calculated.
|
71
|
+
threadsafe_cattr_accessor :work_hours_total
|
72
|
+
|
69
73
|
threadsafe_cattr_accessor :_weekdays # internal
|
70
74
|
|
71
75
|
class << self
|
@@ -1,140 +1,4 @@
|
|
1
1
|
# Add workday and weekday concepts to the Time class
|
2
2
|
class Time
|
3
|
-
|
4
|
-
# Gives the time at the end of the workday, assuming that this time falls on a
|
5
|
-
# workday.
|
6
|
-
# Note: It pretends that this day is a workday whether or not it really is a
|
7
|
-
# workday.
|
8
|
-
def end_of_workday(day)
|
9
|
-
end_of_workday = Time.parse(BusinessTime::Config.end_of_workday(day))
|
10
|
-
change_business_time(day,end_of_workday.hour,end_of_workday.min,end_of_workday.sec)
|
11
|
-
end
|
12
|
-
|
13
|
-
# Gives the time at the beginning of the workday, assuming that this time
|
14
|
-
# falls on a workday.
|
15
|
-
# Note: It pretends that this day is a workday whether or not it really is a
|
16
|
-
# workday.
|
17
|
-
def beginning_of_workday(day)
|
18
|
-
beginning_of_workday = Time.parse(BusinessTime::Config.beginning_of_workday(day))
|
19
|
-
change_business_time(day,beginning_of_workday.hour,beginning_of_workday.min,beginning_of_workday.sec)
|
20
|
-
end
|
21
|
-
|
22
|
-
# True if this time is on a workday (between 00:00:00 and 23:59:59), even if
|
23
|
-
# this time falls outside of normal business hours.
|
24
|
-
def workday?(day)
|
25
|
-
Time.weekday?(day) &&
|
26
|
-
!BusinessTime::Config.holidays.include?(day.to_date)
|
27
|
-
end
|
28
|
-
|
29
|
-
# True if this time falls on a weekday.
|
30
|
-
def weekday?(day)
|
31
|
-
BusinessTime::Config.weekdays.include? day.wday
|
32
|
-
end
|
33
|
-
|
34
|
-
def before_business_hours?(time)
|
35
|
-
time.to_i < beginning_of_workday(time).to_i
|
36
|
-
end
|
37
|
-
|
38
|
-
def after_business_hours?(time)
|
39
|
-
time.to_i > end_of_workday(time).to_i
|
40
|
-
end
|
41
|
-
|
42
|
-
# Rolls forward to the next beginning_of_workday
|
43
|
-
# when the time is outside of business hours
|
44
|
-
def roll_forward(time)
|
45
|
-
|
46
|
-
if Time.before_business_hours?(time) || !Time.workday?(time)
|
47
|
-
next_business_time = Time.beginning_of_workday(time)
|
48
|
-
elsif Time.after_business_hours?(time) || Time.end_of_workday(time) == time
|
49
|
-
next_business_time = Time.beginning_of_workday(time + 1.day)
|
50
|
-
else
|
51
|
-
next_business_time = time.clone
|
52
|
-
end
|
53
|
-
|
54
|
-
while !Time.workday?(next_business_time)
|
55
|
-
next_business_time = Time.beginning_of_workday(next_business_time + 1.day)
|
56
|
-
end
|
57
|
-
|
58
|
-
next_business_time
|
59
|
-
end
|
60
|
-
|
61
|
-
# Rolls backwards to the previous end_of_workday when the time is outside
|
62
|
-
# of business hours
|
63
|
-
def roll_backward(time)
|
64
|
-
prev_business_time = if (Time.before_business_hours?(time) || !Time.workday?(time))
|
65
|
-
Time.end_of_workday(time) - 1.day
|
66
|
-
elsif Time.after_business_hours?(time)
|
67
|
-
Time.end_of_workday(time)
|
68
|
-
else
|
69
|
-
time.clone
|
70
|
-
end
|
71
|
-
|
72
|
-
while !Time.workday?(prev_business_time)
|
73
|
-
prev_business_time -= 1.day
|
74
|
-
end
|
75
|
-
|
76
|
-
prev_business_time
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def change_business_time time, hour, min=0, sec=0
|
82
|
-
if Time.zone
|
83
|
-
time.in_time_zone(Time.zone).change(:hour => hour, :min => min, :sec => sec)
|
84
|
-
else
|
85
|
-
time.change(:hour => hour, :min => min, :sec => sec)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def business_time_until(to_time)
|
91
|
-
# Make sure that we will calculate time from A to B "clockwise"
|
92
|
-
if self < to_time
|
93
|
-
time_a = self
|
94
|
-
time_b = to_time
|
95
|
-
direction = 1
|
96
|
-
else
|
97
|
-
time_a = to_time
|
98
|
-
time_b = self
|
99
|
-
direction = -1
|
100
|
-
end
|
101
|
-
|
102
|
-
# Align both times to the closest business hours
|
103
|
-
time_a = Time::roll_forward(time_a)
|
104
|
-
time_b = Time::roll_forward(time_b)
|
105
|
-
|
106
|
-
# If same date, then calculate difference straight forward
|
107
|
-
if time_a.to_date == time_b.to_date
|
108
|
-
result = time_b - time_a
|
109
|
-
return result * direction
|
110
|
-
end
|
111
|
-
|
112
|
-
# Both times are in different dates
|
113
|
-
#result = Time.parse(time_a.strftime('%Y-%m-%d ') + BusinessTime::Config.end_of_workday) - time_a # First day
|
114
|
-
#result += time_b - Time.parse(time_b.strftime('%Y-%m-%d ') + BusinessTime::Config.beginning_of_workday) # Last day
|
115
|
-
|
116
|
-
result = 0
|
117
|
-
|
118
|
-
# All days in between
|
119
|
-
time_c = time_a
|
120
|
-
while time_c.to_i < time_b.to_i do
|
121
|
-
end_of_workday = Time.end_of_workday(time_c)
|
122
|
-
if time_c.to_date == time_b.to_date
|
123
|
-
if end_of_workday < time_b
|
124
|
-
result += end_of_workday - time_c
|
125
|
-
break
|
126
|
-
else
|
127
|
-
result += time_b - time_c
|
128
|
-
break
|
129
|
-
end
|
130
|
-
else
|
131
|
-
result += end_of_workday - time_c
|
132
|
-
time_c = Time::roll_forward(end_of_workday)
|
133
|
-
end
|
134
|
-
result += 1 if end_of_workday.to_s =~ /23:59:59/
|
135
|
-
end
|
136
|
-
|
137
|
-
# Make sure that sign is correct
|
138
|
-
result * direction
|
139
|
-
end
|
3
|
+
include BusinessTime::TimeExtensions
|
140
4
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module BusinessTime
|
2
|
+
module TimeExtensions
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Gives the time at the end of the workday, assuming that this time falls on a
|
9
|
+
# workday.
|
10
|
+
# Note: It pretends that this day is a workday whether or not it really is a
|
11
|
+
# workday.
|
12
|
+
def end_of_workday(day)
|
13
|
+
end_of_workday = Time.parse(BusinessTime::Config.end_of_workday(day))
|
14
|
+
change_business_time(day,end_of_workday.hour,end_of_workday.min,end_of_workday.sec)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Gives the time at the beginning of the workday, assuming that this time
|
18
|
+
# falls on a workday.
|
19
|
+
# Note: It pretends that this day is a workday whether or not it really is a
|
20
|
+
# workday.
|
21
|
+
def beginning_of_workday(day)
|
22
|
+
beginning_of_workday = Time.parse(BusinessTime::Config.beginning_of_workday(day))
|
23
|
+
change_business_time(day,beginning_of_workday.hour,beginning_of_workday.min,beginning_of_workday.sec)
|
24
|
+
end
|
25
|
+
|
26
|
+
# True if this time is on a workday (between 00:00:00 and 23:59:59), even if
|
27
|
+
# this time falls outside of normal business hours.
|
28
|
+
def workday?(day)
|
29
|
+
Time.weekday?(day) &&
|
30
|
+
!BusinessTime::Config.holidays.include?(day.to_date)
|
31
|
+
end
|
32
|
+
|
33
|
+
# True if this time falls on a weekday.
|
34
|
+
def weekday?(day)
|
35
|
+
BusinessTime::Config.weekdays.include? day.wday
|
36
|
+
end
|
37
|
+
|
38
|
+
def before_business_hours?(time)
|
39
|
+
time.to_i < beginning_of_workday(time).to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def after_business_hours?(time)
|
43
|
+
time.to_i > end_of_workday(time).to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
# Rolls forward to the next beginning_of_workday
|
47
|
+
# when the time is outside of business hours
|
48
|
+
def roll_forward(time)
|
49
|
+
|
50
|
+
if Time.before_business_hours?(time) || !Time.workday?(time)
|
51
|
+
next_business_time = Time.beginning_of_workday(time)
|
52
|
+
elsif Time.after_business_hours?(time) || Time.end_of_workday(time) == time
|
53
|
+
next_business_time = Time.beginning_of_workday(time + 1.day)
|
54
|
+
else
|
55
|
+
next_business_time = time.clone
|
56
|
+
end
|
57
|
+
|
58
|
+
while !Time.workday?(next_business_time)
|
59
|
+
next_business_time = Time.beginning_of_workday(next_business_time + 1.day)
|
60
|
+
end
|
61
|
+
|
62
|
+
next_business_time
|
63
|
+
end
|
64
|
+
|
65
|
+
# Rolls backwards to the previous end_of_workday when the time is outside
|
66
|
+
# of business hours
|
67
|
+
def roll_backward(time)
|
68
|
+
prev_business_time = if (Time.before_business_hours?(time) || !Time.workday?(time))
|
69
|
+
Time.end_of_workday(time - 1.day)
|
70
|
+
elsif Time.after_business_hours?(time)
|
71
|
+
Time.end_of_workday(time)
|
72
|
+
else
|
73
|
+
time.clone
|
74
|
+
end
|
75
|
+
|
76
|
+
while !Time.workday?(prev_business_time)
|
77
|
+
prev_business_time = Time.end_of_workday(prev_business_time - 1.day)
|
78
|
+
end
|
79
|
+
|
80
|
+
prev_business_time
|
81
|
+
end
|
82
|
+
|
83
|
+
def work_hours_total(day)
|
84
|
+
return 0 unless Time.workday?(day)
|
85
|
+
|
86
|
+
day = day.strftime('%a').downcase.to_sym
|
87
|
+
|
88
|
+
if hours = BusinessTime::Config.work_hours[day]
|
89
|
+
BusinessTime::Config.work_hours_total[day] ||= begin
|
90
|
+
hours_last = hours.last
|
91
|
+
if hours_last == '00:00'
|
92
|
+
(Time.parse('23:59') - Time.parse(hours.first)) + 1.minute
|
93
|
+
else
|
94
|
+
Time.parse(hours_last) - Time.parse(hours.first)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
BusinessTime::Config.work_hours_total[:default] ||= begin
|
99
|
+
Time.parse(BusinessTime::Config.end_of_workday) - Time.parse(BusinessTime::Config.beginning_of_workday)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def change_business_time time, hour, min=0, sec=0
|
107
|
+
if Time.zone
|
108
|
+
time.in_time_zone(Time.zone).change(:hour => hour, :min => min, :sec => sec)
|
109
|
+
else
|
110
|
+
time.change(:hour => hour, :min => min, :sec => sec)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def business_time_until(to_time)
|
116
|
+
# Make sure that we will calculate time from A to B "clockwise"
|
117
|
+
if self < to_time
|
118
|
+
time_a = self
|
119
|
+
time_b = to_time
|
120
|
+
direction = 1
|
121
|
+
else
|
122
|
+
time_a = to_time
|
123
|
+
time_b = self
|
124
|
+
direction = -1
|
125
|
+
end
|
126
|
+
|
127
|
+
# Align both times to the closest business hours
|
128
|
+
time_a = Time::roll_forward(time_a)
|
129
|
+
time_b = Time::roll_forward(time_b)
|
130
|
+
|
131
|
+
if time_a.to_date == time_b.to_date
|
132
|
+
time_b - time_a
|
133
|
+
else
|
134
|
+
end_of_workday = Time.end_of_workday(time_a)
|
135
|
+
end_of_workday += 1 if end_of_workday.to_s =~ /23:59:59/
|
136
|
+
|
137
|
+
first_day = end_of_workday - time_a
|
138
|
+
days_in_between = ((time_a.to_date + 1)..(time_b.to_date - 1)).sum{ |day| Time::work_hours_total(day) }
|
139
|
+
last_day = time_b - Time.beginning_of_workday(time_b)
|
140
|
+
|
141
|
+
first_day + days_in_between + last_day
|
142
|
+
end * direction
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: business_time
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-03-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -120,9 +120,11 @@ files:
|
|
120
120
|
- lib/business_time/business_days.rb
|
121
121
|
- lib/business_time/business_hours.rb
|
122
122
|
- lib/business_time/config.rb
|
123
|
+
- lib/business_time/core_ext/active_support/time_with_zone.rb
|
123
124
|
- lib/business_time/core_ext/date.rb
|
124
125
|
- lib/business_time/core_ext/fixnum.rb
|
125
126
|
- lib/business_time/core_ext/time.rb
|
127
|
+
- lib/business_time/time_extensions.rb
|
126
128
|
- lib/business_time/version.rb
|
127
129
|
- lib/generators/business_time/config_generator.rb
|
128
130
|
- rails_generators/business_time_config/business_time_config_generator.rb
|
@@ -143,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
143
145
|
version: '0'
|
144
146
|
segments:
|
145
147
|
- 0
|
146
|
-
hash:
|
148
|
+
hash: 2725720830618277021
|
147
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
150
|
none: false
|
149
151
|
requirements:
|
@@ -152,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
154
|
version: '0'
|
153
155
|
segments:
|
154
156
|
- 0
|
155
|
-
hash:
|
157
|
+
hash: 2725720830618277021
|
156
158
|
requirements: []
|
157
159
|
rubyforge_project:
|
158
160
|
rubygems_version: 1.8.23
|