--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula.functions;\r
+\r
+import java.util.Calendar;\r
+import java.util.Date;\r
+import java.util.GregorianCalendar;\r
+\r
+import org.apache.poi.ss.formula.OperationEvaluationContext;\r
+import org.apache.poi.ss.formula.eval.ErrorEval;\r
+import org.apache.poi.ss.formula.eval.EvaluationException;\r
+import org.apache.poi.ss.formula.eval.NumberEval;\r
+import org.apache.poi.ss.formula.eval.ValueEval;\r
+import org.apache.poi.ss.usermodel.DateUtil;\r
+\r
+/**\r
+ * Implementation for the Excel EOMONTH() function.<p/>\r
+ * <p/>\r
+ * EOMONTH() returns the date of the last day of a month..<p/>\r
+ * <p/>\r
+ * <b>Syntax</b>:<br/>\r
+ * <b>EOMONTH</b>(<b>start_date</b>,<b>months</b>)<p/>\r
+ * <p/>\r
+ * <b>start_date</b> is the starting date of the calculation\r
+ * <b>months</b> is the number of months to be added to <b>start_date</b>,\r
+ * to give a new date. For this new date, <b>EOMONTH</b> returns the date of\r
+ * the last day of the month. <b>months</b> may be positive (in the future),\r
+ * zero or negative (in the past).\r
+ */\r
+public class EOMonth implements FreeRefFunction {\r
+\r
+ public static final FreeRefFunction instance = new EOMonth();\r
+\r
+ @Override\r
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {\r
+ if (args.length != 2) {\r
+ return ErrorEval.VALUE_INVALID;\r
+ }\r
+\r
+ try {\r
+ double startDateAsNumber = NumericFunction.singleOperandEvaluate(args[0], ec.getRowIndex(), ec.getColumnIndex());\r
+ int months = (int) NumericFunction.singleOperandEvaluate(args[1], ec.getRowIndex(), ec.getColumnIndex());\r
+\r
+ // Excel treats date 0 as 1900-01-00; EOMONTH results in 1900-01-31\r
+ if (startDateAsNumber >= 0.0 && startDateAsNumber < 1.0) {\r
+ startDateAsNumber = 1.0;\r
+ }\r
+\r
+ Date startDate = DateUtil.getJavaDate(startDateAsNumber, false);\r
+\r
+ Calendar cal = new GregorianCalendar();\r
+ cal.setTime(startDate);\r
+ cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);\r
+ cal.set(Calendar.MILLISECOND, 0);\r
+\r
+ cal.add(Calendar.MONTH, months + 1);\r
+ cal.set(Calendar.DAY_OF_MONTH, 1);\r
+ cal.add(Calendar.DAY_OF_MONTH, -1);\r
+\r
+ return new NumberEval(DateUtil.getExcelDate(cal.getTime()));\r
+ } catch (EvaluationException e) {\r
+ return e.getErrorEval();\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula.functions;\r
+\r
+import org.apache.poi.ss.formula.eval.AreaEval;\r
+import org.apache.poi.ss.formula.eval.RefEval;\r
+import org.apache.poi.ss.formula.eval.ValueEval;\r
+\r
+final class RefEvalImplementation implements RefEval {\r
+\r
+ private final ValueEval value;\r
+\r
+ public RefEvalImplementation(ValueEval value) {\r
+ this.value = value;\r
+ }\r
+\r
+ @Override\r
+ public AreaEval offset(int relFirstRowIx, int relLastRowIx,\r
+ int relFirstColIx, int relLastColIx) {\r
+ throw new UnsupportedOperationException();\r
+ }\r
+\r
+ @Override\r
+ public ValueEval getInnerValueEval(int sheetIndex) {\r
+ return value;\r
+ }\r
+\r
+ @Override\r
+ public int getNumberOfSheets() {\r
+ return 1;\r
+ }\r
+\r
+ @Override\r
+ public int getFirstSheetIndex() {\r
+ return 0;\r
+ }\r
+\r
+ @Override\r
+ public int getLastSheetIndex() {\r
+ return 0;\r
+ }\r
+\r
+ @Override\r
+ public int getRow() {\r
+ throw new UnsupportedOperationException();\r
+ }\r
+\r
+ @Override\r
+ public int getColumn() {\r
+ throw new UnsupportedOperationException();\r
+ }\r
+\r
+}\r
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula.functions;\r
+\r
+import java.util.Calendar;\r
+import java.util.Date;\r
+\r
+import junit.framework.TestCase;\r
+import org.apache.poi.ss.formula.OperationEvaluationContext;\r
+\r
+import org.apache.poi.ss.formula.eval.BlankEval;\r
+import org.apache.poi.ss.formula.eval.ErrorEval;\r
+import org.apache.poi.ss.formula.eval.NumberEval;\r
+import org.apache.poi.ss.formula.eval.StringEval;\r
+import org.apache.poi.ss.formula.eval.ValueEval;\r
+import org.apache.poi.ss.usermodel.DateUtil;\r
+import org.apache.poi.ss.usermodel.ErrorConstants;\r
+\r
+public class TestEOMonth extends TestCase{\r
+\r
+ private static final double BAD_DATE = -1.0;\r
+\r
+ private static final double DATE_1900_01_01 = 1.0;\r
+ private static final double DATE_1900_01_31 = 31.0;\r
+ private static final double DATE_1900_02_28 = 59.0;\r
+ private static final double DATE_1902_09_26 = 1000.0;\r
+ private static final double DATE_1902_09_30 = 1004.0;\r
+ private static final double DATE_2034_06_09 = 49104.0;\r
+ private static final double DATE_2034_06_30 = 49125.0;\r
+ private static final double DATE_2034_07_31 = 49156.0;\r
+\r
+ private final FreeRefFunction eOMonth = EOMonth.instance;\r
+ private final OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 0, 0, 0, null);\r
+\r
+ public void testEOMonthProperValues() {\r
+ // verify some border-case combinations of startDate and month-increase\r
+ checkValue(DATE_1900_01_01, 0, DATE_1900_01_31);\r
+ checkValue(DATE_1900_01_01, 1, DATE_1900_02_28);\r
+ checkValue(DATE_1902_09_26, 0, DATE_1902_09_30);\r
+ checkValue(DATE_2034_06_09, 0, DATE_2034_06_30);\r
+ checkValue(DATE_2034_06_09, 1, DATE_2034_07_31);\r
+ }\r
+\r
+ public void testEOMonthBadDateValues() {\r
+ checkValue(0.0, -2, BAD_DATE);\r
+ checkValue(0.0, -3, BAD_DATE);\r
+ checkValue(DATE_1900_01_31, -1, BAD_DATE);\r
+ }\r
+\r
+ private void checkValue(double startDate, int monthInc, double expectedResult) {\r
+ NumberEval result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(startDate), new NumberEval(monthInc)}, ec);\r
+ assertEquals(expectedResult, result.getNumberValue());\r
+ }\r
+\r
+ public void testEOMonthZeroDate() {\r
+ NumberEval result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(0), new NumberEval(0)}, ec);\r
+ assertEquals("0 startDate is 1900-01-00", DATE_1900_01_31, result.getNumberValue());\r
+\r
+ result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(0), new NumberEval(1)}, ec);\r
+ assertEquals("0 startDate is 1900-01-00", DATE_1900_02_28, result.getNumberValue());\r
+ }\r
+\r
+ public void testEOMonthInvalidArguments() {\r
+ ValueEval result = eOMonth.evaluate(new ValueEval[] {new NumberEval(DATE_1902_09_26)}, ec);\r
+ assertTrue(result instanceof ErrorEval);\r
+ assertEquals(ErrorConstants.ERROR_VALUE, ((ErrorEval) result).getErrorCode());\r
+\r
+ result = eOMonth.evaluate(new ValueEval[] {new StringEval("a"), new StringEval("b")}, ec);\r
+ assertTrue(result instanceof ErrorEval);\r
+ assertEquals(ErrorConstants.ERROR_VALUE, ((ErrorEval) result).getErrorCode());\r
+ }\r
+\r
+ public void testEOMonthIncrease() {\r
+ checkOffset(new Date(), 2);\r
+ }\r
+\r
+ public void testEOMonthDecrease() {\r
+ checkOffset(new Date(), -2);\r
+ }\r
+\r
+ private void checkOffset(Date startDate, int offset) {\r
+ NumberEval result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(DateUtil.getExcelDate(startDate)), new NumberEval(offset)}, ec);\r
+ Date resultDate = DateUtil.getJavaDate(result.getNumberValue());\r
+ Calendar instance = Calendar.getInstance();\r
+ instance.setTime(startDate);\r
+ instance.add(Calendar.MONTH, offset);\r
+ instance.add(Calendar.MONTH, 1);\r
+ instance.set(Calendar.DAY_OF_MONTH, 1);\r
+ instance.add(Calendar.DAY_OF_MONTH, -1);\r
+ instance.set(Calendar.HOUR_OF_DAY, 0);\r
+ instance.set(Calendar.MINUTE, 0);\r
+ instance.set(Calendar.SECOND, 0);\r
+ instance.set(Calendar.MILLISECOND, 0);\r
+ assertEquals(instance.getTime(), resultDate);\r
+ }\r
+\r
+ public void testBug56688() {\r
+ NumberEval result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(DATE_1902_09_26), new RefEvalImplementation(new NumberEval(0))}, ec);\r
+ assertEquals(DATE_1902_09_30, result.getNumberValue());\r
+ }\r
+\r
+ public void testRefEvalStartDate() {\r
+ NumberEval result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new RefEvalImplementation(new NumberEval(DATE_1902_09_26)), new NumberEval(0)}, ec);\r
+ assertEquals(DATE_1902_09_30, result.getNumberValue());\r
+ }\r
+\r
+ public void testEOMonthBlankValueEval() {\r
+ NumberEval evaluate = (NumberEval) eOMonth.evaluate(new ValueEval[] {BlankEval.instance, new NumberEval(0)}, ec);\r
+ assertEquals("Blank is handled as 0", DATE_1900_01_31, evaluate.getNumberValue());\r
+ }\r
+\r
+ public void testEOMonthBlankRefValueEval() {\r
+ NumberEval result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new RefEvalImplementation(BlankEval.instance), new NumberEval(1)}, ec);\r
+ assertEquals("Blank is handled as 0",\r
+ DATE_1900_02_28, result.getNumberValue());\r
+\r
+ result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(1), new RefEvalImplementation(BlankEval.instance)}, ec);\r
+ assertEquals("Blank is handled as 0",\r
+ DATE_1900_01_31, result.getNumberValue());\r
+ }\r
+}\r