Pular para o conteúdo principal

Obtendo número de dias úteis entre duas datas com C#

Após algum tempo sem postar artigos sobre C# finalmente achei algo interessante e relativamente simples de fazer sem cair na repetição de artigos já bem documentados. Desta vez veremos como criar um método para calcular o número de dias corridos – considerando os dias entre segunda e sexta-feira e sem contar os feriados – entre duas datas usando a linguagem C#.

Para tornar o post um pouco mais interessante isto será feito sem usar o Visual Studio ou qualquer ferramenta de edição integrada de código e compilação, ou seja, vamos usar um editor de texto para o código e o prompt de comandos para compilar o projeto.

A justificativa para esta abordagem é demonstrar como gerar pequenos programas para resolver problemas pontuais quando não há uma versão do Visual Studio disponível. Isto pode ajudar quando se estiver trabalhando em um servidor ou em máquinas de produção onde normalmente somente o Framework .Net está instalado.

O problema a ser resolvido

O cálculo de número de dias úteis entre duas datas consiste em considerar uma data de início e contar quais dias cairão nos dias da semana compreendidos entre segunda e sexta-feira.

Desenvolvi este projeto para resolver um problema que surgiu durante o gerenciamento de um projeto. Este programa aceita um ou dois parâmetros. A figura 1 mostra o que acontece quando se digita o nome do programa sem nenhum destes parâmetros.

image

Se for digitada apenas uma data está será considerada a data inicial e serão calculados os dias úteis entre a data informada e a do sistema.

Também pode ser passado um parâmetro para obter ajuda que é –h ou –? que irá produzir esta saída:

image

Como limitações as datas devem estar no formato DD/MM/AAAA. Se for passada uma data inválida o programa retorna uma mensagem informando este erro e caso sejam passadas duas datas, se a primeira for maior do que a segunda o programa deve verificar isso e colocar na ordem certa.

O editor de código escolhido

O exemplo foi feito usando o Notepad++ que é um editor gratuito muito usado pelos programadores e que provê destaque de sintaxe para várias linguagens.

image

Além da vantagem de oferecer destaque conforme a linguagem escolhida, vários plugins podem ser adicionados para adicionar recursos. Um destes plugins foi tratado neste post.

O núcleo do projeto: o método para calcular a data

Seguindo a filosofia de que um método deve realizar apenas uma e somente uma tarefa e bem feita inicio colocando o código do método que faz o trabalho de calcular os dias. Este método recebe duas datas já na ordem correta e retorna um número indicando os dias corridos sendo que a data atual já conta como um dia corrido.

O método não faz validações nem inverte os parâmetros caso a data inicial seja maior do que a final. Também já recebe os dados no formato correto realizando apenas o trabalho de fazer o cálculo dos dias.

  private static int calculeDate(DateTime startDate, DateTime endDate) {
int days = 0;

while(startDate.Date <= endDate.Date) {
if(startDate.DayOfWeek != DayOfWeek.Saturday
&& startDate.DayOfWeek != DayOfWeek.Sunday)
days++;

startDate = startDate.AddDays(1);
}

return days;
}

Para o cálculo dos dias foi definida a variável days que vai sendo incrementada dentro do loop. Dentro deste é testado se o dia da semana é sábado ou domingo através da propriedade DayOfWeek que possui os valores para os dias da semana (em inglês). Caso não seja nenhum destes, a variável days é incrementada e o bloco switch encerrado. Na sequência, a data de inicio é incrementada em um dia com o método AddDays.


O método se encerra retornando o valor encontrado.


Criando o programa


Para testar este método pode ser criado um programa do tipo console. No exemplo que está sendo proposto, o programa pode receber como parâmetros a data inicial e opcionalmente a final ou ainda, um parâmetro para mostrar ajuda nos parâmetros usando as sequências -h e -?. Neste caso deve mostrar mensagens sobre o funcionamento.


No editor Notepad++ baseta iniciar o programa ou criar um novo arquivo com CTRL+N e ir digitando. O código para criar uma aplicação console segue abaixo.

using System;

