Date calculation in iReport

The Problem

When working with JasperServer, JasperReports, and iReport it is a common problem to perform date calculations. You would usually want to calculate dates to fill parameters when calling sub reports for example. iReport expressions offer a way to do calculations in Java or Groovy. But each calculation, like the definition of a parameter value, must consist of exactly one expression. When trying to define a complex date object, describing the last day of the last month for example, most people find it very difficult, if not impossible, to do this in a single expression. I have seen different suggestions to deal with the situation. Most recommendations seem to go towards doing all date calculation in your SQL query or using an additional Java library. Both ideas help remedy the problem. Both also have drawbacks. SQL based results may force you to design the report around the date calculation, making it harder to unterstand. Additional Java libraries may increase the complexity of your development setup, since each JasperServer instance and each iReport developer needs matching versions of the library. I would like to propose another possibility, that tries to solve the problem doing simple Groovy expressions, and does not require additional libraries.

 

The Idea

The input for a report usually consists of a date. The date to run the report for. In an ideal solution the report logic calculates all other required dates internally. To do this properly you would usually depend on the Java Calendar class. It turns out however that several function calls are required to do date calculations properly using the Calendar class. Suppose we already have the date to run a report for in the parameter runDate. The following lines of code would calculate the last day of the last month.

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();

The problem is of course, that iReport would not allow multiple expressions and a return statement. We need to come up with a way to put all this into a single expression. Don’t worry. It can be done. Let’s do this step by step. First of all we need to convert the local variable cal into an iReport parameter. Just add a parameter of type java.util.Calendar with default value Calendar.getInstance() and ask iReport not to prompt for its value. This is what the parameter definition should look like.

Now we have a calendar object available in our expressions, that can be referenced like any other parameter using $P{cal}. We could rewrite our pseudo code now. Notice how the explicit allocation of the cal object is not necessary any more, saving us one assignment expression.

$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();

Now it is time to have a close look at the documentation for the Calendar class. The add and set methods we use for our calculations do not have a return type. In a boolean context, calls to add and set evaluate to false.  We can exploit this fact and put all three calls into a single expression using the logical OR operator ||.

(
 $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();

We are almost there. We can use the ? operator to write the entire calculation in a single expression, also avoiding the return statement. Take a minute to read up on the ? operator if you are unsure how it works. We know for a fact that the calendar calculation expression we wrote evaluates to false, because each statement evaluates to false. So a single expression that does the calculation and returns the resulting date looks like the following.

(
 $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()

This is it.  We are doing a multiline calculation using the Calendar class in a single expression. You should probably put an expression like this into a report variable and use the variable whenever you need it.

Conclusion

The suggested technique solves the problem of doing multiline date calculations without using external libraries, while also keeping your date calculations out of your SQL. The price to pay is getting used to a slightly inconvenient syntax. For me this is usually a small price to pay, especially since the calculations can be encapsulated in iReport variables or parameters. See the example report for details.

Example Report

You may want to download an example report created in iReport 3.7.3, which demonstrates the suggested calculation technique by defining and printing a few date variables.

猜你喜欢

转载自tooby.iteye.com/blog/2291455