2008-09-26 37 views
6

Dadas dos fechas. Cuál es la mejor manera de calcular el número de horas de trabajo entre ellos. Teniendo en cuenta el horario de trabajo son lunes 8 - 5.30, y martes-viernes 8.30 - 5.30, y que potencialmente cualquier día podría ser un día festivo.Cálculo de las horas de trabajo transcurridas entre 2 datetime

Este es mi esfuerzo, parece horriblemente ineficiente, pero en términos de número de iteraciones y que el método IsWorkingDay golpea el DB para ver si ese datetime es un día festivo.

¿Alguien puede sugerir alguna optimización o alternativa.

public decimal ElapsedWorkingHours(DateTime start, DateTime finish) 
     { 

      decimal counter = 0; 

      while (start.CompareTo(finish) <= 0) 
      { 
       if (IsWorkingDay(start) && IsOfficeHours(start)) 
       { 
        start = start.AddMinutes(1); 
        counter++; 
       } 
       else 
       { 
        start = start.AddMinutes(1); 
       } 
      } 

      decimal hours; 

      if (counter != 0) 
      { 
       hours = counter/60; 
      } 

      return hours; 
     } 
+0

¿Cuántos días festivos tienes? ; P – leppie

Respuesta

1

especialmente teniendo en cuenta el método IsWorkingDay golpea la base de datos para ver si ese día es festivo

Si el problema es el número de consultas en lugar de la cantidad de datos, consultar el trabajo datos del día de la base de datos para todo el rango de días que necesita al comienzo en lugar de consultar en cada iteración de bucle.

+0

Claro que entiendo lo que dices conceptualmente. Pero implementar eso limpiamente ese es el problema. Y creo que incluso si ignoraste las consultas, mi enfoque sigue siendo pobre. – Dan

+0

En lugar de IsWorkingDay() que acepta un día, implemente un método HolidayCount() que acepte un intervalo de fechas y devuelva el número de días festivos en el rango. –

+0

Si hay algo que hace que la solución sea menos eficiente en cuanto al código, ya que tendría que contar incrementalmente en más días. – Dan

3

Antes de comenzar a optimizarlo, hágase dos preguntas.

a) ¿Funciona?

b) ¿Es demasiado lento?

Solo si la respuesta a ambas preguntas es "sí", ¿está listo para comenzar a optimizar?

Aparte de eso

  • sólo tendrá que preocuparse de minutos y horas en el día de inicio y el día final. días intermedios, obviamente, ser un total de 9/9,5 horas, a menos que sean días festivos o fines de semana
  • No hay necesidad de comprobar un día de fin de semana para ver si es un día de fiesta

Así es como yo lo haría

// Normalise start and end  
while start.day is weekend or holiday, start.day++, start.time = 0.00am 
    if start.day is monday, 
     start.time = max(start.time, 8am) 
    else 
     start.time = max(start.time, 8.30am) 
while end.day is weekend or holiday, end.day--, end.time = 11.59pm 
end.time = min(end.time, 5.30pm) 

// Now we've normalised, is there any time left?  
if start > end 
    return 0 

// Calculate time in first day  
timediff = 5.30pm - start.time 
day = start.day + 1 
// Add time on all intervening days 
while(day < end.day) 
    // returns 9 or 9.30hrs or 0 as appropriate, could be optimised to grab all records 
    // from the database in 1 or 2 hits, by counting all intervening mondays, and all 
    // intervening tue-fris (non-holidays) 
    timediff += duration(day) 

// Add time on last day 
timediff += end.time - 08.30am 
if end.day is Monday then 
    timediff += end.time - 08.00am 
else 
    timediff += end.time - 08.30am 

return timediff 

Se podría hacer algo como SELECT COUNT (Día) de vacaciones donde vacaciones entre @Start y @end GRUPO pOR DÍA

para contar el número de días festivos que caen en lunes, martes, miércoles, y así sucesivamente. Probablemente sea una manera de hacer que SQL cuente solo los lunes y no los lunes, aunque no se puede pensar en nada en este momento.

+0

Sí y sí, esto es un poco evitar mi pregunta. – Dan

+1

depende de lo que esté haciendo. Si funciona * lo suficientemente rápido *, no tiene sentido optimizarlo aún más, al menos no desde una perspectiva empresarial. –

+0

@CynicalTyler Es por eso que estoy haciendo esta pregunta, por favor sugiera una mejor manera de lo contrario, perder su tiempo y el mío. – Dan

1

Eche un vistazo a la clase TimeSpan. Eso le dará las horas entre 2 veces.

Una sola llamada de base de datos también puede obtener las vacaciones entre sus dos veces; algo similar a:

SELECT COUNT(*) FROM HOLIDAY WHERE HOLIDAY BETWEEN @Start AND @End 

Multiplicar ese número por 8 y restarlo del total de horas.

-Ian

EDITAR: En respuesta a continuación, si usted está de vacaciones no son de un número constante de horas.puede mantener un HolidayStart y un HolidayEnd Tiempo en su base de datos y simplemente devolverlos desde la llamada a la base de datos también. Haga un recuento de horas similar al método que elija para la rutina principal.

