2010-10-26 68 views
16

Necesito calcular el número de días hábiles entre dos fechas. ¿Cómo puedo sacar eso usando Ruby (o Rails ... si hay ayudantes específicos de Rails).Calcular el número de días hábiles entre dos días

Del mismo modo, me gustaría poder agregar días hábiles a una fecha determinada.

Entonces, si una fecha cae un jueves y agregué 3 días hábiles, volvería el próximo martes.

+0

Pensaría en algunas formas de hacerlo, pero debe decir qué comportamiento desea cuando el "día de referencia" no sea un día hábil (¿Es sábado-> lunes 0 día o 1 día? ¿Es jueves-> sábado 1 o 2 días?) – Tipx

Respuesta

26

Eche un vistazo a business_time. Se puede usar para la segunda mitad de lo que estás preguntando.

p. Ej.

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

Por desgracia, desde las notas parece que el autor es reacio a añadir algo como business_duration_between que cubriría la primera mitad de su pregunta.

actualización

A continuación se muestra un método para contar los días laborables entre dos fechas. Puede ajustar esto para manejar los casos que Tipx menciona de la manera que le gustaría.

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 
+1

El combo de business_time más su fragmento es perfecto. – Shpigford

+1

Gran joya, gracias ... – plang

1

Eche un vistazo a Workpattern. Le permite especificar períodos de trabajo y descanso y puede agregar/restar duraciones a/desde una fecha, así como calcular los minutos entre dos fechas.

Puede configurar patrones de trabajo para diferentes escenarios, como el lunes a viernes o el domingo y puede tener vacaciones y días completos o parciales.

Escribí esto como lejos para aprender Ruby. Todavía necesito hacerlo más Ruby-ish.

0

Basado en la respuesta de @ mikej. Pero esto también tiene en cuenta los días de fiesta, y devuelve una fracción de un día (hasta la Precisión de hora):

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 

Este utiliza los holidays y business_time gemas.

6

business_time tiene toda la funcionalidad que desee.

Desde el readme:

#you También se puede calcular la duración de negocios entre dos fechas

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

Adición días hábiles para una fecha determinada:

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

Utilizamos para usar el algoritmo sugerido en el mikej's answer y descubierto t El cálculo de 25,000 rangos de varios años toma 340 segundos.

Aquí hay otro algoritmo con complejidad asintótica O (1). Hace los mismos cálculos en 0.41 segundos.

# 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 
0

Como señaló una persona diferente, la solución ganadora es realmente fácil de entender, pero tiene un problema. Se tarda más tiempo en ejecutarse a medida que la distancia entre las dos fechas que se comparan se hace más larga. Ese es un problema feo.

El código siguiente es, con suerte, casi tan simple de entender, pero lleva aproximadamente la misma cantidad de tiempo de ejecución, independientemente del número de días entre las dos fechas. Aprovecha el hecho de que cada semana completa de 7 días tendrá 5 días hábiles.

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 

Para ser claros, este método cuenta el número de días de la semana con exclusión de la fecha de finalización de modo que por ejemplo:

  • 0 = número de días de semana entre Lunes y Lunes
  • 1 = número de días de la semana entre el viernes y sábado
  • 1 = número de días de la semana entre el viernes y el lunes
  • 0 = número de días laborables entre domingo y lunes
  • 1 = Número de días laborables entre miércoles y jueves
0

script sencillo para calcular el número total de días de trabajo

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

Aquí es de lunes a viernes mi (no joya y no de vacaciones) ejemplo cuentan:

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 
Cuestiones relacionadas