;

terça-feira, 11 de janeiro de 2011

Como adicionar horas úteis a uma data

O último artigo ensinava como fazer para somar dias úteis a uma data, em C# (Como calcular datas considerando apenas dias úteis). Pois bem, eu modifiquei um pouco o método e agora  é possível adicionar horas úteis a uma data (objeto DateTime). A rotina recebe uma data inicial, a quantidade de horas a serem adicionadas e a hora inicial e final do turno de trabalho, retornando a data final com as horas úteis adicionadas. A rotina ficou assim:

private static DateTime AdicionaTempo(DateTime pDataInicial, double pHoras, DateTime pInicioTurno, DateTime pFinalTurno)
{
    DateTime resultado = pDataInicial;
    double horasADescontar = pHoras;
    double horasTrabalhadasPorDia = pFinalTurno.Subtract(pInicioTurno).TotalHours;

    //Se inicio ou fim estiver fora do turno de trabalho
    //Se for antes de começar a trabalhar, incremento as horas
    if (new DateTime(pInicioTurno.Year, pInicioTurno.Month, pInicioTurno.Day, resultado.Hour, resultado.Minute, resultado.Second) < pInicioTurno)
        resultado = new DateTime(resultado.Year, resultado.Month, resultado.Day, pInicioTurno.Hour, pInicioTurno.Minute, pInicioTurno.Second);

    //se for depois de terminar o turno, jogo pro início do dia seguinte
    if (new DateTime(pFinalTurno.Year, pFinalTurno.Month, pFinalTurno.Day, resultado.Hour, resultado.Minute, resultado.Second) > pFinalTurno)
        resultado = new DateTime(resultado.Year, resultado.Month, resultado.Day, pInicioTurno.Hour, pInicioTurno.Minute, pInicioTurno.Second).AddDays(1);

    while (horasADescontar > -1)
    {
        //Se o primeiro dia é sábado, domingo ou feriado, jogo a data para o inicio do próximo dia útil, para começar a contar
        if (horasADescontar == pHoras && (resultado.DayOfWeek == DayOfWeek.Saturday || resultado.DayOfWeek == DayOfWeek.Sunday || feriados.Contains(resultado)))
        {
            DateTime dataAux = resultado.AddDays(1);
            resultado = new DateTime(dataAux.Year, dataAux.Month, dataAux.Day, pInicioTurno.Hour, pInicioTurno.Minute, pInicioTurno.Second);
        }
        //Se é sábado, domingo ou feriado, ando um dia pra frente
        else if (horasADescontar < pHoras && (resultado.DayOfWeek == DayOfWeek.Saturday || resultado.DayOfWeek == DayOfWeek.Sunday || feriados.Contains(resultado)))
        {
            resultado = resultado.AddDays(1);
        }
        //se for dia normal, e quiser adicionar um tempo inferior ao turno de trabalho
        else if (horasADescontar > 0 && horasADescontar < horasTrabalhadasPorDia)
        {
            DateTime dataAux = resultado.AddHours(horasADescontar);
            // se o tempo estourar o final do turno
            if (new DateTime(1900, 1, 1, dataAux.Hour, dataAux.Minute, dataAux.Second) > pFinalTurno)
            {
                double aux = pFinalTurno.Subtract(new DateTime(1900, 1, 1, resultado.Hour, resultado.Minute, resultado.Second)).TotalHours;
                horasADescontar -= aux;
                resultado = new DateTime(resultado.Year, resultado.Month, resultado.Day, pInicioTurno.Hour, pInicioTurno.Minute, pInicioTurno.Second).AddDays(1);
            }
            else
            {
                resultado = resultado.AddHours(horasADescontar);
                horasADescontar = 0;
            }
        }
        //Ou se quiser adicionar um dia útil (X horas trabalhadas = 1 dia útil)
        else if (horasADescontar > 0)
        {
            resultado = resultado.AddDays(1);
            horasADescontar -= horasTrabalhadasPorDia;
        }
        //Se a data final for no fim de semana ou feriado
        else if (horasADescontar == 0)
        {
            while (resultado.DayOfWeek == DayOfWeek.Saturday || resultado.DayOfWeek == DayOfWeek.Sunday || feriados.Contains(resultado))
            {
                resultado = resultado.AddDays(1);
            }
            horasADescontar = -1;
        }
    }

    return resultado;
}

Ficou um pouco mais complexo. Eu acredito que esta rotina ainda não está otimizada. da melhor forma Dá pra melhorar. Mas funciona muito bem e não chega a ser demorada, para a quantidade de operações que faz. A seguir temos um método de uma aplicação console que faz uso desta rotina.

static void Main(string[] args)
{
    DateTime inicio = new DateTime(2011,1,12,7,0,0);
    double tempo = 30;
    Console.Write("Inicio: ");
    Console.WriteLine(inicio.ToLongDateString() +" - " + inicio.ToLongTimeString());
    Console.WriteLine("Tempo: " + tempo.ToString() + " horas");
    Console.Write("Fim: ");
    DateTime dia = AdicionaTempo(inicio, tempo, new DateTime(1900, 1, 1, 8, 0, 0), new DateTime(1900, 1, 1, 18, 0, 0));
    Console.WriteLine(dia.ToLongDateString() + " - " + dia.ToLongTimeString() );
    Console.ReadLine();
}


Apenas pra lembrar. Lista de feriados é uma lista de objetos DateTime, que precisa ser carregada manualmente.

12 comentários:

Anônimo disse...

Convert.ToDateTime("29/11/2011 00:00:00").AddDays(2)
Bom dia estou com um problema com datas
Ao adicionar 2 dias na data 29/11/2011
o resultado é 31/11/2011.
Sabe algo a respeito?

dotDicas disse...

