Рассчитать количество рабочих дней между двумя днями
Мне нужно рассчитать количество рабочих дней между двумя датами. Как я могу вытащить это с помощью Ruby (или Rails... если есть помощники, специфичные для Rails).
Кроме того, я хотел бы иметь возможность добавлять рабочие дни к указанной дате.
Итак, если дата упала в четверг, и я добавил 3 рабочих дня, она вернется в следующий вторник.
Ответы
Ответ 1
Посмотрите на 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
Это также может быть точно настроено для обработки случаев, которые Типкс упоминает так, как вам хотелось бы.
Ответ 2
Мы использовали алгоритм, предложенный в ответе 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 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
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
Ответ 4
Взгляните на Workpattern. Он позволяет вам указать периоды работы и отдыха, а также добавить/вычесть длительности в/из даты, а также рассчитать минуты между двумя датами.
Вы можете настроить рабочие шаблоны для разных сценариев, таких как mon-fri working или sun-thu, и у вас могут быть праздники и целые или неполные дни.
Я написал это как прочь, чтобы узнать Ruby. Еще нужно сделать его более рубиновым.
Ответ 5
Вот пример подсчета числа дней недели (non gem and non holiday):
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
Основываясь на ответе @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
Здесь используется holidays и business_time драгоценные камни.
Ответ 7
Есть две проблемы с наиболее популярными решениями, перечисленными выше:
- Они включают в себя циклы для подсчета каждого дня между каждой датой (это означает, что производительность ухудшается по мере удаления дат).
- Им неясно, считают ли они с начала дня или до конца. Если считать с утра, то между пятницей и субботой есть один будний день. Если вы рассчитываете от ночи, между пятницей и субботой будет ноль рабочих дней.
После этого я предлагаю это решение, которое решает обе проблемы. В приведенной ниже таблице берется контрольная дата и другая дата, а также вычисляется количество дней недели между ними (возвращая отрицательное число, если другое до контрольной даты). Аргумент 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
Ответ 8
Простой script для расчета общего количества рабочих дней
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}