public class Program {
private static DateTime _startDate, _endDate;

public static void Main(string[] args) {
if(args.Length < 1 || args.Length > 2) {
Console.WriteLine(@"Uso: workdays data inicial
[data final]. Use -h ou -? para obter ajuda.");

return;
}

if(!doValidations(args))
return;

Console.WriteLine(calculeDate(_startDate, _endDate));
}

Uma das grandes diferenças entre escrever código usando uma IDE como o Visual Studio ou um editor de textos é que no segundo caso você vai precisar estar atento a cada detalhe necessário para o programa poder ser compilado corretamente.


A desvantagem é que este tipo de programação é pouco produtivo já que não há ajuda para completar o código automaticamente e nem modelos prontos. Tudo tem de ser feito do zero (a menos que você mantenha um conjunto de modelos à mão). Por outro lado como vantagens cito que seu conhecimento sobre a linguagem será maior, já que ficará menos dependente da IDE e também o programa terá um código mais enxuto.


Um fato importante a observar é que não sou contra a utilização de IDE's profissionais (geralmente pagas) para produzir suas aplicações. Pelo contrário, precisamos ser produtivos e isto só pode ser conseguido com boas ferramentas. Uma aplicação comercial é complexa demais para você gerenciar seu desenvolvimento usando um editor de texto. Por outro lado, como foi citado no início, existem situações onde estas ferramentas não estão disponíveis mas, nem por isso você deve deixar de fazer o que precisa.


Dito isto, verificando o código, note que ele começa referenciando o namespace System que é o único necessário para a aplicação tanto para gerar a saída para o prompt de comando como para usar a classe DateTime, usada no método que calcula os dias úteis.


No C# todos os programas são criados dentro de classes. Assim a primeira declaração feita é da classe que por acaso, neste exemplo, é chamada de Program sendo que poderia ser qualquer nome.


Para auxiliar a validação dos dados foram criadas duas instâncias privadas e estáticas da classe DateTime.


Todo programa do tipo console deve ter uma classe estática nomeada como Main como no exemplo e esta pode ou não receber um array de argumentos. Neste caso, como espera-se parâmetros vindo do prompt de comandos, foi criado um argumento do tipo array de strings.


Se precisar de mais detalhes sobre a criação de aplicações console como Framework .Net e a linguagem C# consulte este artigo da MSDN - Desenvolvendo Aplicações Console que traz várias informações úteis.


Seguindo em frente com o código a verificação feita com o if garante que pelo menos um parâmetro seja passado como argumento e caso não ocorra isto, uma mensagem é enviada e o programa é encerrado.


Após a verificação inicial o programa faz uma validação dos parâmetros recebidos com um método (descrito a seguir) chamado doValidations, caso tudo esteja correto o resultado do método calculeDate é exibido na tela.


Método para a validação dos dados


Neste programa são feitas validações sobre os dados que se seguem:



  1. Se foram passados argumentos para ajuda, deve mostrar a mensagem de ajuda e encerrar o programa.
  2. Se as datas passadas são válidas.
  3. Por fim, se forem passadas duas datas estas devem ser colocadas na ordem correta para o cálculo dos dias.

São estas validações feitas pelo código abaixo.

  private static bool doValidations(string[] args) {    
args[0] = args[0].ToLower();

if(args[0] == "/?" || args[0] == "-?" ||
args[0] == "/h" || args[0] == "-h") {
Console.WriteLine("workdays data inicial [data final]");
Console.WriteLine(
"\tConta os dias uteis corridos entre a data inicial e a final.");
Console.WriteLine(
"\tRetorna um numero inteiro correspondente aos dias.");
Console.WriteLine(@"\tSe for informada apenas uma data,
esta deve ser menor que a
\n\tatual e o ano maior que zero.");
return false;
}

DateTime[] dates = new DateTime[2] { DateTime.Today, DateTime.Today };

for(int i = 0; i < args.Length; i++) {
if (!DateTime.TryParse(args[i], out dates[i])) {
Console.WriteLine("A data {0} informada incorreta: {1}",
(i == 0 ? "Inicial" : "Final"), args[i]);
return false;
}

if(args.Length == 1 &&
dates[0].Date > DateTime.Today.Date) {
Console.WriteLine("A data deve ser menor do que a atual.");
return false;
}
}

if(dates[1].Date < dates[0].Date) {
_startDate = dates[1];
_endDate = dates[0];
}
else {
_startDate = dates[0];
_endDate = dates[1];
}

return true;
}

Compilando


Seguindo na proposta de escrever o programa sem usar uma IDE a compilação deve ser feita a partir da janela de prompt de comandos do Windows. O compilador para a linguagem C# está localizada na pasta de instalação do Framework .Net normalmente localizada no caminho C:\Windows\Microsoft.NET\Framework\v<Versão do Framework> que no exemplo (ver imagem) é a versão 4.0.


image


O código exemplo deste post também pode ser compilado em outras versões uma vez que inclui recursos presentes em todas as versões a partir da 2.0.


Para compilar basta usar o comando csc <caminho completo do arquivo com o código fonte> o que faz como que o executável seja gerado na pasta atual.


Se for necessário gerar a saída em outro local pode se colocar a pasta e o nome do executável a ser gerado usando o argumento /out:. No meu caso usei a sintaxe da imagem abaixo que mostra a saída do compilador quando o programa consegue ser gerado corretamente.


image


Para mais detalhes sobre compilação com o Framework .Net usando o prompt de comando confira os links:



A listagem completa do programa segue encerrando este post. Até a próxima.

using System;

public class Program {
private static DateTime _startDate, _endDate;

public static void Main(string[] args) {
if(args.Length < 1 || args.Length > 2) {
Console.WriteLine("Uso: workdays data inicial [data final]. Use -h ou -? para obter ajuda.");
return;
}

if(!doValidations(args))
return;

Console.WriteLine(calculeDate(_startDate, _endDate));
}

private static bool doValidations(string[] args) {
args[0] = args[0].ToLower();

if(args[0] == "/?" || args[0] == "-?" || args[0] == "/h" || args[0] == "-h") {
Console.WriteLine("workdays data inicial [data final]");
Console.WriteLine("\tConta os dias uteis corridos entre a data inicial e a final.");
Console.WriteLine("\tRetorna um numero inteiro correspondente aos dias.");
Console.WriteLine("\tSe for informada apenas uma data, esta deve ser menor que a\n\tatual e o ano maior que zero.");
return false;
}

DateTime[] dates = new DateTime[2] { DateTime.Today, DateTime.Today };

for(int i = 0; i < args.Length; i++) {
if (!DateTime.TryParse(args[i], out dates[i])) {
Console.WriteLine("A data {0} informada incorreta: {1}", (i == 0 ? "Inicial" : "Final"), args[i]);
return false;
}

if(args.Length == 1 && dates[0].Date > DateTime.Today.Date) {
Console.WriteLine("A data deve ser menor do que a atual.");
return false;
}
}

if(dates[1].Date < dates[0].Date) {
_startDate = dates[1];
_endDate = dates[0];
}
else {
_startDate = dates[0];
_endDate = dates[1];
}

return true;
}

private static int calculeDate(DateTime startDate, DateTime endDate) {
int days = 0;

while(startDate.Date <= endDate.Date) {
if(startDate.DayOfWeek != DayOfWeek.Saturday
&& startDate.DayOfWeek != DayOfWeek.Sunday)
days++;

startDate = startDate.AddDays(1);
}

return days;
}
}

Postagens mais visitadas deste blog

Como gerar scripts para exportar dados no SQL Server 2008

Uma das tarefas mais comuns no trabalho com desenvolvimento de software que consome dados em bancos como o SQL Server 2008 é a necessidade de em algum momento precisarmos exportar os dados de um banco para outro. Quer seja para realizar testes ou fazer simulações existem várias maneiras de se fazer isto. Neste post eu quero demonstrar um recurso do SQL Server Management Studio (SSMS) que permite realizar esta tarefa rapidamente.Para os que estão acostumados a usar esta ferramenta, já devem saber que é possível gerar scripts para o schema e também transferir os dados entre dois bancos distintos. Isto pode ser feito se o SSMS puder conectar-se com as duas bases, de origem e destino. No exemplo que vou dar, o objetivo é gerar o script apenas para uma tabela do banco de dados de exemplo da Microsoft – Northwind.1. Iniciando o assistenteO assistente deve ser iniciado clicando com o botão direito do mouse sobre o banco onde se encontra a tabela a qual iremos gerar o script. Deve se clicar n…

Pivot dinâmico com SQL Server

Passo a passo para usar pivoteamento dinâmicoOs bancos de dados bem configurados e definidos armazenam os dados de forma a otimizar o acesso, evitando duplicidade e garantindo a integridade. Porém, em muitas situações isto pode dificultar a apresentação de forma adequada sendo necessário preparar os dados usando vários recursos entre os quais, fazer o pivoteamento.Se você não precisou ainda usar ou não sabe o que é consiste em transformar cada linha de uma determinada coluna em colunas de uma nova consulta.Assim, considere uma tabela que armazene as notas bimestrais de um boletim. Uma possível estrutura para esta tabela seria algo assim:ColunaTipo de dado/TamanhoDisciplinanvarchar(50)BimestreIntNotanumeric(5,2)Uma consulta select nesta tabela com alguns dados traria um resultado parecido com o abaixo:Porém pode ser que para apresentar estes dados em um relatório seja necessário transformar cada bimestre em uma coluna e agrupar as notas nestas colunas para que fique dessa forma:Isto po…

Desabilitando o auto commit no SSMS (SQL Server Management Studio)

(Ou, como prevenir desastres e manter o emprego a salvo…)Neste post vai uma pequena mas tremendamente útil dica para desabilitar o auto commit da aplicação SQL Server Management Studio (SSMS) que é usada por dez entre dez usuários do banco de dados SQL Server para fazer consultas, alterações e executar scripts no banco de dados. (Preferências à parte, realmente muita gente usa),A primeira e mais importante notícia é que, diferentemente da ferramenta do Oracle, este editor de scripts do SQL Server vem com o recurso de auto commit ativado por padrão, assim, qualquer instrução DML (alteração dos dados com update, insert e delete) ou DDL (alteração no banco como create, drop, alter, etc.) será imediatamente enviada ao banco e persistida.Isto pode ser altamente crítico pois se estiver executando as instruções em um banco de dados de produção não haverá muitas formas de desfazer se é que haverá.Inicialmente, pode se evitar muitos acidades executando estas instruções dentro de um bloco BEGIN…