Realmente é muito estranho. Pode ser alguma inconsistência do framework. Para mim dá 1/12 (estou usando o 3.5).
Não tem cara de ser problema de CultureInfo, mas tenta isto:
Convert.ToDateTime("29/11/2011 00:00:00", System.Globalization.CultureInfo.GetCultureInfo("pt-BR")).AddDays(2).
Mesmo q não seja este o problema, eu recomendo informar o CultureInfo nas conversões de data, uma vez que as vezes não temos como saber a configuração de ambiente do cliente.
Espero que ajude.
Abraço.

Anônimo disse...

Valeu pela dica cara, eu alterei as configurações regionais na máquina e funcionou.

Anônimo disse...

Voce tem experiência no SharePoint?
Estou criando um site de reserva de horários de reunioes e agora preciso checar os horários disponíveis.
Na parte de eventos recorrentes o jeito que achei para buscar os dados informados foi pela query retornada por
rdcRecurrenceData.Value:

suFALSE

Mas é uma forma bem trabalhosa de buscar os campos a partir desse texto.
Você sabe algum meio mais prático de buscar esses dados do evento recorrente?

dotDicas disse...

Que bom. Fico feliz em ter ajudado.
Mas ainda assim, recomendo passar a CultureInfo na conversão de datas, conforme falei no comentário anterior. Desta forma vc garante que sua aplicação irá funcionar, independente da configuração regional do sistema operacional da máquina.
Abraço.

dotDicas disse...

Já cheguei a trabalhar com sharepoint, mas muito pouco, e já tm um tempo. Não sou a melhor pessoa para te ajudar com isto.
Tenta entrar em contato com o dono deste blog: http://www.sharepointiando.com.br. Fiz um curso com ele. O cara sabe tudo de sharepoint.

Anônimo disse...

Boa tarde, poderia me ajudar nessa cara?
Para calcular a diferença em meses eu uso esse trecho de código:

TimeSpan diff;
int intDiferenca;
diff = dtfim - dtini;
intDiferenca = diff.Days/30;
Mas dependendo do dia da data esse código pode dar errado.
Por exemplo do dia 01/11/2011 e 31/12/2011
resultaria em 2 meses, sendo que deveria ser 1 mes.
Sabe alguma outra forma de calcular a diferença em meses entre duas datas?
Obrigado

dotDicas disse...

Opa,
De fato, o resultado da tua operação é 2, porque a diferença é de 60 dias (2 meses). O que tu quer fazer é a subtração entre os meses, certo?
Neste caso tu vai ter que criar uma rotina q faze a diferença entre dtfim.Month e dtini.Month, olhando para o ano - se ano diferente (de dezembro para janeiro) pega esta diferença e faz mod 12 (diff % 12).
Flw

Bruno Feitosa Miranda disse...

Boa tarde!

Primeiramente parabéns pelo site, infelizmente não o conheci antes, mas antes tarde do que nunca. rsrs

Estou trabalhando no desenvolvimento de um Gerenciador de chamados, SLA, basicamente ele tem que fazer exatamente o que você ensinou nessa aula. Embora eu já havia desenvolvido uma forma, diferente, mas que calcula essas horas, o seu exemplo esta muito mais otimizado, por isso estou substituindo todas as minhas classes, pela sua.
Agora tenho que criar um tipo um agendamento, em miúdos, fazer uma pausa, nesse SLA, e não estou consigo.

Será que você pode me ajuda?

Obrigado.

dotDicas disse...

Obrigado Bruno. Sou apenas um colega desenvolvedor compartilhando códigos - aliás, já não escrevo há algum tempo. As aulas deixamos para os professores d vdd, hehe.
Escrevi este artigo baseado justamente no cálculo de SLA de um sistema q ajudei a desenvolver. Não sei se vou conseguir te ajudar, mas posta tuas dúvidas aqui, e se conseguir, eu te dou uma força.
Abraço

Bruno Feitosa Miranda disse...

Meu problema esta sendo para criar um algoritmo que faça o agendamento, a regra de negócio, permite que o usuário agende para determinada data o atendimento do chamado, não estou conseguindo cria lo.
Ex: o SLA é de 16 horas, expediente das 9 as 18.
Um chamado que começou em 30/10/2013 9:00:00, e foi agendado em 31/10/2013 11:00:00, para o dia 4/11/2013 13:00:00. Então já foi gasto 11 horas desse SLA, as 5 horas restantes começará a contar novamente, a partir do dia 4/11/2013 13:00:00, com isso o SLA termina nesse mesmo dia, as 18 horas.

Bem, é isso que não consigo fazer. Rsrs
Tomara que você consiga me ajudar.

dotDicas disse...

Bom, tu pode criar um log de eventos (uma tabela de log no banco de dados), para facilitar o controle..
1 - Abre o chamado, salva o registro 1 (PK, data, ID do chamado, status), 30/10/2013 9:00:00, com status aberto.
2 - Sempre q tu agendar o chamado insere mais DOIS registros, neste caso: reg 2, 31/10/2013 11:00:00 com status agendado/pausa e o registro reg 3, 4/11/2013 13:00:00, com o status reaberto.
3- No momento q encerrar o chamado insere mais um registro, com a data de encerramento e com status encerrado (ou cancelado se for o caso).

Desta forma quando vc calcular os tempos gastos, traz os registros com data inferior à atual, calculando os períodos entre os registros com (status aberto ou reaberto) até (agendado ou pausado ou encerrado).

Se vc fizer a consulta antes do dia 04/11 este registro de reabertura não entra na conta do SLA. Se consultar depois, vai contando o SLA até a data da consulta, ou até pausar de novo, ou encerrar aquele chamado.

Espero ter t dado uma luz.
Abraço

Postar um comentário