Рассчитать количество рабочих дней между двумя днями - PullRequest
19 голосов
/ 27 октября 2010

Мне нужно рассчитать количество рабочих дней между двумя датами. Как я могу осуществить это, используя Ruby (или Rails ..., если есть Rails-специфичные помощники).

Кроме того, я бы хотел добавить рабочие дни к определенной дате.

Так что, если дата упала в четверг, и я добавил 3 рабочих дня, она вернется в следующий вторник.

Ответы [ 8 ]

30 голосов
/ 27 октября 2010

Взгляните на 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 упоминает так, как вы хотели бы.

17 голосов
/ 15 июля 2014

Мы использовали алгоритм, предложенный в ответе mikej , и обнаружили, что для расчета 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
6 голосов
/ 02 января 2014

business_time имеет все необходимые функции.

Из файла readme:

# вы также можете рассчитать продолжительность бизнеса между двумя датами

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
1 голос
/ 29 сентября 2016

Вот мой пример подсчета дней недели (без драгоценных камней и без выходных):

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
1 голос
/ 30 января 2013

Взгляните на Рабочий шаблон . Он позволяет вам указывать периоды работы и отдыха, а также добавлять / вычитать длительности до / от даты, а также вычислять минуты между двумя датами.

Вы можете настроить рабочие шаблоны для различных сценариев, таких как пн-пт или вс-чт, и у вас могут быть праздники, а также целые или неполные дни.

Я написал это как прочь, чтобы выучить Ruby. Еще нужно сделать его более рубиновым.

0 голосов
/ 04 января 2016

Простой скрипт для подсчета общего количества рабочих дней

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}
0 голосов
/ 08 декабря 2015

Есть две проблемы с наиболее популярными решениями, перечисленными выше:

  1. Они включают в себя циклы для подсчета каждого дня между каждой датой (это означает, что производительность ухудшается по мере удаления от даты.
  2. Им неясно, считают ли они с начала дня или до конца. Если считать с утра, то между пятницей и субботой есть один будний день. Если вы рассчитываете от ночи, между пятницей и субботой будет ноль рабочих дней.

После этого я предлагаю решение, которое решает обе проблемы. В приведенной ниже таблице берется контрольная дата и другая дата, а также вычисляется количество дней недели между ними (возвращая отрицательное число, если другое до контрольной даты). Аргумент 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
0 голосов
/ 10 июля 2013

Основано на ответе @ 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

При этом используются праздники и business_time драгоценные камни.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...