<filterchain> \r
<!-- capture any new files -->\r
<linecontainsregexp>\r
- <regexp pattern="(\?|A)......"/>\r
+ <regexp pattern="^(\?|A)......"/>\r
</linecontainsregexp>\r
<!-- filter out the first six characters -->\r
<tokenfilter>\r
- <replaceregex pattern="(.......)" replace=""/>\r
+ <replaceregex pattern="^(.......)" replace=""/>\r
</tokenfilter>\r
<!--remove line breaks -->\r
<striplinebreaks/>\r
<p>
As of Feb 2012, POI supports about 140 built-in functions,
see <link href="#appendixA">Appendix A</link> for the full list.
- You can programmatically list supported / unsuported functions using trhe following helper methods:
+ You can programmatically list supported / unsuported functions using the following helper methods:
</p>
<source>
// list of functions that POI can evaluate
- Collection<String> suportedFuncs = WorkbookEvaluator.getSupportedFunctionNames();
+ Collection<String> supportedFuncs = WorkbookEvaluator.getSupportedFunctionNames();
// list of functions that are not supported by POI
Collection<String> unsupportedFuncs = WorkbookEvaluator.getNotSupportedFunctionNames();
VAR
VARP
VLOOKUP
+ WEEKDAY
WORKDAY
YEAR
YEARFRAC
<changes>
<release version="3.9-beta1" date="2012-??-??">
+ <action dev="poi-developers" type="add">53644 - XLS formula bugfix (CalFieldFunc) + WeekDay addon </action>
<action dev="poi-developers" type="add">53446 - Fixed some problems extracting PNGs </action>
<action dev="poi-developers" type="fix">53205 - Fixed some parsing errors and encoding issues in HDGF </action>
<action dev="poi-developers" type="add">53204 - Improved performanceof PageSettingsBlock in HSSF </action>
retval[68] = CalendarFieldFunction.MONTH;
retval[69] = CalendarFieldFunction.YEAR;
+ retval[70] = WeekdayFunc.instance;
retval[71] = CalendarFieldFunction.HOUR;
retval[72] = CalendarFieldFunction.MINUTE;
retval[73] = CalendarFieldFunction.SECOND;
int col = (int)NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);\r
\r
int refType;\r
- if(args.length > 2){\r
+ if (args.length > 2 && args[2] != MissingArgEval.instance) {\r
refType = (int)NumericFunction.singleOperandEvaluate(args[2], srcRowIndex, srcColumnIndex);\r
} else {\r
- refType = REF_ABSOLUTE;\r
+ refType = REF_ABSOLUTE; // this is also the default if parameter is not given\r
}\r
switch (refType){\r
case REF_ABSOLUTE:\r
import org.apache.poi.ss.formula.eval.BoolEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.MissingArgEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.RefEval;
import org.apache.poi.ss.formula.eval.ValueEval;
if (arg instanceof RefEval) {
ValueEval ve = ((RefEval) arg).getInnerValueEval();
tempVe = OperandResolver.coerceValueToBoolean(ve, true);
+ } else if (arg == MissingArgEval.instance) {
+ tempVe = null; // you can leave out parameters, they are simply ignored
} else {
tempVe = OperandResolver.coerceValueToBoolean(arg, false);
}
package org.apache.poi.ss.formula.functions;
import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
* Implementation of Excel functions Date parsing functions:
* Date - DAY, MONTH and YEAR
* Time - HOUR, MINUTE and SECOND
+ *
+ * @author Others (not mentioned in code)
+ * @author Thies Wellpott
*/
public final class CalendarFieldFunction extends Fixed1ArgFunction {
public static final Function YEAR = new CalendarFieldFunction(Calendar.YEAR);
public static final Function MONTH = new CalendarFieldFunction(Calendar.MONTH);
public static final Function DAY = new CalendarFieldFunction(Calendar.DAY_OF_MONTH);
public static final Function HOUR = new CalendarFieldFunction(Calendar.HOUR_OF_DAY);
- public static final Function MINUTE = new CalendarFieldFunction(Calendar.MINUTE);
- public static final Function SECOND = new CalendarFieldFunction(Calendar.SECOND);
+ public static final Function MINUTE = new CalendarFieldFunction(Calendar.MINUTE);
+ public static final Function SECOND = new CalendarFieldFunction(Calendar.SECOND);
private final int _dateFieldId;
private int getCalField(double serialDate) {
// For some reason, a date of 0 in Excel gets shown
// as the non existant 1900-01-00
- if(((int)serialDate) == 0) {
+ if (((int)serialDate) == 0) {
switch (_dateFieldId) {
case Calendar.YEAR: return 1900;
case Calendar.MONTH: return 1;
}
// TODO Figure out if we're in 1900 or 1904
- Date d = DateUtil.getJavaDate(serialDate, false);
-
- Calendar c = new GregorianCalendar();
- c.setTime(d);
+ // EXCEL functions round up nearly a half second (probably to prevent floating point
+ // rounding issues); use UTC here to prevent daylight saving issues for HOUR
+ Calendar c = DateUtil.getJavaCalendarUTC(serialDate + 0.4995 / DateUtil.SECONDS_PER_DAY, false);
int result = c.get(_dateFieldId);
// Month is a special case due to C semantics
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.functions;
+
+import java.util.Calendar;
+
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.MissingArgEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.usermodel.DateUtil;
+
+
+/**
+ * Implementation for the Excel function WEEKDAY
+ *
+ * @author Thies Wellpott
+ */
+public final class WeekdayFunc implements Function {
+//or: extends Var1or2ArgFunction {
+
+ public static final Function instance = new WeekdayFunc();
+
+ private WeekdayFunc() {
+ // no fields to initialise
+ }
+
+ /* for Var1or2ArgFunction:
+ @Override
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+ }
+
+ @Override
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+ }
+ */
+
+
+ /**
+ * Perform WEEKDAY(date, returnOption) function.
+ * Note: Parameter texts are from German EXCEL-2010 help.
+ * Parameters in args[]:
+ * @param serialDate
+ * EXCEL-date value
+ * Standardmaessig ist der 1. Januar 1900 die fortlaufende Zahl 1 und
+ * der 1. Januar 2008 die fortlaufende Zahl 39.448, da dieser Tag nach 39.448 Tagen
+ * auf den 01.01.1900 folgt.
+ * @param returnOption (optional)
+ * Bestimmt den Rueckgabewert:
+ 1 oder nicht angegeben Zahl 1 (Sonntag) bis 7 (Samstag). Verhaelt sich wie fruehere Microsoft Excel-Versionen.
+ 2 Zahl 1 (Montag) bis 7 (Sonntag).
+ 3 Zahl 0 (Montag) bis 6 (Sonntag).
+ 11 Die Zahlen 1 (Montag) bis 7 (Sonntag)
+ 12 Die Zahlen 1 (Dienstag) bis 7 (Montag)
+ 13 Die Zahlen 1 (Mittwoch) bis 7 (Dienstag)
+ 14 Die Zahlen 1 (Donnerstag) bis 7 (Mittwoch)
+ 15 Die Zahlen 1 (Freitag) bis 7 (Donnerstag)
+ 16 Die Zahlen 1 (Samstag) bis 7 (Freitag)
+ 17 Die Zahlen 1 (Sonntag) bis 7 (Samstag)
+ */
+ @Override
+ public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
+ try {
+ if (args.length < 1 || args.length > 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ // extract first parameter
+ ValueEval serialDateVE = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex);
+ double serialDate = OperandResolver.coerceValueToDouble(serialDateVE);
+ if (!DateUtil.isValidExcelDate(serialDate)) {
+ return ErrorEval.NUM_ERROR; // EXCEL uses this and no VALUE_ERROR
+ }
+ Calendar date = DateUtil.getJavaCalendar(serialDate, false); // (XXX 1904-windowing not respected)
+ int weekday = date.get(Calendar.DAY_OF_WEEK); // => sunday = 1, monday = 2, ..., saturday = 7
+
+ // extract second parameter
+ int returnOption = 1; // default value
+ if (args.length == 2) {
+ ValueEval ve = OperandResolver.getSingleValue(args[1], srcRowIndex, srcColumnIndex);
+ if (ve == MissingArgEval.instance || ve == BlankEval.instance) {
+ return ErrorEval.NUM_ERROR; // EXCEL uses this and no VALUE_ERROR
+ }
+ returnOption = OperandResolver.coerceValueToInt(ve);
+ if (returnOption == 2) {
+ returnOption = 11; // both mean the same
+ }
+ } // if
+
+ // perform calculation
+ double result;
+ if (returnOption == 1) {
+ result = weekday;
+ // value 2 is handled above (as value 11)
+ } else if (returnOption == 3) {
+ result = (weekday + 6 - 1) % 7;
+ } else if (returnOption >= 11 && returnOption <= 17) {
+ result = (weekday + 6 - (returnOption - 10)) % 7 + 1; // rotate in the value range 1 to 7
+ } else {
+ return ErrorEval.NUM_ERROR; // EXCEL uses this and no VALUE_ERROR
+ }
+
+ return new NumberEval(result);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ } // evaluate()
+
+}
* @author Hack Kampbjorn (hak at 2mba.dk)
* @author Alex Jacoby (ajacoby at gmail.com)
* @author Pavel Krupets (pkrupets at palmtreebusiness dot com)
+ * @author Thies Wellpott
*/
public class DateUtil {
protected DateUtil() {
// no instances of this class
}
- private static final int SECONDS_PER_MINUTE = 60;
- private static final int MINUTES_PER_HOUR = 60;
- private static final int HOURS_PER_DAY = 24;
- private static final int SECONDS_PER_DAY = (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE);
+
+ public static final int SECONDS_PER_MINUTE = 60;
+ public static final int MINUTES_PER_HOUR = 60;
+ public static final int HOURS_PER_DAY = 24;
+ public static final int SECONDS_PER_DAY = (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE);
private static final int BAD_DATE = -1; // used to specify that date is invalid
- private static final long DAY_MILLISECONDS = SECONDS_PER_DAY * 1000L;
+ public static final long DAY_MILLISECONDS = SECONDS_PER_DAY * 1000L;
private static final Pattern TIME_SEPARATOR_PATTERN = Pattern.compile(":");
// elapsed time patterns: [h],[m] and [s]
private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]");
+ // only get this static info once (because operations are not really cheap)
+ private static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone("UTC");
+
+
/**
* Given a Date, converts it into a double representing its internal Excel representation,
* which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
* @return Java representation of the date, or null if date is not a valid Excel date
*/
public static Date getJavaDate(double date, boolean use1904windowing, TimeZone tz) {
- if (!isValidExcelDate(date)) {
- return null;
- }
- Calendar calendar;
- if (tz != null)
- calendar = new GregorianCalendar(tz);
- else
- calendar = new GregorianCalendar(); // using default time-zone
-
- int wholeDays = (int)Math.floor(date);
- int millisecondsInDay = (int)((date - wholeDays) * DAY_MILLISECONDS + 0.5);
- setCalendar(calendar, wholeDays, millisecondsInDay, use1904windowing);
- return calendar.getTime();
+ return getJavaCalendar(date, use1904windowing, tz).getTime();
}
/**
* Given an Excel date with either 1900 or 1904 date windowing,
* @see java.util.TimeZone
*/
public static Date getJavaDate(double date, boolean use1904windowing) {
- return getJavaDate(date, use1904windowing, (TimeZone)null);
+ return getJavaCalendar(date, use1904windowing).getTime();
}
+
+
public static void setCalendar(Calendar calendar, int wholeDays,
int millisecondsInDay, boolean use1904windowing) {
int startYear = 1900;
}
+ /**
+ * Get EXCEL date as Java Calendar (with default time zone).
+ * This is like {@link #getJavaDate(double, boolean)} but returns a Calendar object.
+ * @param date The Excel date.
+ * @param use1904windowing true if date uses 1904 windowing,
+ * or false if using 1900 date windowing.
+ * @return Java representation of the date, or null if date is not a valid Excel date
+ */
+ public static Calendar getJavaCalendar(double date, boolean use1904windowing) {
+ return getJavaCalendar(date, use1904windowing, (TimeZone)null);
+ }
+
+ /**
+ * Get EXCEL date as Java Calendar with UTC time zone.
+ * This is similar to {@link #getJavaDate(double, boolean)} but returns a
+ * Calendar object that has UTC as time zone, so no daylight saving hassle.
+ * @param date The Excel date.
+ * @param use1904windowing true if date uses 1904 windowing,
+ * or false if using 1900 date windowing.
+ * @return Java representation of the date in UTC, or null if date is not a valid Excel date
+ */
+ public static Calendar getJavaCalendarUTC(double date, boolean use1904windowing) {
+ return getJavaCalendar(date, use1904windowing, TIMEZONE_UTC);
+ }
+
+
+ /**
+ * Get EXCEL date as Java Calendar with given time zone.
+ * @see getJavaDate(double, TimeZone)
+ * @return Java representation of the date, or null if date is not a valid Excel date
+ */
+ public static Calendar getJavaCalendar(double date, boolean use1904windowing, TimeZone timeZone) {
+ if (!isValidExcelDate(date)) {
+ return null;
+ }
+ int wholeDays = (int)Math.floor(date);
+ int millisecondsInDay = (int)((date - wholeDays) * DAY_MILLISECONDS + 0.5);
+ Calendar calendar;
+ if (timeZone != null) {
+ calendar = new GregorianCalendar(timeZone);
+ } else {
+ calendar = new GregorianCalendar(); // using default time-zone
+ }
+ setCalendar(calendar, wholeDays, millisecondsInDay, use1904windowing);
+ return calendar;
+ }
+
+
/**
* Given a format ID and its format String, will check to see if the
* format represents a date format or not.
}
String fs = formatString;
- if (false) {
+ /*if (false) {
// Normalize the format string. The code below is equivalent
// to the following consecutive regexp replacements:
// The code above was reworked as suggested in bug 48425:
// simple loop is more efficient than consecutive regexp replacements.
- }
+ }*/
StringBuilder sb = new StringBuilder(fs.length());
for (int i = 0; i < fs.length(); i++) {
char c = fs.charAt(i);
String formulaText = "ADDRESS(1,2)";\r
confirmResult(fe, cell, formulaText, "$B$1");\r
\r
+ formulaText = "ADDRESS(1,2,)"; // with explicitly empty third parameter\r
+ confirmResult(fe, cell, formulaText, "$B$1");\r
+\r
formulaText = "ADDRESS(22,44)";\r
confirmResult(fe, cell, formulaText, "$AR$22");\r
\r
confirm("SECOND(40627.4860417)", 54);
}
+ public void testRounding() {
+ // 41484.999994200 = 23:59:59,499
+ // 41484.9999942129 = 23:59:59,500 (but sub-milliseconds are below 0.5 (0.49999453965575), XLS-second results in 59)
+ // 41484.9999942130 = 23:59:59,500 (sub-milliseconds are 0.50000334065408, XLS-second results in 00)
+
+ confirm("DAY(41484.999994200)", 29);
+ confirm("SECOND(41484.999994200)", 59);
+
+ confirm("DAY(41484.9999942129)", 29);
+ confirm("HOUR(41484.9999942129)", 23);
+ confirm("MINUTE(41484.9999942129)", 59);
+ confirm("SECOND(41484.9999942129)", 59);
+
+ confirm("DAY(41484.9999942130)", 30);
+ confirm("HOUR(41484.9999942130)", 0);
+ confirm("MINUTE(41484.9999942130)", 0);
+ confirm("SECOND(41484.9999942130)", 0);
+ }
+
+ public void testDaylightSaving() {
+ confirm("HOUR(41364.08263888890000)", 1); // 31.03.2013 01:59:00,000
+ confirm("HOUR(41364.08333333330000)", 2); // 31.03.2013 02:00:00,000 (this time does not exist in TZ CET, but EXCEL does not care)
+ confirm("HOUR(41364.08402777780000)", 2); // 31.03.2013 02:01:00,000
+ confirm("HOUR(41364.12430555560000)", 2); // 31.03.2013 02:59:00,000
+ confirm("HOUR(41364.12500000000000)", 3); // 31.03.2013 03:00:00,000
+ }
+
public void testBugDate() {
confirm("YEAR(0.0)", 1900);
confirm("MONTH(0.0)", 1);