计算两天之间的工作日数

时间:2022-08-25 18:17:20

I need to calculate the number of business days between two dates. How can I pull that off using Ruby (or Rails...if there are Rails-specific helpers).

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

Likewise, I'd like to be able to add business days to a given date.

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

So if a date fell on a Thursday and I added 3 business days, it would return the next Tuesday.

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

8 个解决方案

#1


26  

Take a look at business_time. It can be used for the second half of what you're asking.

看看business_time。它可以用于你要求的后半部分。

e.g.

例如

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

Unfortunately, from the notes it appears the author is reluctant to add something like business_duration_between which would cover the first half of your question.

不幸的是,从笔记看来,作者似乎不愿意添加像business_duration_之类的东西来覆盖你问题的前半部分。

Update

更新

Below is a method to count the business days between two dates. You can fine tune this to handle the cases that Tipx mentions in the way that you would like.

以下是计算两个日期之间的工作日的方法。您可以对此进行微调,以处理Tipx以您希望的方式提及的情况。

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

#2


15  

We used to use the algorithm suggested in the mikej's answer and discovered that calculating 25,000 ranges of several years each takes 340 seconds.

我们过去常常使用mikej的答案中建议的算法,并发现计算25,000个数年的范围每个需要340秒。

Here's another algorithm with asymptotic complexity O(1). It does the same calculations in 0.41 seconds.

这是另一种渐近复杂度为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

#3


6  

business_time has all the functionallity you want.

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

From the readme:

从自述文件:

#you can also calculate business duration between two dates

#you还可以计算两个日期之间的业务持续时间

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

Adding business days to a given date:

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

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

#4


1  

Take a look at Workpattern. It alows you to specify working and resting periods and can add/subtract durations to/from a date as well as calculate the minutes between two dates.

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

You can set up workpatterns for different scenarios such as mon-fri working or sun-thu and you can have holidays and whole or part days.

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

I wrote this as away to learn Ruby. Still need to make it more Ruby-ish.

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

#5


1  

Here is my (non gem and non holiday) weekday count example:

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

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

#6


0  

Based on @mikej's answer. But this also takes into account holidays, and returns a fraction of a day (up to the hour accurancy):

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

This uses the holidays and business_time gems.

这使用假日和business_time宝石。

#7


0  

As a different person pointed out, the winning solution is really easy to understand but has a problem. It takes longer to run as the distance between the two dates being compared gets longer. That's kind of an ugly problem.

正如一位不同的人所指出的那样,获胜的解决方案非常容易理解,但却存在问题。由于比较的两个日期之间的距离变长,因此运行时间更长。这是一个丑陋的问题。

The below code is hopefully almost as simple to understand but takes roughly the same amount of time run regardless of the number of days between the two dates. It takes advantage of the fact that each full 7 day week will have 5 working days in it.

希望下面的代码几乎同样易于理解,但无论两个日期之间的天数如何,都需要大致相同的时间。它充分利用了每个完整的7天工作周将有5个工作日的事实。

require 'date'

  def weekdays_between(earlier_date,later_date)
    days_diff = (later_date - earlier_date).to_i
    weekdays = 0
    if days_diff >= 7
      whole_weeks = (days_diff/7).to_i
      later_date -= whole_weeks*7  
      weekdays += whole_weeks*5
    end
    if later_date > earlier_date
      dates_between = earlier_date..(later_date-1)
      weekdays += dates_between.count{|d| ![0,6].include?(d.wday)}
    end
    return weekdays
  end

To be clear, this method counts the number of weekdays EXCLUDING the end date so that for example:

需要说明的是,此方法计算工作日的数量,排除结束日期,例如:

  • 0 = number of weekdays between Monday and Monday
  • 0 =星期一和星期一之间的工作日数
  • 1 = number of weekdays between Friday and Saturday
  • 1 =星期五和星期六之间的工作日数
  • 1 = number of weekdays between Friday and Monday
  • 1 =星期五和星期一之间的工作日数
  • 0 = number of weekdays between Sunday and Monday
  • 0 =星期日和星期一之间的工作日数
  • 1 = Number of weekdays between Wednesday and Thursday
  • 1 =星期三和星期四之间的工作日数

