From a314d6501dd4266ad197847f8879ac0a119ec930 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Sat, 29 Sep 2018 19:36:51 +0000 Subject: [PATCH] add support for monthname function; implement better string to date/time conversions git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1202 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/expr/EvalContext.java | 15 +- .../jackcess/expr/LocaleContext.java | 41 ++++ .../jackcess/expr/TemporalConfig.java | 72 ++++++- .../jackcess/expr/package-info.java | 4 +- .../impl/expr/DefaultDateFunctions.java | 57 ++++- .../impl/expr/ExpressionTokenizer.java | 202 ++++++++++-------- .../jackcess/impl/expr/Expressionator.java | 3 +- .../jackcess/impl/expr/StringValue.java | 1 + .../jackcess/impl/expr/ValueSupport.java | 8 +- .../jackcess/PropertyExpressionTest.java | 27 ++- .../impl/expr/DefaultFunctionsTest.java | 9 + 11 files changed, 319 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java index a7e0ecd..c82fe43 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java @@ -16,7 +16,6 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; -import java.text.SimpleDateFormat; import javax.script.Bindings; /** @@ -26,20 +25,8 @@ import javax.script.Bindings; * * @author James Ahlborn */ -public interface EvalContext +public interface EvalContext extends LocaleContext { - /** - * @return the currently configured TemporalConfig (from the - * {@link EvalConfig}) - */ - public TemporalConfig getTemporalConfig(); - - /** - * @return an appropriately configured (i.e. TimeZone and other date/time - * flags) SimpleDateFormat for the given format. - */ - public SimpleDateFormat createDateFormat(String formatStr); - /** * @param seed the seed for the random value, following the rules for the * "Rnd" function diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java new file mode 100644 index 0000000..8a9a649 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java @@ -0,0 +1,41 @@ +/* +Copyright (c) 2018 James Ahlborn + +Licensed 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 com.healthmarketscience.jackcess.expr; + +import java.text.SimpleDateFormat; + +/** + * LocaleContext encapsulates all shared localization state for expression + * parsing and evaluation. + * + * @author James Ahlborn + */ +public interface LocaleContext +{ + /** + * @return the currently configured TemporalConfig (from the + * {@link EvalConfig}) + */ + public TemporalConfig getTemporalConfig(); + + /** + * @return an appropriately configured (i.e. TimeZone and other date/time + * flags) SimpleDateFormat for the given format. + */ + public SimpleDateFormat createDateFormat(String formatStr); + +} diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java b/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java index 2ea1a12..d7061ca 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java @@ -16,6 +16,9 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; +import java.text.DateFormatSymbols; +import java.util.Locale; + /** * A TemporalConfig encapsulates date/time formatting options for expression * evaluation. The default {@link #US_TEMPORAL_CONFIG} instance provides US @@ -33,7 +36,45 @@ public class TemporalConfig /** default implementation which is configured for the US locale */ public static final TemporalConfig US_TEMPORAL_CONFIG = new TemporalConfig( - US_DATE_FORMAT, US_TIME_FORMAT_12, US_TIME_FORMAT_24, '/', ':'); + US_DATE_FORMAT, US_TIME_FORMAT_12, US_TIME_FORMAT_24, '/', ':', Locale.US); + + public enum Type { + DATE, TIME, DATE_TIME, TIME_12, TIME_24, DATE_TIME_12, DATE_TIME_24; + + public Type getDefaultType() { + switch(this) { + case DATE: + return DATE; + case TIME: + case TIME_12: + case TIME_24: + return TIME; + case DATE_TIME: + case DATE_TIME_12: + case DATE_TIME_24: + return DATE_TIME; + default: + throw new RuntimeException("invalid type " + this); + } + } + + public Value.Type getValueType() { + switch(this) { + case DATE: + return Value.Type.DATE; + case TIME: + case TIME_12: + case TIME_24: + return Value.Type.TIME; + case DATE_TIME: + case DATE_TIME_12: + case DATE_TIME_24: + return Value.Type.DATE_TIME; + default: + throw new RuntimeException("invalid type " + this); + } + } + } private final String _dateFormat; private final String _timeFormat12; @@ -42,6 +83,7 @@ public class TemporalConfig private final char _timeSeparator; private final String _dateTimeFormat12; private final String _dateTimeFormat24; + private final DateFormatSymbols _symbols; /** * Instantiates a new TemporalConfig with the given configuration. Note @@ -63,7 +105,7 @@ public class TemporalConfig */ public TemporalConfig(String dateFormat, String timeFormat12, String timeFormat24, char dateSeparator, - char timeSeparator) + char timeSeparator, Locale locale) { _dateFormat = dateFormat; _timeFormat12 = timeFormat12; @@ -72,6 +114,7 @@ public class TemporalConfig _timeSeparator = timeSeparator; _dateTimeFormat12 = _dateFormat + " " + _timeFormat12; _dateTimeFormat24 = _dateFormat + " " + _timeFormat24; + _symbols = DateFormatSymbols.getInstance(locale); } public String getDateFormat() { @@ -113,4 +156,29 @@ public class TemporalConfig public char getTimeSeparator() { return _timeSeparator; } + + public String getDateTimeFormat(Type type) { + switch(type) { + case DATE: + return getDefaultDateFormat(); + case TIME: + return getDefaultTimeFormat(); + case DATE_TIME: + return getDefaultDateTimeFormat(); + case TIME_12: + return getTimeFormat12(); + case TIME_24: + return getTimeFormat24(); + case DATE_TIME_12: + return getDateTimeFormat12(); + case DATE_TIME_24: + return getDateTimeFormat24(); + default: + throw new IllegalArgumentException("unknown date/time type " + type); + } + } + + public DateFormatSymbols getDateFormatSymbols() { + return _symbols; + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java index 416c96e..05c72e6 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java @@ -138,7 +138,7 @@ limitations under the License. * HourY * MinuteY * MonthY - * MonthName + * MonthNameY * NowY * SecondY * TimeY @@ -146,7 +146,7 @@ limitations under the License. * TimeSerialY * TimeValueY * WeekdayY - * WeekdayName + * WeekdayNameY * YearY * * diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java index c8ffc0f..b062a05 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java @@ -26,6 +26,7 @@ import java.util.Date; import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.EvalException; import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.ColumnImpl; import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*; @@ -204,6 +205,27 @@ public class DefaultDateFunctions } }); + public static final Function MONTHNAME = registerFunc(new FuncVar("MonthName", 1, 2) { + @Override + protected Value evalVar(EvalContext ctx, Value[] params) { + Value param1 = params[0]; + if(param1 == null) { + return null; + } + // convert from 1 based to 0 based value + int month = param1.getAsLongInt() - 1; + + boolean abbreviate = getOptionalBooleanParam(params, 1); + + DateFormatSymbols syms = ctx.createDateFormat( + ctx.getTemporalConfig().getDateFormat()).getDateFormatSymbols(); + String[] monthNames = (abbreviate ? + syms.getShortMonths() : syms.getMonths()); + // note, the array is 1 based + return ValueSupport.toValue(monthNames[month]); + } + }); + public static final Function DAY = registerFunc(new Func1NullIsNull("Day") { @Override protected Value eval1(EvalContext ctx, Value param1) { @@ -236,10 +258,7 @@ public class DefaultDateFunctions } int weekday = param1.getAsLongInt(); - boolean abbreviate = false; - if(params.length > 1) { - abbreviate = params[1].getAsBoolean(); - } + boolean abbreviate = getOptionalBooleanParam(params, 1); int firstDay = getFirstDayParam(params, 2); @@ -280,14 +299,31 @@ public class DefaultDateFunctions } if(type == Value.Type.STRING) { - // see if we can coerce to date/time - // FIXME use ExpressionatorTokenizer to detect explicit date/time format + // see if we can coerce to date/time or double + String valStr = param.getAsString(); + TemporalConfig.Type valTempType = ExpressionTokenizer.determineDateType( + valStr, ctx); + + if(valTempType != null) { + + try { + DateFormat parseDf = ExpressionTokenizer.createParseDateFormat( + valTempType, ctx); + Date dateVal = ExpressionTokenizer.parseComplete(parseDf, valStr); + return ValueSupport.toValue(ctx, valTempType.getValueType(), + dateVal); + } catch(java.text.ParseException pe) { + // not a valid date string, not a date/time + return null; + } + } + // see if string can be coerced to number try { return numberToDateValue(ctx, param.getAsDouble()); } catch(NumberFormatException ignored) { - // not a number + // not a number, not a date/time return null; } } @@ -358,4 +394,11 @@ public class DefaultDateFunctions } return firstDay; } + + private static boolean getOptionalBooleanParam(Value[] params, int idx) { + if(params.length > idx) { + return params[idx].getAsBoolean(); + } + return false; + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java index f14f934..c75fae0 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; +import java.util.EnumMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -32,9 +33,10 @@ import java.util.Set; import java.util.TimeZone; import static com.healthmarketscience.jackcess.impl.expr.Expressionator.*; -import com.healthmarketscience.jackcess.expr.Value; -import com.healthmarketscience.jackcess.expr.TemporalConfig; +import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.ParseException; +import com.healthmarketscience.jackcess.expr.TemporalConfig; +import com.healthmarketscience.jackcess.expr.Value; /** @@ -290,9 +292,33 @@ class ExpressionTokenizer private static Token parseDateLiteral(ExprBuf buf) { - TemporalConfig cfg = buf.getTemporalConfig(); String dateStr = parseDateLiteralString(buf); + TemporalConfig.Type type = determineDateType( + dateStr, buf.getContext()); + if(type == null) { + throw new ParseException("Invalid date/time literal " + dateStr + + " " + buf); + } + + // note that although we may parse in the time "24" format, we will + // display as the default time format + DateFormat parseDf = buf.getDateTimeFormat(type); + DateFormat df = buf.getDateTimeFormat(type.getDefaultType()); + + try { + return new Token(TokenType.LITERAL, parseComplete(parseDf, dateStr), + dateStr, type.getValueType(), df); + } catch(java.text.ParseException pe) { + throw new ParseException( + "Invalid date/time literal " + dateStr + " " + buf, pe); + } + } + + static TemporalConfig.Type determineDateType( + String dateStr, LocaleContext ctx) + { + TemporalConfig cfg = ctx.getTemporalConfig(); boolean hasDate = (dateStr.indexOf(cfg.getDateSeparator()) >= 0); boolean hasTime = (dateStr.indexOf(cfg.getTimeSeparator()) >= 0); boolean hasAmPm = false; @@ -306,29 +332,65 @@ class ExpressionTokenizer PM_SUFFIX, 0, AMPM_SUFFIX_LEN))); } - DateFormat sdf = null; - Value.Type valType = null; - if(hasDate && hasTime) { - sdf = (hasAmPm ? buf.getDateTimeFormat12() : buf.getDateTimeFormat24()); - valType = Value.Type.DATE_TIME; - } else if(hasDate) { - sdf = buf.getDateFormat(); - valType = Value.Type.DATE; + if(hasDate) { + if(hasTime) { + return (hasAmPm ? TemporalConfig.Type.DATE_TIME_12 : + TemporalConfig.Type.DATE_TIME_24); + } + return TemporalConfig.Type.DATE; } else if(hasTime) { - sdf = (hasAmPm ? buf.getTimeFormat12() : buf.getTimeFormat24()); - valType = Value.Type.TIME; - } else { - throw new ParseException("Invalid date time literal " + dateStr + - " " + buf); + return (hasAmPm ? TemporalConfig.Type.TIME_12 : + TemporalConfig.Type.TIME_24); } + return null; + } - try { - return new Token(TokenType.LITERAL, sdf.parse(dateStr), dateStr, valType, - sdf); - } catch(java.text.ParseException pe) { - throw new ParseException( - "Invalid date time literal " + dateStr + " " + buf, pe); + static DateFormat createParseDateFormat(TemporalConfig.Type type, + LocaleContext ctx) + { + TemporalConfig cfg = ctx.getTemporalConfig(); + DateFormat df = ctx.createDateFormat(cfg.getDateTimeFormat(type)); + + TemporalConfig.Type parseType = null; + switch(type) { + case TIME: + parseType = TemporalConfig.Type.DATE_TIME; + break; + case TIME_12: + parseType = TemporalConfig.Type.DATE_TIME_12; + break; + case TIME_24: + parseType = TemporalConfig.Type.DATE_TIME_24; + break; + default: + } + + if(parseType != null) { + // we need to use a special DateFormat impl which handles parsing + // separately from formatting + String baseDate = getBaseDatePrefix(ctx); + DateFormat parseDf = ctx.createDateFormat( + cfg.getDateTimeFormat(parseType)); + df = new TimeFormat(parseDf, df, baseDate); + } + + return df; + } + + private static String getBaseDatePrefix(LocaleContext ctx) { + String dateFmt = ctx.getTemporalConfig().getDateFormat(); + String baseDate = BASE_DATE; + if(!BASE_DATE_FMT.equals(dateFmt)) { + try { + // need to reformat the base date to the relevant date format + DateFormat parseDf = ctx.createDateFormat(BASE_DATE_FMT); + DateFormat df = ctx.createDateFormat(dateFmt); + baseDate = df.format(parseComplete(parseDf, baseDate)); + } catch(Exception e) { + throw new ParseException("Could not parse base date", e); + } } + return baseDate + " "; } private static Token maybeParseNumberLiteral(char firstChar, ExprBuf buf) { @@ -426,17 +488,29 @@ class ExpressionTokenizer return new AbstractMap.SimpleImmutableEntry(a, b); } + static Date parseComplete(DateFormat df, String str) + throws java.text.ParseException + { + // the java parsers will parse "successfully" even if there is leftover + // information. we only want to consider a parse operation successful if + // it parses the entire string (ignoring surrounding whitespace) + str = str.trim(); + ParsePosition pp = new ParsePosition(0); + Object d = df.parse(str, pp); + if(pp.getIndex() < str.length()) { + throw new java.text.ParseException("Failed parsing '" + str + "'", + pp.getIndex()); + } + return (Date)d; + } + private static final class ExprBuf { private final String _str; private final ParseContext _ctx; private int _pos; - private DateFormat _dateFmt; - private DateFormat _timeFmt12; - private DateFormat _dateTimeFmt12; - private DateFormat _timeFmt24; - private DateFormat _dateTimeFmt24; - private String _baseDate; + private final Map _dateTimeFmts = + new EnumMap(TemporalConfig.Type.class); private final StringBuilder _scratch = new StringBuilder(); private ExprBuf(String str, ParseContext ctx) { @@ -484,69 +558,17 @@ class ExpressionTokenizer return _scratch; } - public TemporalConfig getTemporalConfig() { - return _ctx.getTemporalConfig(); + public ParseContext getContext() { + return _ctx; } - public DateFormat getDateFormat() { - if(_dateFmt == null) { - _dateFmt = _ctx.createDateFormat(getTemporalConfig().getDateFormat()); + public DateFormat getDateTimeFormat(TemporalConfig.Type type) { + DateFormat df = _dateTimeFmts.get(type); + if(df == null) { + df = createParseDateFormat(type, _ctx); + _dateTimeFmts.put(type, df); } - return _dateFmt; - } - - public DateFormat getTimeFormat12() { - if(_timeFmt12 == null) { - _timeFmt12 = new TimeFormat( - getDateTimeFormat12(), _ctx.createDateFormat( - getTemporalConfig().getTimeFormat12()), - getBaseDate()); - } - return _timeFmt12; - } - - public DateFormat getDateTimeFormat12() { - if(_dateTimeFmt12 == null) { - _dateTimeFmt12 = _ctx.createDateFormat( - getTemporalConfig().getDateTimeFormat12()); - } - return _dateTimeFmt12; - } - - public DateFormat getTimeFormat24() { - if(_timeFmt24 == null) { - _timeFmt24 = new TimeFormat( - getDateTimeFormat24(), _ctx.createDateFormat( - getTemporalConfig().getTimeFormat24()), - getBaseDate()); - } - return _timeFmt24; - } - - public DateFormat getDateTimeFormat24() { - if(_dateTimeFmt24 == null) { - _dateTimeFmt24 = _ctx.createDateFormat( - getTemporalConfig().getDateTimeFormat24()); - } - return _dateTimeFmt24; - } - - private String getBaseDate() { - if(_baseDate == null) { - String dateFmt = getTemporalConfig().getDateFormat(); - String baseDate = BASE_DATE; - if(!BASE_DATE_FMT.equals(dateFmt)) { - try { - // need to reformat the base date to the relevant date format - DateFormat df = _ctx.createDateFormat(BASE_DATE_FMT); - baseDate = getDateFormat().format(df.parse(baseDate)); - } catch(Exception e) { - throw new ParseException("Could not parse base date", e); - } - } - _baseDate = baseDate + " "; - } - return _baseDate; + return df; } @Override @@ -618,6 +640,10 @@ class ExpressionTokenizer } } + /** + * Special date/time format which will parse time-only strings "correctly" + * according to how access handles time-only values. + */ private static final class TimeFormat extends DateFormat { private static final long serialVersionUID = 0L; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java index f948d15..d53d6d3 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -41,6 +41,7 @@ import com.healthmarketscience.jackcess.expr.Expression; import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.FunctionLookup; import com.healthmarketscience.jackcess.expr.Identifier; +import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.ParseException; import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; @@ -65,7 +66,7 @@ public class Expressionator DEFAULT_VALUE, EXPRESSION, FIELD_VALIDATOR, RECORD_VALIDATOR; } - public interface ParseContext { + public interface ParseContext extends LocaleContext { public TemporalConfig getTemporalConfig(); public SimpleDateFormat createDateFormat(String formatStr); public FunctionLookup getFunctionLookup(); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java index 51b9c6a..781faf4 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java @@ -90,6 +90,7 @@ public class StringValue extends BaseValue if(tmpVal.charAt(0) != NUMBER_BASE_PREFIX) { // parse using standard numeric support + // FIXME, this should handle grouping separator, but needs ctx _num = ValueSupport.normalize(new BigDecimal(tmpVal)); return (BigDecimal)_num; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java index 279a4f0..b0924f3 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java @@ -20,7 +20,7 @@ import java.math.BigDecimal; import java.text.DateFormat; import java.util.Date; -import com.healthmarketscience.jackcess.expr.EvalContext; +import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.EvalException; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.ColumnImpl; @@ -90,7 +90,7 @@ public class ValueSupport dd, fmt.getCalendar())), fmt); } - public static Value toValue(EvalContext ctx, Value.Type type, Date d) { + public static Value toValue(LocaleContext ctx, Value.Type type, Date d) { return toValue(type, d, getDateFormatForType(ctx, type)); } @@ -107,7 +107,7 @@ public class ValueSupport } } - static Value toDateValue(EvalContext ctx, Value.Type type, double v, + static Value toDateValue(LocaleContext ctx, Value.Type type, double v, Value param1, Value param2) { DateFormat fmt = null; @@ -124,7 +124,7 @@ public class ValueSupport return toValue(type, d, fmt); } - static DateFormat getDateFormatForType(EvalContext ctx, Value.Type type) { + static DateFormat getDateFormatForType(LocaleContext ctx, Value.Type type) { String fmtStr = null; switch(type) { case DATE: diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java index e5d7295..d9f4932 100644 --- a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java @@ -16,7 +16,9 @@ limitations under the License. package com.healthmarketscience.jackcess; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.script.Bindings; import javax.script.SimpleBindings; @@ -87,6 +89,26 @@ public class PropertyExpressionTest extends TestCase assertTable(expectedRows, t); + setProp(t, "data2", PropertyMap.REQUIRED_PROP, true); + + t.addRow(Column.AUTO_NUMBER, "blah", 13); + t.addRow(Column.AUTO_NUMBER, "blah", null); + + expectedRows = new ArrayList(expectedRows); + expectedRows.add( + createExpectedRow( + "id", 4, + "data1", "blah", + "data2", 13)); + expectedRows.add( + createExpectedRow( + "id", 5, + "data1", "blah", + "data2", 42)); + + assertTable(expectedRows, t); + + db.close(); } } @@ -274,7 +296,8 @@ public class PropertyExpressionTest extends TestCase { TemporalConfig tempConf = new TemporalConfig("yyyy/M/d", "hh.mm.ss a", - "HH.mm.ss", '/', '.'); + "HH.mm.ss", '/', '.', + Locale.US); FunctionLookup lookup = new FunctionLookup() { public Function getFunction(String name) { @@ -327,7 +350,7 @@ public class PropertyExpressionTest extends TestCase } private static void setProp(Table t, String colName, String propName, - String propVal) throws Exception { + Object propVal) throws Exception { PropertyMap props = t.getColumn(colName).getProperties(); if(propVal != null) { props.put(propName, propVal); diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java index 6a36199..4051fde 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java @@ -232,10 +232,19 @@ public class DefaultFunctionsTest extends TestCase assertEquals("1/2/2003", eval("=CStr(DateValue(#01/02/2003 7:00:00 AM#))")); assertEquals("7:00:00 AM", eval("=CStr(TimeValue(#01/02/2003 7:00:00 AM#))")); + assertEquals("1:10:00 PM", eval("=CStr(#13:10:00#)")); + assertEquals(2003, eval("=Year(#01/02/2003 7:00:00 AM#)")); assertEquals(1, eval("=Month(#01/02/2003 7:00:00 AM#)")); assertEquals(2, eval("=Day(#01/02/2003 7:00:00 AM#)")); + assertEquals(2003, eval("=Year('01/02/2003 7:00:00 AM')")); + assertEquals(1899, eval("=Year(#7:00:00 AM#)")); + + assertEquals("January", eval("=MonthName(1)")); + assertEquals("Feb", eval("=MonthName(2,True)")); + assertEquals("March", eval("=MonthName(3,False)")); + assertEquals(7, eval("=Hour(#01/02/2003 7:10:27 AM#)")); assertEquals(19, eval("=Hour(#01/02/2003 7:10:27 PM#)")); assertEquals(10, eval("=Minute(#01/02/2003 7:10:27 AM#)")); -- 2.39.5