Ao trabalhar com JasperServer, JasperReports e iReport, calcular datas é um problema comum, principalmente se elas lidam com timezones, patterns de formatação e regras de validação diferentes. Você normalmente quer calcular as datas para preencher parâmetros ao chamar sub-relatórios, por exemplo.

Expressões iReport oferecem uma maneira de fazer cálculos em Java ou Groovy. Contudo, cada cálculo, tal como a definição de um valor de parâmetro, deve consistir em exatamente uma expressão. Ao tentar definir um objeto de data complexo, descrevendo o último dia do mês passado, por exemplo, a maioria das pessoas acha que é muito difícil, se não impossível, fazer isso em uma única expressão. Existem diferentes sugestões para lidar com a situação.

A maioria das recomendações parece seguir o plano de fazer todos os cálculos de data em uma consulta SQL ou usando uma biblioteca Java adicional. Ambas as ideias ajudam a resolver o problema, mas também têm desvantagens. Resultados baseados em SQL podem forçá-lo a criar o relatório em torno do cálculo da data, tornando mais difícil o entendimento. Por outro lado, o uso de bibliotecas Java adicionais pode aumentar a complexidade da configuração do seu desenvolvimento, uma vez que cada instância JasperServer e cada desenvolvedor iReport precisa casar as versões correspondentes da biblioteca. Mas existem outras possibilidades, dentre elas as que tentam resolver o problema fazendo expressões Groovy simples, e não requerendo bibliotecas adicionais.

O Conceito

A entrada de um relatório geralmente consiste em uma data para executá-lo. Em uma solução ideal o relatório lógico calcula todas as outras datas necessárias internamente. Para fazer isso corretamente você normalmente depende da classe Calendar Java. Acontece, porém, que várias chamadas de função são obrigadas para fazer cálculos de data corretamente usando essa classe. Suponha que já tenhamos a data para executar um relatório para o parâmetro runDate. A Listagem 1 exibe as linhas de código necessárias para calcular o último dia do último mês.


Calendar cal = Calendar.getInstance();
cal.set($P{runDate}.getYear()+1900, $P{runDate}.getMonth(), $P{runDate}.getDate());
cal.add(Calendar.MONTH, -1);
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
return cal.getTime();
Listagem 1. Código para calcular o ultimo dia do mês

O problema, claro, é que o iReport não permitiria múltiplas expressões e uma declaração de retorno. É preciso chegar a uma maneira de colocar tudo isso em uma única expressão. Não se preocupe, pois isso pode ser feito. Veja o passo a passo:

  1. Primeiramente é preciso converter a variável local cal em um parâmetro iReport. Basta adicionar um parâmetro do tipo java.util.Calendar com valor padrão Calendar.getInstance() e pedir ao iReport para não solicitar o seu valor. Na Figura 1 você pode ver o que a definição do parâmetro deve ser parecida.

    Propriedades da variável cal
    Figura 1. Propriedades da variável cal

    Agora se tem um objeto de calendário disponível nas expressões, que pode ser referenciado como qualquer outro parâmetro com $P{cal}. É possível reescrever o pseudocódigo agora, observando como a alocação explícita do objeto cal não é mais necessária, poupando uma expressão de atribuição (Listagem 2).

    
    $P{cal}.set($P{runDate}.getYear()+1900,$P{runDate}.getMonth(),$P{runDate}.getDate());
    $P{cal}.add(Calendar.MONTH, -1);
    $P{cal}.set(Calendar.DAY_OF_MONTH, $P{cal}.getActualMaximum(Calendar.DAY_OF_MONTH));
    return $P{cal}.getTime();
    
    Listagem 2. Código de atribuição dos parâmetros

    Agora é hora de ter um olhar mais atento sobre a documentação para a classe Calendar. Os métodos de adicionar e configurar que foram usados para os cálculos não tem um tipo de retorno. Em um contexto booleano, as chamadas para adicionar e configurar são avaliadas para false. É possível explorar esse fato e colocar todas as três chamadas em uma única expressão usando o operador lógico OR | |, tal como é mostrado na Listagem 3.

    
    (
     $P{cal}.set($P{runDate}.getYear()+1900, $P{runDate}.getMonth(), $P{runDate}.getDate()) ||
     $P{cal}.add(Calendar.MONTH, -1) ||
     $P{cal}.set(Calendar.DAY_OF_MONTH, $P{cal}.getActualMaximum(Calendar.DAY_OF_MONTH))
    );
    return $P{cal}.getTime();
    
    Listagem 3. Chamadas do operador lógico OR

    Também é possível usar o operador ternário (?) para escrever todo o cálculo em uma única expressão, evitando também a instrução de retorno. Tome um minuto para ler sobre o operador ternário se você não sabe como ele funciona. Sabe-se que a expressão do cálculo de Calendar foi avaliada como falsa, porque cada instrução é avaliada como false. Assim, uma única expressão que faz o cálculo e retorna a data resultante é semelhante ao que é mostrado na Listagem 4.

    
    (
     $P{cal}.set($P{runDate}.getYear()+1900, $P{runDate}.getMonth(), $P{runDate}.getDate()) ||
     $P{cal}.add(Calendar.MONTH, -1) ||
     $P{cal}.set(Calendar.DAY_OF_MONTH, $P{cal}.getActualMaximum(Calendar.DAY_OF_MONTH))
    )
    ? null : $P{cal}.getTime()
    
    Listagem 4. Cálculo avaliado como false usando o operador ternário

    É isso. Esse é um cálculo de múltiplas linhas usando a classe Calendar em uma única expressão. Você provavelmente deve colocar uma expressão como esta em uma variável de relatório e usar a variável sempre que você precisar.

    Conclusão

    A técnica sugerida resolve o problema de fazer cálculos de data em várias linhas sem usar bibliotecas externas, ao mesmo tempo, mantendo os cálculos de data de seu SQL. O preço a pagar é se acostumar a uma sintaxe um pouco inconveniente. Particularmente, este é um pequeno preço a pagar, especialmente desde que os cálculos podem ser encapsulados em variáveis ou parâmetros iReport.

    No curso iReport, você conhece a técnica de cálculo sugerida pela definição e imprime algumas variáveis ​​de data. Vá em frente, teste e veja como funciona!