计算两天之间的工作日数

时间:2010-10-26 20:36:47

标签: ruby-on-rails ruby date

我需要计算两个日期之间的工作日数。如何使用Ruby(或Rails ......如果有特定于Rails的帮助程序)来解决这个问题。

同样,我希望能够在指定日期添加工作日。

因此,如果一个日期在星期四下降并且我增加了3个工作日,它将在下周二返回。

8 个答案:

答案 0 :(得分:30)

看看business_time。它可以用于你要问的两件事。

计算两个日期之间的营业日:

wednesday = Date.parse("October 17, 2018")
monday = Date.parse("October 22, 2018")
wednesday.business_days_until(monday) # => 3

将工作日添加到指定日期:

4.business_days.from_now
8.business_days.after(some_date)

历史回答

最初询问此问题时,business_time未提供business_days_until方法,因此提供了以下方法来回答问题的第一部分。

这对于那些不需要business_time中任何其他功能并希望避免添加其他依赖项的人来说仍然有用。

def business_days_between(date1, date2)
  business_days = 0
  date = date2
  while date > date1
   business_days = business_days + 1 unless date.saturday? or date.sunday?
   date = date - 1.day
  end
  business_days
end

这也可以通过微调来处理Tipx以您希望的方式提及的案例。

答案 1 :(得分:17)

我们曾经使用mikej's answer中建议的算法,并发现计算25,000个数年的范围每个需要340秒。

这是另一种渐近复杂度为O(1)的算法。它在0.41秒内完成相同的计算。

# Calculates the number of business days in range (start_date, end_date]
#
# @param start_date [Date]
# @param end_date [Date]
#
# @return [Fixnum]
def business_days_between(start_date, end_date)
  days_between = (end_date - start_date).to_i
  return 0 unless days_between > 0

  # Assuming we need to calculate days from 9th to 25th, 10-23 are covered
  # by whole weeks, and 24-25 are extra days.
  #
  # Su Mo Tu We Th Fr Sa    # Su Mo Tu We Th Fr Sa
  #        1  2  3  4  5    #        1  2  3  4  5
  #  6  7  8  9 10 11 12    #  6  7  8  9 ww ww ww
  # 13 14 15 16 17 18 19    # ww ww ww ww ww ww ww
  # 20 21 22 23 24 25 26    # ww ww ww ww ed ed 26
  # 27 28 29 30 31          # 27 28 29 30 31
  whole_weeks, extra_days = days_between.divmod(7)

  unless extra_days.zero?
    # Extra days start from the week day next to start_day,
    # and end on end_date's week date. The position of the
    # start date in a week can be either before (the left calendar)
    # or after (the right one) the end date.
    #
    # Su Mo Tu We Th Fr Sa    # Su Mo Tu We Th Fr Sa
    #        1  2  3  4  5    #        1  2  3  4  5
    #  6  7  8  9 10 11 12    #  6  7  8  9 10 11 12
    # ## ## ## ## 17 18 19    # 13 14 15 16 ## ## ##
    # 20 21 22 23 24 25 26    # ## 21 22 23 24 25 26
    # 27 28 29 30 31          # 27 28 29 30 31
    #
    # If some of the extra_days fall on a weekend, they need to be subtracted.
    # In the first case only corner days can be days off,
    # and in the second case there are indeed two such days.
    extra_days -= if start_date.tomorrow.wday <= end_date.wday
                    [start_date.tomorrow.sunday?, end_date.saturday?].count(true)
                  else
                    2
                  end
  end

  (whole_weeks * 5) + extra_days
end

答案 2 :(得分:6)

business_time具有您想要的所有功能。

自述文件:

  

#您还可以计算两个日期之间的营业时间

friday = Date.parse("December 24, 2010")
monday = Date.parse("December 27, 2010")
friday.business_days_until(monday) #=> 1

将工作日添加到指定日期:

some_date = Date.parse("August 4th, 1969")
8.business_days.after(some_date) #=> 14 Aug 1969

答案 3 :(得分:1)

看看Workpattern。它允许您指定工作和休息时间,并可以在一个日期之间添加/减去持续时间,以及计算两个日期之间的分钟数。

您可以为不同的场景设置工作模式,例如mon-fri working或sun-thu,您可以有假期以及整个或部分时间。

我写这篇文章是为了学习Ruby。还需要让它更像Ruby-ish。

答案 4 :(得分:1)

这是我的(非宝石和非假期)工作日计数示例:

first_date = Date.new(2016,1,5)
second_date = Date.new(2016,1,12)
count = 0
(first_date...second_date).each{|d| count+=1 if (1..5).include?(d.wday)}
count

答案 5 :(得分:0)

基于@ mikej的回答。但这也考虑到假期,并返回一天的一小部分(达到小时精度):

def num_days hi, lo
  num_hours = 0
  while hi > lo
    num_hours += 1 if hi.workday? and !hi.holiday?
    hi -= 1.hour
  end
  num_hours.to_f / 24
end

这会使用holidaysbusiness_time宝石。

答案 6 :(得分:0)

上面列出的最流行的解决方案存在两个问题:

  1. 它们涉及循环来计算每个日期之间的每一天(意味着日期越远,性能越差。
  2. 他们不清楚他们是从一天开始算还是算结束。如果算上午,周五和周六之间有一个工作日。如果从夜晚算起,周五和周六之间的工作日为零。
  3. 炖过之后,我提出了解决这两个问题的解决方案。以下是参考日期和其他日期,并计算它们之间的工作日数(如果其他日期在参考日期之前,则返回负数)。参数 eod_base 控制是否从一天结束(eod)或一天开始计算。它可以更紧凑地编写,但希望它相对容易理解,它不需要宝石或轨道。

      require 'date'
    
      def weekdays_between(ref,otr,eod_base=true)
            dates = [ref,otr].sort
            return 0 if dates[0] == dates[1]
            full_weeks = ((dates[1]-dates[0])/7).floor  
            dates[eod_base ? 0 : 1] += (eod_base ? 1 : -1)
            part_week = Range.new(dates[0],dates[1])
                             .inject(0){|m,v|  (v.wday >=1 && v.wday <= 5) ? (m+1) : m }
            return (otr <=> ref) * (full_weeks*5 + part_week)
      end
    

答案 7 :(得分:0)

计算总工作天数的简单脚本

require 'date'
(DateTime.parse('2016-01-01')...DateTime.parse('2017-01-01')).
inject({}) do |s,e| 
   s[e.month]||=0
   if((1..5).include?(e.wday)) 
     s[e.month]+=1
   end
   s
end

# => {1=>21, 2=>21, 3=>23, 4=>21, 5=>22, 6=>22, 7=>21, 8=>23, 9=>22, 10=>21, 11=>22, 12=>22}