module TimeCalculation =begin put working hours matrix and timezone in function, returns UTC working hours matrix working_hours_martix = TimeCalculation.working_hours('2013-10-27 20:00:15', working_hours_matrix, 'Europe/Berlin') working_hours_martix = { :Mon => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil], :Tue => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil], :Wed => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil], :Thu => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil], :Fri => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil], :Sat => [], :Sun => [], } =end def self.working_hours(start_time, config, timezone) time_diff = 0 if timezone begin time_diff = Time.parse(start_time.to_s).in_time_zone(timezone).utc_offset rescue Exception => e puts "ERROR: Can't fine tomezone #{timezone}" puts e.inspect puts e.backtrace end end beginning_of_workday = Time.parse("1977-10-27 #{config['beginning_of_workday']}") end_of_workday = Time.parse("1977-10-27 #{config['end_of_workday']}") - 3600 config_ok = false working_hours = {} [:Mon, :Tue, :Wed, :Thu, :Fri, :Sat, :Sun].each {|day| working_hours[day] = [] if config[day.to_s] == true || config[day.to_s] == day.to_s config_ok = true (0..23).each {|hour| time = Time.parse("1977-10-27 #{hour}:00:00") if time >= beginning_of_workday && time <= end_of_workday working_hours[day].push true else working_hours[day].push nil end } end } if !config_ok raise "sla config is invalid! " + config.inspect end # shift working hours / if needed if time_diff && time_diff != 0 hours_to_shift = (time_diff / 3600 ).round move_items = { :Mon => [], :Tue => [], :Wed => [], :Thu => [], :Fri => [], :Sat => [], :Sun => [], } (1..hours_to_shift).each {|count| working_hours.each {|day, value| if working_hours[day] to_move = working_hours[day].shift if day == :Mon move_items[:Tue].push to_move elsif day == :Tue move_items[:Wed].push to_move elsif day == :Wed move_items[:Thu].push to_move elsif day == :Thu move_items[:Fri].push to_move elsif day == :Fri move_items[:Sat].push to_move elsif day == :Sat move_items[:Sun].push to_move elsif day == :Sun move_items[:Mon].push to_move end end } } move_items.each {|day, value| value.each {|item| working_hours[day].push item } } end return working_hours end =begin returns business hours in minutes between to dates business_hours_in_min = Time.Calculation.business_time_diff( '2013-10-27 14:00:15', '2013-10-27 18:10:15', working_hours_martix, 'Europe/Berlin', ) =end def self.business_time_diff(start_time, end_time, config = nil, timezone = '') if start_time.class == String start_time = Time.parse( start_time.to_s + 'UTC' ) end if end_time.class == String end_time = Time.parse( end_time.to_s + 'UTC' ) end # if no config is given, just return calculation directly if !config return ((end_time - start_time) / 60 ).round end working_hours = self.working_hours(start_time, config, timezone) week_day_map = { 1 => :Mon, 2 => :Tue, 3 => :Wed, 4 => :Thu, 5 => :Fri, 6 => :Sat, 0 => :Sun, } count = 0 calculation = true first_loop = true while calculation do week_day = start_time.wday day = start_time.day month = start_time.month year = start_time.year hour = start_time.hour # check if it's vacation day if config if config['holidays'] if config['holidays'].include?("#{year}-#{month}-#{day}") # jump to next day start_time = start_time.beginning_of_day + 86400 next end end end # check if it's countable day if working_hours[ week_day_map[week_day] ].empty? # jump to next day start_time = start_time.beginning_of_day + 86400 next end # fillup to first full hour if first_loop diff = end_time - start_time if diff > 59 * 60 diff = start_time - start_time.beginning_of_hour end start_time += diff # check if it's countable hour if working_hours[ week_day_map[week_day] ][ hour ] count += diff end end first_loop = false # loop to next hour (hour..23).each { |next_hour| # check if end time is lower if start_time >= end_time calculation = false break end # check if end_time is within this hour diff = end_time - start_time if diff > 59 * 60 diff = 3600 end # keep it in current day if next_hour == 23 start_time += diff-1 else start_time += diff end # check if it's business hour and count if working_hours[ week_day_map[week_day] ][ next_hour ] count += diff end } # loop to next day start_time = start_time.beginning_of_day + 86400 end diff = count / 60 diff.round end =begin returns destination date of start time plus X minutes dest_time = Time.Calculation.dest_time( '2013-10-27 14:00:15', 120, working_hours_martix, 'Europe/Berlin', ) =end def self.dest_time(start_time, diff_in_min, config = nil, timezone = '') if start_time.class == String start_time = Time.parse( start_time.to_s + ' UTC' ) end return start_time if diff_in_min == 0 # if no config is given, just return calculation directly if !config return start_time + (diff_in_min * 60) end # loop working_hours = self.working_hours(start_time, config, timezone) week_day_map = { 1 => :Mon, 2 => :Tue, 3 => :Wed, 4 => :Thu, 5 => :Fri, 6 => :Sat, 0 => :Sun, } count = diff_in_min * 60 calculation = true first_loop = true while calculation do week_day = start_time.wday day = start_time.day month = start_time.month year = start_time.year hour = start_time.hour #puts "start outer loop #{start_time}-#{week_day}-#{year}-#{month}-#{day}-#{hour}|c#{count}" # check if it's vacation day if config if config['holidays'] if config['holidays'].include?("#{year}-#{month}-#{day}") # jump to next day start_time = start_time.beginning_of_day + 86400 next end end end # check if it's countable day if working_hours[ week_day_map[week_day] ].empty? # jump to next day start_time = start_time.beginning_of_day + 86400 next end # fillup to first full hour if first_loop # get rest of this hour if diff_in_min in lower the one hour diff_to_count = 3600 if diff_to_count > (diff_in_min * 60) diff_to_count = diff_in_min * 60 end diff = diff_to_count - (start_time - start_time.beginning_of_hour) start_time += diff # check if it's countable hour if working_hours[ week_day_map[week_day] ][ hour ] count -= diff end # start on next hour of we moved to next if diff != 0 hour += 1 end end first_loop = false # loop to next hour (hour..23).each { |next_hour| diff = 3600 # check if count positiv if count <= 0 calculation = false break end # check if it's business hour and count if working_hours[ week_day_map[week_day] ][ next_hour ] # check if count is within this hour if count > 59 * 60 diff = 3600 else diff = count end count -= diff end # keep it in current day if next_hour == 23 start_time += diff-1 else start_time += diff end } # check if count positiv if count <= 0 calculation = false break end # loop to next day start_time = start_time.beginning_of_day + 86400 end return start_time end end