-
Notifications
You must be signed in to change notification settings - Fork 360
/
DateDifFunction.java
226 lines (190 loc) · 9.04 KB
/
DateDifFunction.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2006 - 2013 Pentaho Corporation and Contributors. All rights reserved.
*/
package org.pentaho.reporting.libraries.formula.function.datetime;
import org.pentaho.reporting.libraries.formula.EvaluationException;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import org.pentaho.reporting.libraries.formula.LocalizationContext;
import org.pentaho.reporting.libraries.formula.function.Function;
import org.pentaho.reporting.libraries.formula.function.ParameterCallback;
import org.pentaho.reporting.libraries.formula.lvalues.TypeValuePair;
import org.pentaho.reporting.libraries.formula.typing.TypeRegistry;
import org.pentaho.reporting.libraries.formula.typing.coretypes.NumberType;
import org.pentaho.reporting.libraries.formula.util.NumberUtil;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
/**
* This function returns the number of years, months, or days between two date numbers.<br/>
* <p/>
* The Format is a code from the following table, entered as text, that specifies the format you want: <TABLE> <TR>
* <TH>format</TH> <TH>Returns the number of</TH> </TR> <TR> <TD>y</TD> <TD>Years</TD> </TR> <TR> <TD>m</TD> <TD>Months.
* If there is not a complete month between the dates, 0 will be returned.</TD> </TR> <TR> <TD>d</TD> <TD>Days</TD>
* </TR> <TR> <TD>md</TD> <TD>Days, ignoring months and years</TD> </TR> <TR> <TD>ym</TD> <TD>Months, ignoring
* years</TD> </TR> <TR> <TD>yd</TD> <TD>Days, ignoring years</TD> </TR> <TR> <TD></TD> <TD></TD> </TR> </TABLE>
*
* @author Cedric Pronzato
*/
public class DateDifFunction implements Function {
public static final String YEARS_CODE = "y";
public static final String MONTHS_CODE = "m";
public static final String DAYS_CODE = "d";
public static final String DAYS_IGNORING_YEARS = "yd";
public static final String MONTHS_IGNORING_YEARS = "ym";
public static final String DAYS_IGNORING_MONTHS_YEARS = "md";
private static final long serialVersionUID = 81013707499607068L;
public DateDifFunction() {
}
public String getCanonicalName() {
return "DATEDIF"; // NON-NLS
}
public TypeValuePair evaluate( final FormulaContext context,
final ParameterCallback parameters )
throws EvaluationException {
if ( parameters.getParameterCount() != 3 ) {
throw EvaluationException.getInstance( LibFormulaErrorValue.ERROR_ARGUMENTS_VALUE );
}
final TypeRegistry typeRegistry = context.getTypeRegistry();
final String formatCode = typeRegistry.convertToText
( parameters.getType( 2 ), parameters.getValue( 2 ) );
if ( formatCode == null || "".equals( formatCode ) ) {
throw EvaluationException.getInstance(
LibFormulaErrorValue.ERROR_INVALID_ARGUMENT_VALUE );
}
long days = computeDays( parameters, typeRegistry );
if ( DateDifFunction.DAYS_CODE.equals( formatCode ) ) {
return new TypeValuePair( NumberType.GENERIC_NUMBER, new BigDecimal( days ) );
}
final Date date1 = typeRegistry.convertToDate( parameters.getType( 0 ), parameters.getValue( 0 ) );
final Date date2 = typeRegistry.convertToDate( parameters.getType( 1 ), parameters.getValue( 1 ) );
if ( date1 == null || date2 == null ) {
throw EvaluationException.getInstance( LibFormulaErrorValue.ERROR_INVALID_ARGUMENT_VALUE );
}
final LocalizationContext localizationContext = context.getLocalizationContext();
final TimeZone timeZone = localizationContext.getTimeZone();
final Locale locale = localizationContext.getLocale();
final GregorianCalendar calandar1 = new GregorianCalendar( timeZone, locale );
calandar1.setTime( min( date1, date2 ) );
final GregorianCalendar calandar2 = new GregorianCalendar( timeZone, locale );
calandar2.setTime( max( date1, date2 ) );
int sign = ( date1.getTime() < date2.getTime() ) ? 1 : -1;
final long res = sign * computeDateDifference( formatCode, calandar1, calandar2, days );
return new TypeValuePair( NumberType.GENERIC_NUMBER, new BigDecimal( res ) );
}
protected long computeDays( final ParameterCallback parameters,
final TypeRegistry typeRegistry ) throws EvaluationException {
final Number date1 = typeRegistry.convertToNumber( parameters.getType( 0 ), parameters.getValue( 0 ) );
final Number date2 = typeRegistry.convertToNumber( parameters.getType( 1 ), parameters.getValue( 1 ) );
final BigDecimal dn1 = NumberUtil.performIntRounding( NumberUtil.getAsBigDecimal( date1 ) );
final BigDecimal dn2 = NumberUtil.performIntRounding( NumberUtil.getAsBigDecimal( date2 ) );
return dn2.longValue() - dn1.longValue();
}
protected long computeDateDifference( final String formatCode,
final GregorianCalendar min,
final GregorianCalendar max,
final long days ) throws EvaluationException {
if ( DateDifFunction.YEARS_CODE.equals( formatCode ) ) {
// done
return computeYears( min, max );
} else if ( DateDifFunction.MONTHS_CODE.equals( formatCode ) ) {
// done
return computeMonths( min, max );
} else if ( DateDifFunction.DAYS_IGNORING_MONTHS_YEARS.equals( formatCode ) ) {
return computeMonthDays( min, max );
} else if ( DateDifFunction.MONTHS_IGNORING_YEARS.equals( formatCode ) ) {
// done
return computeYearMonth( min, max );
} else if ( DateDifFunction.DAYS_IGNORING_YEARS.equals( formatCode ) ) {
// done
return computeYearDays( min, max, days );
} else {
throw EvaluationException.getInstance( LibFormulaErrorValue.ERROR_INVALID_ARGUMENT_VALUE );
}
}
private long computeYearDays( final GregorianCalendar min, final GregorianCalendar max, final long dayDiff ) {
final int year1 = min.get( Calendar.YEAR );
final int year2 = max.get( Calendar.YEAR );
if ( year1 == year2 ) {
// simple case: We are within the same year
return Math.abs( dayDiff );
}
final int dayMinDate = min.get( Calendar.DAY_OF_YEAR );
final int dayMaxDate = max.get( Calendar.DAY_OF_YEAR );
if ( dayMinDate <= dayMaxDate ) {
return dayMaxDate - dayMinDate;
}
int daysInMinYear = min.getActualMaximum( Calendar.DAY_OF_YEAR );
int daysToEndOfYear = daysInMinYear - dayMinDate;
return dayMaxDate + daysToEndOfYear;
}
private long computeYearMonth( final GregorianCalendar min, final GregorianCalendar max ) {
return computeMonths( min, max ) % 12;
}
private long computeMonthDays( final GregorianCalendar min, final GregorianCalendar max ) {
// The number of days between Date1 and Date2, as if Date1 and
// Date2 were in the same month and the same year.
int dayMin = min.get( Calendar.DAY_OF_MONTH );
int dayMax = max.get( Calendar.DAY_OF_MONTH );
if ( dayMin <= dayMax ) {
return dayMax - dayMin;
}
int maxDaysInMonth = max.getActualMaximum( Calendar.DAY_OF_MONTH );
return maxDaysInMonth + dayMax - dayMin;
}
private int addFieldLoop( final GregorianCalendar c, final GregorianCalendar target, final int field ) {
c.set( Calendar.MILLISECOND, 0 );
c.set( Calendar.SECOND, 0 );
c.set( Calendar.MINUTE, 0 );
c.set( Calendar.HOUR_OF_DAY, 0 );
target.set( Calendar.MILLISECOND, 0 );
target.set( Calendar.SECOND, 0 );
target.set( Calendar.MINUTE, 0 );
target.set( Calendar.HOUR_OF_DAY, 0 );
if ( c.getTimeInMillis() == target.getTimeInMillis() ) {
return 0;
}
int count = 0;
while ( true ) {
c.add( field, 1 );
if ( c.getTimeInMillis() > target.getTimeInMillis() ) {
return count;
}
count += 1;
}
}
private long computeMonths( final GregorianCalendar min, final GregorianCalendar max ) {
return addFieldLoop( min, max, Calendar.MONTH );
}
private long computeYears( final GregorianCalendar min, final GregorianCalendar max ) {
return addFieldLoop( min, max, Calendar.YEAR );
}
private Date min( final Date d1, final Date d2 ) {
if ( d1.getTime() < d2.getTime() ) {
return d1;
}
return d2;
}
private Date max( final Date d1, final Date d2 ) {
if ( d1.getTime() >= d2.getTime() ) {
return d1;
}
return d2;
}
}