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.
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:
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.
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:
- Se foram passados argumentos para ajuda, deve mostrar a mensagem de ajuda e encerrar o programa.
- Se as datas passadas são válidas.
- 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.
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.
Para mais detalhes sobre compilação com o Framework .Net usando o prompt de comando confira os links:
- Compilação pela linha de comando com csc.exe
- C# Compiler Options
- C# Compiler Options Listed Alphabetically
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;
}
}