How can I create a single jasper report JRExpression that visualize the difference between two java.util.Date
in format yy year(s) month(s) dd day(s), hh hour(s), mm minute(s), ss second(s)
java.util.Date startDate
java.util.Date endDate
A JRExpression is a single line where variable declaration is not allowed, however you may use conditional statements using syntax boolean ? yes:no
, for you who are not familiar imagine one line of System.out.println();
Example of desired output (if you have a nice solution remove description of unit when not present and consider singular/plural but this is not necessary if it's a serie of if statements):
2 years, 8 months, 12 days, 2 hours, 53 minutes, 10 s
1 hour, 1 minute
both Feb 2- Mar 4 and Mar 4 - April 6 are "1 month, 2 days", daylight savings however can be ignored - thanks @Affe
Additional requirements:
There is no need to format the answer as jasper report expression it can be a simple System.out.println
code (I'm happy to edit your answer later to also add the jasper report expression code). Example
((endDate.getTime()-startDate.getTime()) / (60 * 60 * 1000)) % 24 + " hour(s), " +
((endDate.getTime()-startDate.getTime()) / (60 * 1000)) % 60 + " minute(s)"
What have I tried:
I answer multiple question in the jasper report section of SO, and this question is common in report generation. I would prefer a good answer from the java section that I can link rather then passing my code on this issue (that I would only know to solve partially as example)
This is an example on question in jasper-report: Calculating Time and Date difference
Some reference code:
Calculate date/time difference in java
How to find the duration of difference between two dates in java?
For you that are familiar to jasper report I don't want to use the variable declaration since this would invalidate the solution if user need to use it on parameters.
The Calendar API cannot be directly be used for this problem: every operation would require multiple lines since the interesting methods are void
returning and can't be chained.
This is a very big stretch, but, as listed in the dependencies of JasperReports, there is org.codehaus.castor:castor-xml:1.3.3
which depends itself on commons-lang:commons-lang:2.6
. We therefore can make use of the DurationFormatUtils.formatPeriod(startMillis, endMillis, format)
method, which is present in commons-lang
. The format String to use here would be
"y' years 'M' months 'd' days 'H' hours 'm' minutes 's' seconds'"
which would print the wanted String. Care must still be taken: this will include 0s (like "0 months"
) and will also have incorrect pluralization (like "1 months"
).
"(?<!\\d)0 (\\w+) ?"
to remove all the 0s for the String. This regex matches any 0, not preceded by a digit (we don't want to match 10 for example), followed by one or more word characters and optionally followed by a space."(?<!\\d)1 (\\w+)s"
to match every occurence of "1 ...s"
and replace it with "1 ..."
to have proper pluralization. This regular expression matches any 1, not preceded by a digit, followed by one or more word characters (captured in a group) ending with an s
; it would be replaced with "1 $1"
, i.e. 1 followed by the value captured.Example:
System.out.println(
org.apache.commons.lang.time.DurationFormatUtils.formatPeriod(
startDate.getTime(),
endDate.getTime(),
"y' years 'M' months 'd' days 'H' hours 'm' minutes 's' seconds'"
)
.replaceAll("(?<!\\d)0 (\\w+) ?", "")
.replaceAll("(?<!\\d)1 (\\w+)s", "1 $1")
);
All this can be done with Java 7 or lower.
In a JasperReports, this would be an example:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.2.1.final using JasperReports Library version 6.2.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports jasperreports.sourceforge.net/…" name="Blank_A4" pageWidth="595" pageHeight="842" whenNoDataType="AllSectionsNoDetail" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="f067f2c4-395f-4669-9fda-4fe81cc59227">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<parameter name="dateStart" class="java.util.Date" isForPrompting="false">
<defaultValueExpression><![CDATA[new java.util.Date(1)]]></defaultValueExpression>
</parameter>
<parameter name="dateEnd" class="java.util.Date" isForPrompting="false">
<defaultValueExpression><![CDATA[new java.util.Date()]]></defaultValueExpression>
</parameter>
<queryString><![CDATA[]]></queryString>
<title>
<band height="43" splitType="Stretch">
<textField>
<reportElement x="0" y="0" width="560" height="30" uuid="cc03531c-2983-4f9a-9619-2826ed92760e"/>
<textFieldExpression><![CDATA[org.apache.commons.lang.time.DurationFormatUtils.formatPeriod($P{dateStart}.getTime(),$P{dateEnd}.getTime(),"y' years 'M' months 'd' days 'H' hours 'm' minutes 's' seconds'").replaceAll("(?<!\\d)0 (\\w+) ?", "").replaceAll("(?<!\\d)1 (\\w+)s", "1 $1")]]></textFieldExpression>
</textField>
</band>
</title>
</jasperReport>
With the output being:
If the above looks too fragile (because of the explicit dependency towards commons-lang
that could not be there for all JasperReports version), there is another possible solution using the Java Time API introduced in Java 8.
This is horrible (I don't think there is a simpler way), but the output is exactly the same as above, where start
and end
are both LocalDateTime
objects:
System.out.println((
ChronoUnit.YEARS.between(start, end) + " years " +
ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end) + " months " +
ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end) + " days " +
ChronoUnit.HOURS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)).plusDays(ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end)), end) + " hours " +
ChronoUnit.MINUTES.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)).plusDays(ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end)).plusHours(ChronoUnit.HOURS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)).plusDays(ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end)), end)), end) + " minutes " +
ChronoUnit.SECONDS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)).plusDays(ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end)).plusHours(ChronoUnit.HOURS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)).plusDays(ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end)), end)).plusMinutes(ChronoUnit.MINUTES.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)).plusDays(ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end)).plusHours(ChronoUnit.HOURS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)).plusDays(ChronoUnit.DAYS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)).plusMonths(ChronoUnit.MONTHS.between(start.plusYears(ChronoUnit.YEARS.between(start, end)), end)), end)), end)), end)), end) + " seconds"
)
.replaceAll("(?<!\\d)0 (\\w+) ?", "")
.replaceAll("(?<!\\d)1 (\\w+)s", "1 $1")
);