Browse Source

Moved to external module for business time calculation.

Martin Edenhofer 12 years ago
parent
commit
be6a3fdcc4

+ 70 - 0
lib/business_time/business_minutes.rb

@@ -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 - 0
lib/business_time/core_ext/fixnum_minute.rb

@@ -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 - 0
lib/business_time/core_ext/time_fix.rb

@@ -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 - 0
lib/time_calculation.rb

@@ -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

+ 98 - 12
test/unit/working_time_test.rb

@@ -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-17 08:00:00',
+        :end    => '2012-12-17 09:00:00',
+        :diff   => 60,
+        :config => {
+          '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-24 10:30:42',
-        :diff   => 0,
+        :end    => '2012-12-27 10:30:42',
+#        :diff   => 0,
+        :diff   => 151,
         :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',
+          '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