#8


0  

Simple script to calculate total number of working days

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

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}

#1


26  

Take a look at business_time. It can be used for the second half of what you're asking.

看看business_time。它可以用于你要求的后半部分。

e.g.

例如

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

Unfortunately, from the notes it appears the author is reluctant to add something like business_duration_between which would cover the first half of your question.

不幸的是,从笔记看来,作者似乎不愿意添加像business_duration_之类的东西来覆盖你问题的前半部分。

Update

更新

Below is a method to count the business days between two dates. You can fine tune this to handle the cases that Tipx mentions in the way that you would like.

以下是计算两个日期之间的工作日的方法。您可以对此进行微调,以处理Tipx以您希望的方式提及的情况。

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

#2


15  

We used to use the algorithm suggested in the mikej's answer and discovered that calculating 25,000 ranges of several years each takes 340 seconds.

我们过去常常使用mikej的答案中建议的算法,并发现计算25,000个数年的范围每个需要340秒。

Here's another algorithm with asymptotic complexity O(1). It does the same calculations in 0.41 seconds.

这是另一种渐近复杂度为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

#3


6  

business_time has all the functionallity you want.

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

From the readme:

从自述文件:

#you can also calculate business duration between two dates

#you还可以计算两个日期之间的业务持续时间

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

Adding business days to a given date:

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

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

#4


1  

Take a look at Workpattern. It alows you to specify working and resting periods and can add/subtract durations to/from a date as well as calculate the minutes between two dates.

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

You can set up workpatterns for different scenarios such as mon-fri working or sun-thu and you can have holidays and whole or part days.

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

I wrote this as away to learn Ruby. Still need to make it more Ruby-ish.

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

#5


1  

Here is my (non gem and non holiday) weekday count example:

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

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

#6


0  

Based on @mikej's answer. But this also takes into account holidays, and returns a fraction of a day (up to the hour accurancy):

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

This uses the holidays and business_time gems.

这使用假日和business_time宝石。

#7


0  

As a different person pointed out, the winning solution is really easy to understand but has a problem. It takes longer to run as the distance between the two dates being compared gets longer. That's kind of an ugly problem.

正如一位不同的人所指出的那样,获胜的解决方案非常容易理解,但却存在问题。由于比较的两个日期之间的距离变长,因此运行时间更长。这是一个丑陋的问题。

The below code is hopefully almost as simple to understand but takes roughly the same amount of time run regardless of the number of days between the two dates. It takes advantage of the fact that each full 7 day week will have 5 working days in it.

希望下面的代码几乎同样易于理解,但无论两个日期之间的天数如何,都需要大致相同的时间。它充分利用了每个完整的7天工作周将有5个工作日的事实。

require 'date'

  def weekdays_between(earlier_date,later_date)
    days_diff = (later_date - earlier_date).to_i
    weekdays = 0
    if days_diff >= 7
      whole_weeks = (days_diff/7).to_i
      later_date -= whole_weeks*7  
      weekdays += whole_weeks*5
    end
    if later_date > earlier_date
      dates_between = earlier_date..(later_date-1)
      weekdays += dates_between.count{|d| ![0,6].include?(d.wday)}
    end
    return weekdays
  end

To be clear, this method counts the number of weekdays EXCLUDING the end date so that for example:

需要说明的是,此方法计算工作日的数量,排除结束日期,例如:

  • 0 = number of weekdays between Monday and Monday
  • 0 =星期一和星期一之间的工作日数
  • 1 = number of weekdays between Friday and Saturday
  • 1 =星期五和星期六之间的工作日数
  • 1 = number of weekdays between Friday and Monday
  • 1 =星期五和星期一之间的工作日数
  • 0 = number of weekdays between Sunday and Monday
  • 0 =星期日和星期一之间的工作日数
  • 1 = Number of weekdays between Wednesday and Thursday
  • 1 =星期三和星期四之间的工作日数

#8


0  

Simple script to calculate total number of working days

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

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}