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
|
||||
require 'test_helper'
|
||||
require 'time_calculation'
|
||||
|
||||
class WorkingTimeTest < ActiveSupport::TestCase
|
||||
test 'working time' do
|
||||
|
@ -9,32 +10,117 @@ class WorkingTimeTest < ActiveSupport::TestCase
|
|||
{
|
||||
:start => '2012-12-17 08:00:00',
|
||||
:end => '2012-12-18 08:00:00',
|
||||
:diff => 480,
|
||||
:diff => 600,
|
||||
:config => {
|
||||
:work_week => [:mon, :tue, :wed, :thu, :fri ],
|
||||
:beginning_of_workday => '8:00 am',
|
||||
:end_of_workday => '6:00 pm',
|
||||
'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-23 08:00:00',
|
||||
:end => '2012-12-24 10:30:42',
|
||||
:diff => 0,
|
||||
:start => '2012-12-17 08:00:00',
|
||||
:end => '2012-12-17 09:00:00',
|
||||
:diff => 60,
|
||||
:config => {
|
||||
:work_week => [:mon, :tue, :wed, :thu, :fri ],
|
||||
:beginning_of_workday => '8:00 am',
|
||||
:end_of_workday => '6:00 pm',
|
||||
:holidays => [
|
||||
'Mon' => true,
|
||||
'Tue' => true,
|
||||
'Wed' => true,
|
||||
'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'
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
tests.each { |test|
|
||||
# diff = some_method( test[:start], test[:end], test[:config] )
|
||||
# assert_equal( diff, test[:diff], 'diff' )
|
||||
TimeCalculation.config( test[:config] )
|
||||
diff = TimeCalculation.business_time_diff( test[:start], test[:end] )
|
||||
assert_equal( diff, test[:diff], 'diff' )
|
||||
}
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue