Calendar API 不能直接用于这个问题:每个操作都需要多行,因为有趣的方法是 void 返回并且不能被链接。
这是一个很大的延伸,但是,正如the dependencies of JasperReports 中列出的那样,org.codehaus.castor:castor-xml:1.3.3 依赖于 commons-lang:commons-lang:2.6。因此,我们可以使用DurationFormatUtils.formatPeriod(startMillis, endMillis, format) 方法,它存在于commons-lang 中。此处使用的格式字符串为
"y' years 'M' months 'd' days 'H' hours 'm' minutes 's' seconds'"
这将打印想要的字符串。仍然必须小心:这将包括 0(如 "0 months")并且还会有不正确的复数形式(如 "1 months")。
- 我们可以使用正则表达式
"(?<!\\d)0 (\\w+) ?" 删除字符串的所有0。此正则表达式匹配任何 0,not preceded by a digit(例如,我们不想匹配 10),后跟一个或多个单词字符,并且可以选择后跟一个空格。
- 然后,我们可以使用正则表达式
"(?<!\\d)1 (\\w+)s"来匹配"1 ...s"的每一个出现,并将其替换为"1 ..."以进行正确的复数。此正则表达式匹配任何 1,前面没有数字,后面跟着一个或多个以 s 结尾的单词字符(捕获在一个组中);它将替换为"1 $1",即 1 后跟捕获的值。
例子:
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")
);
所有这些都可以使用 Java 7 或更低版本完成。
在 JasperReports 中,这将是一个示例:
<?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>
输出为:
如果上面看起来太脆弱(因为对 commons-lang 的显式依赖,并非所有 JasperReports 版本都存在),there is another possible solution 使用 Java 8 中引入的 Java Time API。
这是可怕(我认为没有更简单的方法),但输出与上面完全相同,其中start 和end 都是LocalDateTime 对象:
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")
);