diff --git a/src/java/org/apache/poi/ss/formula/atp/ArgumentsEvaluator.java b/src/java/org/apache/poi/ss/formula/atp/ArgumentsEvaluator.java
index f97728c341..0c3f537dd6 100644
--- a/src/java/org/apache/poi/ss/formula/atp/ArgumentsEvaluator.java
+++ b/src/java/org/apache/poi/ss/formula/atp/ArgumentsEvaluator.java
@@ -17,8 +17,8 @@
package org.apache.poi.ss.formula.atp;
+import java.time.LocalDate;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.List;
import org.apache.poi.ss.formula.eval.AreaEvalBase;
@@ -27,6 +27,7 @@ import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.util.DateParser;
/**
* Evaluator for formula arguments.
@@ -59,7 +60,7 @@ final class ArgumentsEvaluator {
if (dVal != null) {
return dVal.doubleValue();
}
- Calendar date = DateParser.parseDate(strVal);
+ LocalDate date = DateParser.parseLocalDate(strVal);
return DateUtil.getExcelDate(date, false);
}
return OperandResolver.coerceValueToDouble(ve);
diff --git a/src/java/org/apache/poi/ss/formula/atp/YearFrac.java b/src/java/org/apache/poi/ss/formula/atp/YearFrac.java
index bb2134a951..a4f14b5436 100644
--- a/src/java/org/apache/poi/ss/formula/atp/YearFrac.java
+++ b/src/java/org/apache/poi/ss/formula/atp/YearFrac.java
@@ -17,7 +17,7 @@
package org.apache.poi.ss.formula.atp;
-import java.util.Calendar;
+import java.time.LocalDate;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
@@ -28,6 +28,8 @@ import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.util.DateParser;
+
/**
* Implementation of Excel 'Analysis ToolPak' function YEARFRAC()
*
@@ -90,7 +92,7 @@ final class YearFrac implements FreeRefFunction {
if (dVal != null) {
return dVal.doubleValue();
}
- Calendar date = DateParser.parseDate(strVal);
+ LocalDate date = DateParser.parseLocalDate(strVal);
return DateUtil.getExcelDate(date, false);
}
return OperandResolver.coerceValueToDouble(ve);
diff --git a/src/java/org/apache/poi/ss/formula/functions/DateValue.java b/src/java/org/apache/poi/ss/formula/functions/DateValue.java
index ea62ee156d..fa241a7389 100644
--- a/src/java/org/apache/poi/ss/formula/functions/DateValue.java
+++ b/src/java/org/apache/poi/ss/formula/functions/DateValue.java
@@ -17,23 +17,13 @@
package org.apache.poi.ss.formula.functions;
-import java.text.DateFormatSymbols;
-import java.time.DateTimeException;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.MatchResult;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
+import org.apache.poi.ss.util.DateParser;
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.NumberEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.usermodel.DateUtil;
-import org.apache.poi.util.LocaleUtil;
/**
* Implementation for the DATEVALUE() Excel function.
@@ -56,32 +46,6 @@ import org.apache.poi.util.LocaleUtil;
*/
public class DateValue extends Fixed1ArgFunction {
- private enum Format {
- YMD_DASHES("^(\\d{4})-(\\w+)-(\\d{1,2})$", "ymd"),
- DMY_DASHES("^(\\d{1,2})-(\\w+)-(\\d{4})$", "dmy"),
- MD_DASHES("^(\\w+)-(\\d{1,2})$", "md"),
- MDY_SLASHES("^(\\w+)/(\\d{1,2})/(\\d{4})$", "mdy"),
- YMD_SLASHES("^(\\d{4})/(\\w+)/(\\d{1,2})$", "ymd"),
- MD_SLASHES("^(\\w+)/(\\d{1,2})$", "md");
-
- private Pattern pattern;
- private boolean hasYear;
- private int yearIndex;
- private int monthIndex;
- private int dayIndex;
-
- Format(String patternString, String groupOrder) {
- this.pattern = Pattern.compile(patternString);
- this.hasYear = groupOrder.contains("y");
- if (hasYear) {
- yearIndex = groupOrder.indexOf("y");
- }
- monthIndex = groupOrder.indexOf("m");
- dayIndex = groupOrder.indexOf("d");
- }
-
- }
-
@Override
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval dateTextArg) {
try {
@@ -92,45 +56,9 @@ public class DateValue extends Fixed1ArgFunction {
return BlankEval.instance;
}
- for (Format format : Format.values()) {
- Matcher matcher = format.pattern.matcher(dateText);
- if (matcher.find()) {
- MatchResult matchResult = matcher.toMatchResult();
- List groups = new ArrayList<>();
- for (int i = 1; i <= matchResult.groupCount(); ++i) {
- groups.add(matchResult.group(i));
- }
- int year = format.hasYear
- ? Integer.parseInt(groups.get(format.yearIndex))
- : LocalDate.now(LocaleUtil.getUserTimeZone().toZoneId()).getYear();
- int month = parseMonth(groups.get(format.monthIndex));
- int day = Integer.parseInt(groups.get(format.dayIndex));
- return new NumberEval(DateUtil.getExcelDate(LocalDate.of(year, month, day)));
-
- }
- }
- } catch (DateTimeException e) {
- return ErrorEval.VALUE_INVALID;
+ return new NumberEval(DateUtil.getExcelDate(DateParser.parseLocalDate(dateText)));
} catch (EvaluationException e) {
return e.getErrorEval();
}
-
- return ErrorEval.VALUE_INVALID;
- }
-
- private int parseMonth(String monthPart) {
- try {
- return Integer.parseInt(monthPart);
- } catch (NumberFormatException ignored) {
- }
-
-
- String[] months = DateFormatSymbols.getInstance(LocaleUtil.getUserLocale()).getMonths();
- for (int month = 0; month < months.length; ++month) {
- if (months[month].toLowerCase(LocaleUtil.getUserLocale()).startsWith(monthPart.toLowerCase(LocaleUtil.getUserLocale()))) {
- return month + 1;
- }
- }
- return -1;
}
}
diff --git a/src/java/org/apache/poi/ss/util/DateParser.java b/src/java/org/apache/poi/ss/util/DateParser.java
new file mode 100644
index 0000000000..292630006d
--- /dev/null
+++ b/src/java/org/apache/poi/ss/util/DateParser.java
@@ -0,0 +1,133 @@
+/* ====================================================================
+ 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.util;
+
+import java.text.DateFormatSymbols;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.util.LocaleUtil;
+
+/**
+ * Parser for java dates.
+ */
+public class DateParser {
+ private DateParser() {
+ // enforcing singleton
+ }
+
+
+ private enum Format {
+ YMD_DASHES("^(\\d{4})-(\\w+)-(\\d{1,2})( .*)?$", "ymd"),
+ DMY_DASHES("^(\\d{1,2})-(\\w+)-(\\d{4})( .*)?$", "dmy"),
+ MD_DASHES("^(\\w+)-(\\d{1,2})( .*)?$", "md"),
+ MDY_SLASHES("^(\\w+)/(\\d{1,2})/(\\d{4})( .*)?$", "mdy"),
+ YMD_SLASHES("^(\\d{4})/(\\w+)/(\\d{1,2})( .*)?$", "ymd"),
+ MD_SLASHES("^(\\w+)/(\\d{1,2})( .*)?$", "md");
+
+ private Pattern pattern;
+ private boolean hasYear;
+ private int yearIndex;
+ private int monthIndex;
+ private int dayIndex;
+
+ Format(String patternString, String groupOrder) {
+ this.pattern = Pattern.compile(patternString);
+ this.hasYear = groupOrder.contains("y");
+ if (hasYear) {
+ yearIndex = groupOrder.indexOf("y");
+ }
+ monthIndex = groupOrder.indexOf("m");
+ dayIndex = groupOrder.indexOf("d");
+ }
+
+ }
+
+ private static int parseMonth(String monthPart) {
+ try {
+ return Integer.parseInt(monthPart);
+ } catch (NumberFormatException ignored) {
+ }
+
+
+ String[] months = DateFormatSymbols.getInstance(LocaleUtil.getUserLocale()).getMonths();
+ for (int month = 0; month < months.length; ++month) {
+ if (months[month].toLowerCase(LocaleUtil.getUserLocale()).startsWith(monthPart.toLowerCase(LocaleUtil.getUserLocale()))) {
+ return month + 1;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Parses a date from a string.
+ *
+ * @param strVal a string with a date pattern.
+ * @return a date parsed from argument.
+ * @throws EvaluationException exception upon parsing.
+ */
+ public static LocalDate parseLocalDate(String strVal) throws EvaluationException {
+ for (Format format : Format.values()) {
+ Matcher matcher = format.pattern.matcher(strVal);
+ if (matcher.find()) {
+ MatchResult matchResult = matcher.toMatchResult();
+ List groups = new ArrayList<>();
+ for (int i = 1; i <= matchResult.groupCount(); ++i) {
+ groups.add(matchResult.group(i));
+ }
+ int year = format.hasYear
+ ? Integer.parseInt(groups.get(format.yearIndex))
+ : LocalDate.now(LocaleUtil.getUserTimeZone().toZoneId()).getYear();
+ int month = parseMonth(groups.get(format.monthIndex));
+ int day = Integer.parseInt(groups.get(format.dayIndex));
+ return LocalDate.of(year, month, day);
+
+ }
+ }
+
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+
+ public static Calendar parseDate(String strVal) throws EvaluationException {
+ LocalDate date = parseLocalDate(strVal);
+ return makeDate(date.getYear(), date.getMonthValue(), date.getDayOfMonth());
+ }
+
+ /**
+ * @param month 1-based
+ */
+ private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
+ if (month < 1 || month > 12) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ Calendar cal = LocaleUtil.getLocaleCalendar(year, month - 1, 1, 0, 0, 0);
+ if (day < 1 || day > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ cal.set(Calendar.DAY_OF_MONTH, day);
+ return cal;
+ }
+
+}
diff --git a/src/testcases/org/apache/poi/ss/util/TestDateParser.java b/src/testcases/org/apache/poi/ss/util/TestDateParser.java
new file mode 100644
index 0000000000..d20d234ef7
--- /dev/null
+++ b/src/testcases/org/apache/poi/ss/util/TestDateParser.java
@@ -0,0 +1,79 @@
+/* ====================================================================
+ 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.Calendar;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.util.LocaleUtil;
+import org.junit.Test;
+
+public class TestDateParser {
+ @Test
+ public void testFailWhenNoDate() {
+ try {
+ DateParser.parseDate("potato");
+ fail("Shouldn't parse potato!");
+ } catch (EvaluationException e) {
+ assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
+ }
+ }
+
+ @Test
+ public void testFailWhenLooksLikeDateButItIsnt() {
+ try {
+ DateParser.parseDate("potato/cucumber/banana");
+ fail("Shouldn't parse this thing!");
+ } catch (EvaluationException e) {
+ assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
+ }
+ }
+
+ @Test
+ public void testFailWhenIsInvalidDate() {
+ try {
+ DateParser.parseDate("13/13/13");
+ fail("Shouldn't parse this thing!");
+ } catch (EvaluationException e) {
+ assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
+ }
+ }
+
+ @Test
+ public void testShouldParseValidDate() throws EvaluationException {
+ Calendar expDate = LocaleUtil.getLocaleCalendar(1984, Calendar.OCTOBER, 20);
+ Calendar actDate = DateParser.parseDate("1984/10/20");
+ assertEquals("Had: " + expDate.getTime() + " and " + actDate.getTime() + "/" +
+ expDate.getTimeInMillis() + "ms and " + actDate.getTimeInMillis() + "ms",
+ expDate, actDate);
+ }
+
+ @Test
+ public void testShouldIgnoreTimestamp() throws EvaluationException {
+ Calendar expDate = LocaleUtil.getLocaleCalendar(1984, Calendar.OCTOBER, 20);
+ Calendar actDate = DateParser.parseDate("1984/10/20 12:34:56");
+ assertEquals("Had: " + expDate.getTime() + " and " + actDate.getTime() + "/" +
+ expDate.getTimeInMillis() + "ms and " + actDate.getTimeInMillis() + "ms",
+ expDate, actDate);
+ }
+
+}