Moved to external module for business time calculation.
This commit is contained in:
parent
b30ef07940
commit
be6a3fdcc4
5 changed files with 351 additions and 13 deletions
70
lib/business_time/business_minutes.rb
Normal file
70
lib/business_time/business_minutes.rb
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
module BusinessTime
|
||||||
|
|
||||||
|
class BusinessMinutes
|
||||||
|
def initialize(minutes)
|
||||||
|
@minutes = minutes
|
||||||
|
end
|
||||||
|
|
||||||
|
def ago
|
||||||
|
Time.zone ? before(Time.zone.now) : before(Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def from_now
|
||||||
|
Time.zone ? after(Time.zone.now) : after(Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def after(time)
|
||||||
|
after_time = Time.roll_forward(time)
|
||||||
|
# Step through the hours, skipping over non-business hours
|
||||||
|
@minutes.times do
|
||||||
|
after_time = after_time + 1.minute
|
||||||
|
|
||||||
|
# Ignore minutes before opening and after closing
|
||||||
|
if (after_time > Time.end_of_workday(after_time))
|
||||||
|
after_time = after_time + off_minutes
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ignore weekends and holidays
|
||||||
|
while !Time.workday?(after_time)
|
||||||
|
after_time = after_time + 1.day
|
||||||
|
end
|
||||||
|
end
|
||||||
|
after_time
|
||||||
|
end
|
||||||
|
alias_method :since, :after
|
||||||
|
|
||||||
|
def before(time)
|
||||||
|
before_time = Time.roll_forward(time)
|
||||||
|
# Step through the hours, skipping over non-business hours
|
||||||
|
@minutes.times do
|
||||||
|
before_time = before_time - 1.minute
|
||||||
|
|
||||||
|
# Ignore hours before opening and after closing
|
||||||
|
if (before_time < Time.beginning_of_workday(before_time))
|
||||||
|
before_time = before_time - off_minutes
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ignore weekends and holidays
|
||||||
|
while !Time.workday?(before_time)
|
||||||
|
before_time = before_time - 1.day
|
||||||
|
end
|
||||||
|
end
|
||||||
|
before_time
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def off_minutes
|
||||||
|
return @gap if @gap
|
||||||
|
if Time.zone
|
||||||
|
gap_end = Time.zone.parse(BusinessTime::Config.beginning_of_workday)
|
||||||
|
gap_begin = (Time.zone.parse(BusinessTime::Config.end_of_workday)-1.day)
|
||||||
|
else
|
||||||
|
gap_end = Time.parse(BusinessTime::Config.beginning_of_workday)
|
||||||
|
gap_begin = (Time.parse(BusinessTime::Config.end_of_workday) - 1.day)
|
||||||
|
end
|
||||||
|
@gap = gap_end - gap_begin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
11
lib/business_time/core_ext/fixnum_minute.rb
Normal file
11
lib/business_time/core_ext/fixnum_minute.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# hook into fixnum so we can say things like:
|
||||||
|
# 5.business_minutes.from_now
|
||||||
|
# 4.business_minutes.before(some_date_time)
|
||||||
|
class Fixnum
|
||||||
|
include BusinessTime
|
||||||
|
|
||||||
|
def business_minutes
|
||||||
|
BusinessMinutes.new(self)
|
||||||
|
end
|
||||||
|
alias_method :business_minute, :business_minutes
|
||||||
|
end
|
131
lib/business_time/core_ext/time_fix.rb
Normal file
131
lib/business_time/core_ext/time_fix.rb
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
# Add workday and weekday concepts to the Time class
|
||||||
|
class Time
|
||||||
|
class << self
|
||||||
|
|
||||||
|
# Gives the time at the end of the workday, assuming that this time falls on a
|
||||||
|
# workday.
|
||||||
|
# Note: It pretends that this day is a workday whether or not it really is a
|
||||||
|
# workday.
|
||||||
|
def end_of_workday(day)
|
||||||
|
format = "%B %d %Y #{BusinessTime::Config.end_of_workday}"
|
||||||
|
Time.zone ? Time.zone.parse(day.strftime(format)) :
|
||||||
|
Time.parse(day.strftime(format))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gives the time at the beginning of the workday, assuming that this time
|
||||||
|
# falls on a workday.
|
||||||
|
# Note: It pretends that this day is a workday whether or not it really is a
|
||||||
|
# workday.
|
||||||
|
def beginning_of_workday(day)
|
||||||
|
format = "%B %d %Y #{BusinessTime::Config.beginning_of_workday}"
|
||||||
|
Time.zone ? Time.zone.parse(day.strftime(format)) :
|
||||||
|
Time.parse(day.strftime(format))
|
||||||
|
end
|
||||||
|
|
||||||
|
# True if this time is on a workday (between 00:00:00 and 23:59:59), even if
|
||||||
|
# this time falls outside of normal business hours.
|
||||||
|
def workday?(day)
|
||||||
|
Time.weekday?(day) &&
|
||||||
|
!BusinessTime::Config.holidays.include?(day.to_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
# True if this time falls on a weekday.
|
||||||
|
def weekday?(day)
|
||||||
|
BusinessTime::Config.weekdays.include? day.wday
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_business_hours?(time)
|
||||||
|
time < beginning_of_workday(time)
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_business_hours?(time)
|
||||||
|
time > end_of_workday(time)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rolls forward to the next beginning_of_workday
|
||||||
|
# when the time is outside of business hours
|
||||||
|
def roll_forward(time)
|
||||||
|
if (Time.before_business_hours?(time) || !Time.workday?(time))
|
||||||
|
next_business_time = Time.beginning_of_workday(time)
|
||||||
|
elsif Time.after_business_hours?(time + 1)
|
||||||
|
next_business_time = Time.beginning_of_workday(time) + 1.day
|
||||||
|
else
|
||||||
|
next_business_time = time.clone
|
||||||
|
end
|
||||||
|
|
||||||
|
while !Time.workday?(next_business_time)
|
||||||
|
puts '4'
|
||||||
|
next_business_time += 1.day
|
||||||
|
end
|
||||||
|
|
||||||
|
next_business_time
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Time
|
||||||
|
|
||||||
|
def business_time_until(to_time)
|
||||||
|
|
||||||
|
# Make sure that we will calculate time from A to B "clockwise"
|
||||||
|
direction = 1
|
||||||
|
if self < to_time
|
||||||
|
time_a = self
|
||||||
|
time_b = to_time
|
||||||
|
else
|
||||||
|
time_a = to_time
|
||||||
|
time_b = self
|
||||||
|
direction = -1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Align both times to the closest business hours
|
||||||
|
time_a = Time::roll_forward(time_a)
|
||||||
|
time_b = Time::roll_forward(time_b)
|
||||||
|
|
||||||
|
# If same date, then calculate difference straight forward
|
||||||
|
if time_a.to_date == time_b.to_date
|
||||||
|
result = time_b - time_a
|
||||||
|
return result *= direction
|
||||||
|
end
|
||||||
|
|
||||||
|
# Both times are in different dates
|
||||||
|
result = Time.parse(time_a.strftime('%Y-%m-%d ') + BusinessTime::Config.end_of_workday) - time_a # First day
|
||||||
|
result += time_b - Time.parse(time_b.strftime('%Y-%m-%d ') + BusinessTime::Config.beginning_of_workday) # Last day
|
||||||
|
|
||||||
|
# All days in between
|
||||||
|
puts "--- #{time_a}-#{time_b} - #{direction}"
|
||||||
|
|
||||||
|
# duration_of_working_day = Time.parse(BusinessTime::Config.end_of_workday) - Time.parse(BusinessTime::Config.beginning_of_workday)
|
||||||
|
# result += (time_a.to_date.business_days_until(time_b.to_date) - 1) * duration_of_working_day
|
||||||
|
|
||||||
|
result = 0
|
||||||
|
|
||||||
|
# All days in between
|
||||||
|
time_c = time_a
|
||||||
|
while time_c.to_i < time_b.to_i do
|
||||||
|
end_of_workday = Time.end_of_workday(time_c)
|
||||||
|
if !Time.workday?(time_c)
|
||||||
|
time_c = Time.beginning_of_workday(time_c) + 1.day
|
||||||
|
puts 'VACATIONS! ' + time_c.to_s
|
||||||
|
end
|
||||||
|
if time_c.to_date == time_b.to_date
|
||||||
|
if end_of_workday < time_b
|
||||||
|
result += end_of_workday - time_c
|
||||||
|
break
|
||||||
|
else
|
||||||
|
result += time_b - time_c
|
||||||
|
break
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result += end_of_workday - time_c
|
||||||
|
time_c = Time::roll_forward(end_of_workday)
|
||||||
|
end
|
||||||
|
result += 1 if end_of_workday.to_s =~ /23:59:59/
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make sure that sign is correct
|
||||||
|
result *= direction
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
40
lib/time_calculation.rb
Normal file
40
lib/time_calculation.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
require 'business_time'
|
||||||
|
require 'business_time/business_minutes'
|
||||||
|
require 'business_time/core_ext/fixnum_minute'
|
||||||
|
require 'business_time/core_ext/time_fix'
|
||||||
|
|
||||||
|
module TimeCalculation
|
||||||
|
def self.config(config)
|
||||||
|
BusinessTime::Config.beginning_of_workday = config['beginning_of_workday']
|
||||||
|
BusinessTime::Config.end_of_workday = config['end_of_workday']
|
||||||
|
days = []
|
||||||
|
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].each {|day|
|
||||||
|
if config[day]
|
||||||
|
days.push day.downcase.to_sym
|
||||||
|
end
|
||||||
|
}
|
||||||
|
BusinessTime::Config.work_week = days
|
||||||
|
holidays = []
|
||||||
|
if config['holidays']
|
||||||
|
config['holidays'].each {|holiday|
|
||||||
|
date = Date.parse( holiday )
|
||||||
|
holidays.push date.to_date
|
||||||
|
}
|
||||||
|
end
|
||||||
|
BusinessTime::Config.holidays = holidays
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.business_time_diff(start_time, end_time)
|
||||||
|
start_time = Time.parse( start_time.to_s + 'UTC' )
|
||||||
|
end_time = Time.parse( end_time.to_s + 'UTC' )
|
||||||
|
diff = start_time.business_time_until( end_time ) / 60
|
||||||
|
diff.round
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.dest_time(start_time, diff_in_min)
|
||||||
|
start_time = Time.parse( start_time.to_s + 'UTC' )
|
||||||
|
dest_time = diff_in_min.round.business_minute.after( start_time )
|
||||||
|
return dest_time
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -1,5 +1,6 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
require 'time_calculation'
|
||||||
|
|
||||||
class WorkingTimeTest < ActiveSupport::TestCase
|
class WorkingTimeTest < ActiveSupport::TestCase
|
||||||
test 'working time' do
|
test 'working time' do
|
||||||
|
@ -9,32 +10,117 @@ class WorkingTimeTest < ActiveSupport::TestCase
|
||||||
{
|
{
|
||||||
:start => '2012-12-17 08:00:00',
|
:start => '2012-12-17 08:00:00',
|
||||||
:end => '2012-12-18 08:00:00',
|
:end => '2012-12-18 08:00:00',
|
||||||
:diff => 480,
|
:diff => 600,
|
||||||
:config => {
|
:config => {
|
||||||
:work_week => [:mon, :tue, :wed, :thu, :fri ],
|
'Mon' => true,
|
||||||
:beginning_of_workday => '8:00 am',
|
'Tue' => true,
|
||||||
:end_of_workday => '6:00 pm',
|
'Wed' => true,
|
||||||
|
'Thu' => true,
|
||||||
|
'Fri' => true,
|
||||||
|
'beginning_of_workday' => '8:00 am',
|
||||||
|
'end_of_workday' => '6:00 pm',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
# test 2
|
# test 2
|
||||||
{
|
{
|
||||||
:start => '2012-12-23 08:00:00',
|
:start => '2012-12-17 08:00:00',
|
||||||
:end => '2012-12-24 10:30:42',
|
:end => '2012-12-17 09:00:00',
|
||||||
:diff => 0,
|
:diff => 60,
|
||||||
:config => {
|
:config => {
|
||||||
:work_week => [:mon, :tue, :wed, :thu, :fri ],
|
'Mon' => true,
|
||||||
:beginning_of_workday => '8:00 am',
|
'Tue' => true,
|
||||||
:end_of_workday => '6:00 pm',
|
'Wed' => true,
|
||||||
:holidays => [
|
'Thu' => true,
|
||||||
|
'Fri' => true,
|
||||||
|
'beginning_of_workday' => '8:00 am',
|
||||||
|
'end_of_workday' => '6:00 pm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
# test 3
|
||||||
|
{
|
||||||
|
:start => '2012-12-17 08:00:00',
|
||||||
|
:end => '2012-12-17 08:15:00',
|
||||||
|
:diff => 15,
|
||||||
|
:config => {
|
||||||
|
'Mon' => true,
|
||||||
|
'Tue' => true,
|
||||||
|
'Wed' => true,
|
||||||
|
'Thu' => true,
|
||||||
|
'Fri' => true,
|
||||||
|
'beginning_of_workday' => '8:00 am',
|
||||||
|
'end_of_workday' => '6:00 pm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
# test 4
|
||||||
|
{
|
||||||
|
:start => '2012-12-23 08:00:00',
|
||||||
|
:end => '2012-12-27 10:30:42',
|
||||||
|
# :diff => 0,
|
||||||
|
:diff => 151,
|
||||||
|
:config => {
|
||||||
|
'Mon' => true,
|
||||||
|
'Tue' => true,
|
||||||
|
'Wed' => true,
|
||||||
|
'Thu' => true,
|
||||||
|
'Fri' => true,
|
||||||
|
'beginning_of_workday' => '8:00 am',
|
||||||
|
'end_of_workday' => '6:00 pm',
|
||||||
|
'holidays' => [
|
||||||
'2012-12-24', '2012-12-25', '2012-12-26'
|
'2012-12-24', '2012-12-25', '2012-12-26'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
tests.each { |test|
|
tests.each { |test|
|
||||||
# diff = some_method( test[:start], test[:end], test[:config] )
|
TimeCalculation.config( test[:config] )
|
||||||
# assert_equal( diff, test[:diff], 'diff' )
|
diff = TimeCalculation.business_time_diff( test[:start], test[:end] )
|
||||||
|
assert_equal( diff, test[:diff], 'diff' )
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'dest time' do
|
||||||
|
tests = [
|
||||||
|
|
||||||
|
# test 1
|
||||||
|
{
|
||||||
|
:start => '2012-12-17 08:00:00',
|
||||||
|
:dest_time => '2012-12-17 18:00:00',
|
||||||
|
:diff => 600,
|
||||||
|
:config => {
|
||||||
|
'Mon' => true,
|
||||||
|
'Tue' => true,
|
||||||
|
'Wed' => true,
|
||||||
|
'Thu' => true,
|
||||||
|
'Fri' => true,
|
||||||
|
'beginning_of_workday' => '8:00 am',
|
||||||
|
'end_of_workday' => '6:00 pm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
# test 2
|
||||||
|
{
|
||||||
|
:start => '2012-12-17 08:00:00',
|
||||||
|
:dest_time => '2012-12-18 08:30:00',
|
||||||
|
:diff => 630,
|
||||||
|
:config => {
|
||||||
|
'Mon' => true,
|
||||||
|
'Tue' => true,
|
||||||
|
'Wed' => true,
|
||||||
|
'Thu' => true,
|
||||||
|
'Fri' => true,
|
||||||
|
'beginning_of_workday' => '8:00 am',
|
||||||
|
'end_of_workday' => '6:00 pm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
tests.each { |test|
|
||||||
|
TimeCalculation.config( test[:config] )
|
||||||
|
dest_time = TimeCalculation.dest_time( test[:start], test[:diff] )
|
||||||
|
assert_equal( dest_time, Time.parse( test[:dest_time] + ' UTC' ), 'dest time' )
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue