Verificando números de dias úteis no mês, se determinada data é feriado e dias úteis em intervalos de datas.

É muito comum verificar, se determinada data vai cair em feriado e assim calcularmos quantidade de dias uteis etc.

Existem datas que são feriados fixos, e estas são relativamente simples de trabalhar. Porem tem os feirados religiosos e estes alteram de acordo com o ano.

Quando precisamos calcular o números de dias úteis em um mês podemos nos deparar com o seguinte problema: como saber o número de dias úteis em Março de 2017?.

Pensando neste problema, montei a classe exemplificada abaixo. Ela contém duas funções chamadas NumberWorkDaysInMonth e IsBrazilNationalHoliday que realiza estes cálculos.

Explicacão das funções

IsBrazilNationalHoliday: Apenas retorna verdadeiro se a data passada for feriada, caso contrário retorna falso.
NumberWorkDaysInMonth: Percorre todos os dias do mês informado e verifica se são feriados ou fim de semana. Devolve o total de dias úteis.

Código em C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class Test
{
	public static void Main()
	{
		var holiday = new Holiday();
		Console.WriteLine("Dias uteis neste mes");
		Console.WriteLine(holiday.NumberWorkDaysInMonth(2014, 7));
		Console.WriteLine("");
		Console.WriteLine("Verifica se é feriado no brasil");
		Console.WriteLine("25 de Dezembro de 2014 é feriado: " + holiday.IsBrazilNationalHoliday(new DateTime(2014,12,25)));
		Console.WriteLine("21 de Abril de 2014 é feriado: " + holiday.IsBrazilNationalHoliday(new DateTime(2014,04,21)));
		Console.WriteLine("09 de Julho de 2014 é feriado: " + holiday.IsBrazilNationalHoliday(new DateTime(2014,07,09)));
/*-> saida do programa acima
		Dias uteis neste mes
		23

		Verifica se é feriado no brasil
		25 de Dezembro de 2014 é feriado: True
		21 de Abril de 2014 é feriado: True
		09 de Julho de 2014 é feriado: False
		*/
	}
}

public class Holiday{

	/// <summary>
    /// Calcula o numero de dias uteis no mes.
    /// </summary>
    /// <param name="year">Valor inteiro com o ANO</param>
    /// <param name="month">Valor inteiro com o MES</param>
    /// <returns>Valor inteiro com o numero de dias uteis do mes</returns>
    public int NumberWorkDaysInMonth(int year, int month)
    {
        int diasNoMes = DateTime.DaysInMonth(year, month);
        int diasUteisMes = 0;
        for (int i = 1; i <= diasNoMes; i++)
        {
            if (!IsBrazilNationalHoliday(new DateTime(year, month, i))
                && (int)new DateTime(year, month, i).DayOfWeek != 0
                && (int)new DateTime(year, month, i).DayOfWeek != 6)
            {
                diasUteisMes++;
            }
        }
        return diasUteisMes;
    }

    /// <summary>
    /// Formula desenvolvida por Karl Friedrich Gauss
    /// Estabelecendo o método gaussiano de cálculo manual válido de 1900 a 2099
    /// OBS: Carnaval ainda eh considerado ponto facultativo no Brasil e
    ///     por este motivo foi comentado da lista de feriados
    /// </summary>
    /// <param name="checkDate">DateTime da data que sera verificada</param>
    /// <returns>True em caso de feriado ou Falso caso não seja feriado</returns>
    public Boolean IsBrazilNationalHoliday(DateTime checkDate)
    {
        DateTime dateToBeChecked = new DateTime(checkDate.Year, checkDate.Month, checkDate.Day);
        int anoCheckDate = checkDate.Year;
        int a = (int)anoCheckDate % 4;
        int b = (int)anoCheckDate % 7;
        int c = (int)anoCheckDate % 19;
        int d = (int)((19 * c) + 24) % 30;
        int e = (int)((2 * a) + (4 * b) + (6 * d) + 5) % 7;
        int diaPascoa = 22 + d + e;
        int mesPascoa = 3;
        if (diaPascoa > 31)
        {
            diaPascoa = d + e - 9;
            mesPascoa = 4;
        }
        List<String> feriados = new List<String>();
        feriados.Add(new DateTime(anoCheckDate, mesPascoa, diaPascoa).ToString()); //Pascoa
        feriados.Add(new DateTime(anoCheckDate, mesPascoa, diaPascoa).AddDays(-2).ToString()); //Sexta Feira da Paixão
        feriados.Add(new DateTime(anoCheckDate, mesPascoa, diaPascoa).AddDays(60).ToString()); //Corpus Cristi
        //feriados.Add(new DateTime(checkDate.Year, mesPascoa, diaPascoa).AddDays(-47).ToString()); //Carnaval
        feriados.Add(new DateTime(anoCheckDate, 1, 1).ToString()); //Dia de Ano Novo (Confraternização Universal)
        feriados.Add(new DateTime(anoCheckDate, 4, 21).ToString()); //Dia de Tiradentes
        feriados.Add(new DateTime(anoCheckDate, 5, 1).ToString()); //Dia do Trabalho
        feriados.Add(new DateTime(anoCheckDate, 9, 7).ToString()); //Dia da Independência
        feriados.Add(new DateTime(anoCheckDate, 10, 12).ToString()); //Dia da Padroeira do Brasil
        feriados.Add(new DateTime(anoCheckDate, 11, 2).ToString()); //Dia de Finados
        feriados.Add(new DateTime(anoCheckDate, 11, 15).ToString()); //Data da Proclamação da República
        feriados.Add(new DateTime(anoCheckDate, 12, 25).ToString()); //Natal
        return feriados.Contains(dateToBeChecked.ToString());
    }
}

Reescrevendo em PHP

O algoritmo foi reescrito usando PHP. Nesta mudança foram realizadas algumas melhorias. Por exemplo calcular o número de dias úteis entre duas datas.

Outras melhorias foram para deixar o código mais eficiente e legível. Ele esta pronto e pode ser usado como uma referencia completa para seu código.

Código PHP – testado na versão 5.3 +

<?php

echo '<pre>';
$holiday = new Holidays();
echo 'Number of working day on a specific month: 2017-12' . PHP_EOL;
echo $holiday->numberWorkDaysInMonth(2017, 12) . PHP_EOL; // => 20
echo 'Number of working day between two dates: 2017-10-01 e 2017-12-31' . PHP_EOL;
echo $holiday->mumberWorkDaysBetweenDates('2017-10-05', '2017-12-31') . PHP_EOL; // => 58
echo 'Number of working day between two dates: 2018-05-01 e 2018-05-31' . PHP_EOL;
echo $holiday->mumberWorkDaysBetweenDates('2018-05-01', '2018-05-31') . PHP_EOL; // => 21
echo 'Number of working day between two dates: 2017-12-15 e 2018-01-15' . PHP_EOL;
echo $holiday->mumberWorkDaysBetweenDates('2017-12-15', '2018-01-15'); // => 20

class Holidays
{

    private $holidaysBrazil = array();

    /**
     * Calculates de number of working days in a month
     */
    public function numberWorkDaysInMonth($year, $month)
    {
        if (function_exists('cal_days_in_month')) {
            $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
        } else {
            $daysInMonth = $this->daysInMonth($month, $year);
        }
        $workingDay = 0;
        for ($i = 1; $i <= $daysInMonth; $i++) {
            $dateToCheck = new DateTime($year . '-' . $month . '-' . $i);
            if (!$this->isBrazilNationalHoliday($dateToCheck) && (int) $dateToCheck->format('w') != 0 && (int) $dateToCheck->format('w') != 6) {
                $workingDay++;
            }
        }
        return $workingDay;
    }

    /**
     * Calculates de number of working days between two dates
     */
    public function mumberWorkDaysBetweenDates($dateBegin, $dateEnd)
    {
        $daysNumber = $this->dateDiff($dateBegin, $dateEnd);
        $workingDay = 0;
        $datetimeBegin = new DateTime($dateBegin);
        for ($i = 1; $i <= $daysNumber; $i++) {
            if (!$this->isBrazilNationalHoliday($datetimeBegin) && (int) $datetimeBegin->format('w') != 0 && (int) $datetimeBegin->format('w') != 6) {
                $workingDay++;
            }
            $datetimeBegin->modify('+1 day');
        }
        return $workingDay;
    }

    /**
     * Used if "calendar extension" for PHP is not compiled 
     */
    private function daysInMonth($month, $year)
    {
        return $month == 2 ? ($year % 4 ? 28 : ($year % 100 ? 29 : ($year % 400 ? 28 : 29))) : (($month - 1) % 7 % 2 ? 30 : 31);
    }

    /**
     * Genrates de list os holidays on Brazil
     * Note: Carnival is still considered an optional point in Brazil and for this reason it was commented on the list of holidays
     */
    private function generateHolidaysList($checkDate)
    {
        $yearCheckDate = $checkDate->format('Y');
        $a = (int) $yearCheckDate % 4;
        $b = (int) $yearCheckDate % 7;
        $c = (int) $yearCheckDate % 19;
        $d = (int) ((19 * $c) + 24) % 30;
        $e = (int) ((2 * $a) + (4 * $b) + (6 * $d) + 5) % 7;
        $easterDay = 22 + $d + $e;
        $easterMonth = 3;
        if ($easterDay > 31) {
            $easterDay = $d + $e - 9;
            $easterMonth = 4;
        }
        $holidays = array();
        $easterFullDate = new DateTime($yearCheckDate . '-' . $easterMonth . '-' . $easterDay);
        $christPassion = new DateTime($yearCheckDate . '-' . $easterMonth . '-' . $easterDay);
        $christPassion->modify('-2 days');
        $corpusCristi = new DateTime($yearCheckDate . '-' . $easterMonth . '-' . $easterDay);
        $corpusCristi->modify('+60 days');
//        $carnival = new DateTime($yearCheckDate . '-' . $easterMonth . '-' . $easterDay);
//        $carnival->modify('-47 days');
        $holidays[] = $easterFullDate;
        $holidays[] = $christPassion;
        $holidays[] = $corpusCristi;
//        $holidays[] = $carnival;
        $holidays[] = new DateTime($yearCheckDate . '-01-01'); // => News Years
        $holidays[] = new DateTime($yearCheckDate . '-04-21'); // => Tiradentes
        $holidays[] = new DateTime($yearCheckDate . '-05-01'); // => Work Day
        $holidays[] = new DateTime($yearCheckDate . '-09-07'); // => Independence Day
        $holidays[] = new DateTime($yearCheckDate . '-10-12'); // => Patroness
        $holidays[] = new DateTime($yearCheckDate . '-11-01'); // => Deads
        $holidays[] = new DateTime($yearCheckDate . '-11-15'); // => Proclamation Republic
        $holidays[] = new DateTime($yearCheckDate . '-12-25'); // => Christmas
        foreach ($holidays as $key => $holiday) {
            $holidays[$key] = $holiday->format('Y-m-d');
        }
        $this->holidaysBrazil[$yearCheckDate] = $holidays;
    }

    /**
     * Check is day is holiday
     */
    private function isBrazilNationalHoliday($checkDate)
    {
        $yearCheckDate = $checkDate->format('Y');
        // => add the year on the holidays array
        if (empty($this->holidaysBrazil[$yearCheckDate])) {
            $this->generateHolidaysList($checkDate);
        }
        return in_array($checkDate->format('Y-m-d'), $this->holidaysBrazil[$yearCheckDate]);
    }

    /**
     * Proposed solution to solve a problem that occurs in the "diff" of PHP V5.3 in Win
     * Fonte: http://acme-tech.net/blog/php-datetimediff-returns-6015/
     */
    private function dateDiff($dt1, $dt2, $timeZone = 'GMT')
    {
        $tZone = new DateTimeZone($timeZone);
        $dt1 = new DateTime($dt1, $tZone);
        $dt2 = new DateTime($dt2, $tZone);
        $ts1 = $dt1->format('Y-m-d');
        $ts2 = $dt2->format('Y-m-d');
        $diff = abs(strtotime($ts1) - strtotime($ts2));
        $diff /= 3600 * 24;
        return $diff + 1; // => includes da day on calculation
    }

}

Qualquer dúvida ou dicas, entre em contato: leandrolt@gmail.com

Leave a Reply

Your email address will not be published. Required fields are marked *