Bug horario

En esta página se describe un bug que se mantuvo oculto en la aplicación de mi PFC durante algunos meses, ya que no era especialmente molesto.

El problema observado es que al pedirle a la herramienta que distribuyese un número de horas en un espacio de tiempo (como en el ejemplo de la derecha), había ocasiones en las que no se distribuían todas las horas. El error pasó desapercibido porque, como se ve en la segunda tabla de la derecha, solamente ocurría alrededor de un 5% de las veces, esto es, una de cada veinte.

Dado que el algoritmo que distribuye las horas redondea al alza a múltiplos de cinco cuando es posible y a que tiene en cuenta fines de semana y festivos, no era fácil hallar el error directamente en el código. A continuación, la función que calcula el número de días laborables entre dos fechas, que es la parte que contiene el error. ¿Puedes encontrarlo?

function num_laborables($f_ini,$f_fin) {
    $fecha = $f_ini;
    $num = 0;
    while($fecha <= $f_fin) {
        $ds = date('w', $fecha);
        $dm = date('j', $fecha);
        $mm = date('n', $fecha); if ($ds > 0 && $ds < 6 && !fiesta($dm,$mm)){             $num++;         }         $fecha += 86400;     }     return $num; }

Dado que no hallaba el error, escribí el script adjunto al final de esta página (ejecutable desde aquí), que genera fechas automáticamente y comprueba si se distribuyen las horas completamente. Se encontró que los errores se producían cuando la exigencia de horas era máxima: el número de horas a distribuir se aproximaba al número máximo de horas que se podían ejecutar en el periodo asumiendo un calendario de 8 horas diarias.

El siguiente es un ejemplo generado automáticamente que daba error:

  • Fecha de inicio: 15/12/2011
  • Fecha de fin: 11/04/2012
  • Horas límite: 2368 (296 días laborables)
  • Duración en horas: 2368 horas
  • Horas por día: 8
  • Horas asignadas: 2360
  • ¿Éxito?: ¡No!
  • Diferencia: -8
Tras probar varios valores que daban error en la herramienta real, se vio que los problemas se daban siempre en los meses de marzo y octubre. ¿Y qué sucede en marzo y octubre...?

En efecto, el cambio de hora. El número de días laborables se contaba desde la fecha de inicio y se pasaba al día siguiente sumando 86400 segundos (24horas × 60 minutos × 60 segundos). Sin embargo, los días del cambio de hora NO tienen 24 horas, y PHP lo sabe. La solución, pasar al día siguiente de una de estas dos maneras:

$fecha = 
    mktime(0, 0, 0,         date('m', $fecha),
        date('d', $fecha) + 1,
        date('Y', $fecha)
    );
$fecha = 
    strtotime('+1 day', $fecha);

Ejemplo de uso:

El algoritmo debe repartir 700 horas de trabajo de un empleado entre el 16/01/2012 y el 21/09/2012. En el reparto se tienen en cuenta el número de días laborables reales de cada mes (incluyendo festivos nacionales). El reparto automático es el siguiente:

 Horas
Enero50
Febrero85
Marzo90
Abril85
Mayo90
Junio85
Julio90
 Agosto90
 Septiembre35



Datos obtenidos ejecutando el script adjunto:

Prueba nIteracionesErrores% Errores
15000%
25048%
35012%
45036%
55012%
65048%
75012%
850510%
95036%
105036%
1150510%
125024%
135000%
145048%
155024%
165024%
175048%
185012%
195000%
205036%
215024%
225012%
235000%
245024%
255036%
265024%
275012%
285024%
295024%
305048%
Total150067 4.667% 



ċ
script_prueba_distribucion.php
(4k)
Javier Cejudo Goñi,
11 jun. 2011 14:48
Comments