+0

1. Esa consulta no ayuda a la implementación 2. No todos los días son 8 largos y el datestime de inicio y final puede ser a mitad del día – Dan

+0

En defensa de Ian, con respecto a 2, su pregunta dice "Dadas dos fechas" no dos fechas. – Powerlord

+0

@Bemrose gracias por la corrección, iv lo cambió. – Dan

0

Basándose en lo que @OregonGhost dijo, en lugar de usar una función IsWorkingDay() en acepta un día y devuelve un booleano, tiene una función HolidayCount() que acepta un rango y devuelve un número entero que indica el número de días festivos en el rango . El truco aquí es que si tiene que lidiar con una fecha parcial para sus días de inicio y finalización de boundry, es posible que aún necesite determinar si esas fechas son en sí mismas vacaciones. Pero aun así, puede usar el nuevo método para asegurarse de que necesita a lo sumo tres llamadas a DB.

0

Probar algo en este sentido:

TimeSpan = TimeSpan Between Date1 And Date2 
cntDays = TimeSpan.Days 
cntNumberMondays = Iterate Between Date1 And Date2 Counting Mondays 
cntdays = cntdays - cntnumbermondays 
NumHolidays = DBCall To Get # Holidays BETWEEN Date1 AND Date2 
Cntdays = cntdays - numholidays 
numberhours = ((decimal)cntdays * NumberHoursInWorkingDay)+((decimal)cntNumberMondays * NumberHoursInMondayWorkDay) 
+0

Esto no tiene en cuenta las vacaciones los lunes. –

+0

O fracciones de días. O si las fechas están fuera del horario de oficina – Dan

1

También existe la solución recursiva. No necesariamente eficiente, pero un montón de diversión:

public decimal ElapseddWorkingHours(DateTime start, DateTime finish) 
{ 
    if (start.Date == finish.Date) 
     return (finish - start).TotalHours; 

    if (IsWorkingDay(start.Date)) 
     return ElapsedWorkingHours(start, new DateTime(start.Year, start.Month, start.Day, 17, 30, 0)) 
      + ElapsedWorkingHours(start.Date.AddDays(1).AddHours(DateStartTime(start.Date.AddDays(1)), finish); 
    else 
     return ElapsedWorkingHours(start.Date.AddDays(1), finish); 
} 
0

La forma más eficiente de hacer esto es calcular la diferencia de tiempo total, luego restar el tiempo que es un fin de semana o vacaciones. Hay bastantes casos extremos a considerar, pero puede simplificar eso tomando el primer y último día del rango y calculándolos por separado.

El método COUNT (*) sugerido por Ian Jacobs parece una buena forma de contar las vacaciones. Cualquier cosa que use, solo manejará los días completos, necesita cubrir las fechas de inicio y finalización por separado.

Contando los días de fin de semana es fácil; si tiene un día de la semana de función (fecha) que devuelve 0 para el lunes a 6 para el domingo, que se parece a esto:

saturdays = ((finish - start) + Weekday(start) + 2)/7; 
sundays = ((finish - start) + Weekday(start) + 1)/7; 

Nota: (acabado - empezar) no debe ser tomada literalmente, reemplazarlo con algo que calcula el lapso de tiempo en días.

0

Utilice la consulta de @ Ian para verificar las fechas para saber qué días no son días laborables. Luego, haga algunos cálculos para saber si su hora de inicio o hora de finalización cae en un día inhábil y reste la diferencia.

Por lo tanto, si el inicio es el mediodía del sábado y finaliza el lunes al mediodía, la consulta debe devolverle 2 días, a partir de los cuales calcula 48 horas (2 x 24). Si su consulta en IsWorkingDay (inicio) devuelve falso, reste 24 desde la hora del comienzo hasta la medianoche, lo que le daría 12 horas o 36 horas de horas no laborables totales.

Ahora, si sus horas de oficina son las mismas para todos los días, usted hace algo similar. Si sus horas de oficina están un poco dispersas, tendrá más problemas.

Idealmente, haga una sola consulta en la base de datos que le proporcione todas las horas de oficina entre las dos veces (o incluso las fechas). Luego haz las matemáticas localmente desde ese conjunto.

-1
Dim totalMinutes As Integer = 0 

For minute As Integer = 0 To DateDiff(DateInterval.Minute, contextInParameter1, contextInParameter2) 
    Dim d As Date = contextInParameter1.AddMinutes(minute) 
    If d.DayOfWeek <= DayOfWeek.Friday AndAlso _ 
     d.DayOfWeek >= DayOfWeek.Monday AndAlso _ 
     d.Hour >= 8 AndAlso _ 
     d.Hour <= 17 Then 
     totalMinutes += 1 
    Else 
     Dim test = "" 
    End If 
Next minute 

Dim totalHours = totalMinutes/60 

Piece of Cake!

¡Salud!

Cuestiones relacionadas