mirror of https://github.com/apache/poi.git
[github-189] Move date parsing logic to DateParser. Thanks to Miłosz Rembisz. This closes #189
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1880777 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
8329582036
commit
e86ba86f2d
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
package org.apache.poi.ss.formula.atp;
|
package org.apache.poi.ss.formula.atp;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.poi.ss.formula.eval.AreaEvalBase;
|
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.StringEval;
|
||||||
import org.apache.poi.ss.formula.eval.ValueEval;
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
import org.apache.poi.ss.usermodel.DateUtil;
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
import org.apache.poi.ss.util.DateParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluator for formula arguments.
|
* Evaluator for formula arguments.
|
||||||
|
@ -59,7 +60,7 @@ final class ArgumentsEvaluator {
|
||||||
if (dVal != null) {
|
if (dVal != null) {
|
||||||
return dVal.doubleValue();
|
return dVal.doubleValue();
|
||||||
}
|
}
|
||||||
Calendar date = DateParser.parseDate(strVal);
|
LocalDate date = DateParser.parseLocalDate(strVal);
|
||||||
return DateUtil.getExcelDate(date, false);
|
return DateUtil.getExcelDate(date, false);
|
||||||
}
|
}
|
||||||
return OperandResolver.coerceValueToDouble(ve);
|
return OperandResolver.coerceValueToDouble(ve);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package org.apache.poi.ss.formula.atp;
|
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.ErrorEval;
|
||||||
import org.apache.poi.ss.formula.eval.EvaluationException;
|
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.functions.FreeRefFunction;
|
||||||
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
import org.apache.poi.ss.usermodel.DateUtil;
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
import org.apache.poi.ss.util.DateParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br>
|
* Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br>
|
||||||
*
|
*
|
||||||
|
@ -90,7 +92,7 @@ final class YearFrac implements FreeRefFunction {
|
||||||
if (dVal != null) {
|
if (dVal != null) {
|
||||||
return dVal.doubleValue();
|
return dVal.doubleValue();
|
||||||
}
|
}
|
||||||
Calendar date = DateParser.parseDate(strVal);
|
LocalDate date = DateParser.parseLocalDate(strVal);
|
||||||
return DateUtil.getExcelDate(date, false);
|
return DateUtil.getExcelDate(date, false);
|
||||||
}
|
}
|
||||||
return OperandResolver.coerceValueToDouble(ve);
|
return OperandResolver.coerceValueToDouble(ve);
|
||||||
|
|
|
@ -17,23 +17,13 @@
|
||||||
|
|
||||||
package org.apache.poi.ss.formula.functions;
|
package org.apache.poi.ss.formula.functions;
|
||||||
|
|
||||||
import java.text.DateFormatSymbols;
|
import org.apache.poi.ss.util.DateParser;
|
||||||
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.formula.eval.BlankEval;
|
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.EvaluationException;
|
||||||
import org.apache.poi.ss.formula.eval.NumberEval;
|
import org.apache.poi.ss.formula.eval.NumberEval;
|
||||||
import org.apache.poi.ss.formula.eval.OperandResolver;
|
import org.apache.poi.ss.formula.eval.OperandResolver;
|
||||||
import org.apache.poi.ss.formula.eval.ValueEval;
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
import org.apache.poi.ss.usermodel.DateUtil;
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
import org.apache.poi.util.LocaleUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation for the DATEVALUE() Excel function.<p>
|
* Implementation for the DATEVALUE() Excel function.<p>
|
||||||
|
@ -56,32 +46,6 @@ import org.apache.poi.util.LocaleUtil;
|
||||||
*/
|
*/
|
||||||
public class DateValue extends Fixed1ArgFunction {
|
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
|
@Override
|
||||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval dateTextArg) {
|
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval dateTextArg) {
|
||||||
try {
|
try {
|
||||||
|
@ -92,45 +56,9 @@ public class DateValue extends Fixed1ArgFunction {
|
||||||
return BlankEval.instance;
|
return BlankEval.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Format format : Format.values()) {
|
return new NumberEval(DateUtil.getExcelDate(DateParser.parseLocalDate(dateText)));
|
||||||
Matcher matcher = format.pattern.matcher(dateText);
|
|
||||||
if (matcher.find()) {
|
|
||||||
MatchResult matchResult = matcher.toMatchResult();
|
|
||||||
List<String> 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;
|
|
||||||
} catch (EvaluationException e) {
|
} catch (EvaluationException e) {
|
||||||
return e.getErrorEval();
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<String> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue