From 42344d756bc0660725eebc66fca25bc2b4cc1f1e Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Sat, 14 Jan 2017 02:32:58 +0000 Subject: [PATCH] add TemporalConfig to allow for custom expression date/time formats git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1079 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/expr/EvalContext.java | 2 + .../jackcess/expr/TemporalConfig.java | 92 +++++++++++++++++++ .../jackcess/impl/expr/BaseDelayedValue.java | 16 +++- .../jackcess/impl/expr/BaseNumericValue.java | 17 +++- .../jackcess/impl/expr/BuiltinOperators.java | 6 +- .../impl/expr/ExpressionTokenizer.java | 60 +++++++++--- .../jackcess/impl/expr/Expressionator.java | 9 +- .../impl/expr/ExpressionatorTest.java | 4 + 8 files changed, 183 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java index 3ec3a88..01a27b0 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java @@ -24,6 +24,8 @@ import java.text.SimpleDateFormat; */ public interface EvalContext { + public TemporalConfig getTemporalConfig(); + public Value.Type getResultType(); 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 new file mode 100644 index 0000000..1c5bd74 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java @@ -0,0 +1,92 @@ +/* +Copyright (c) 2017 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; + +/** + * + * @author James Ahlborn + */ +public class TemporalConfig +{ + public static final String US_DATE_FORMAT = "M/d/yyyy"; + public static final String US_TIME_FORMAT_12 = "hh:mm:ss a"; + public static final String US_TIME_FORMAT_24 = "HH:mm:ss"; + + public static final TemporalConfig US_TEMPORAL_CONFIG = new TemporalConfig( + US_DATE_FORMAT, US_TIME_FORMAT_12, US_TIME_FORMAT_24, '/', ':'); + + private final String _dateFormat; + private final String _timeFormat12; + private final String _timeFormat24; + private final char _dateSeparator; + private final char _timeSeparator; + private final String _dateTimeFormat12; + private final String _dateTimeFormat24; + + public TemporalConfig(String dateFormat, String timeFormat12, + String timeFormat24, char dateSeparator, + char timeSeparator) + { + _dateFormat = dateFormat; + _timeFormat12 = timeFormat12; + _timeFormat24 = timeFormat24; + _dateSeparator = dateSeparator; + _timeSeparator = timeSeparator; + _dateTimeFormat12 = _dateFormat + " " + _timeFormat12; + _dateTimeFormat24 = _dateFormat + " " + _timeFormat24; + } + + public String getDateFormat() { + return _dateFormat; + } + + public String getTimeFormat12() { + return _timeFormat12; + } + + public String getTimeFormat24() { + return _timeFormat24; + } + + public String getDateTimeFormat12() { + return _dateTimeFormat12; + } + + public String getDateTimeFormat24() { + return _dateTimeFormat24; + } + + public String getDefaultDateFormat() { + return getDateFormat(); + } + + public String getDefaultTimeFormat() { + return getTimeFormat12(); + } + + public String getDefaultDateTimeFormat() { + return getDateTimeFormat12(); + } + + public char getDateSeparator() { + return _dateSeparator; + } + + public char getTimeSeparator() { + return _timeSeparator; + } +} diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java index c75f2b5..5ee037c 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java @@ -1,4 +1,18 @@ -// Copyright (c) 2016 Dell Boomi, Inc. +/* +Copyright (c) 2017 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.impl.expr; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java index 7f62b6a..fc136bf 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java @@ -1,8 +1,21 @@ -// Copyright (c) 2016 Dell Boomi, Inc. +/* +Copyright (c) 2017 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.impl.expr; -import com.healthmarketscience.jackcess.expr.Value; /** * diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java index 0329426..5bd6ac9 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java @@ -600,13 +600,13 @@ public class BuiltinOperators String fmtStr = null; switch(type) { case DATE: - fmtStr = ExpressionTokenizer.DATE_FORMAT; + fmtStr = ctx.getTemporalConfig().getDefaultDateFormat(); break; case TIME: - fmtStr = ExpressionTokenizer.TIME_FORMAT_24; + fmtStr = ctx.getTemporalConfig().getDefaultTimeFormat(); break; case DATE_TIME: - fmtStr = ExpressionTokenizer.DATE_TIME_FORMAT_24; + fmtStr = ctx.getTemporalConfig().getDefaultDateTimeFormat(); break; default: throw new RuntimeException("Unexpected type " + type); 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 46a8160..d078d44 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -16,7 +16,6 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; -import java.math.BigDecimal; import java.text.DateFormat; import java.text.FieldPosition; import java.text.ParseException; @@ -34,6 +33,7 @@ import java.util.TimeZone; import static com.healthmarketscience.jackcess.impl.expr.Expressionator.*; import com.healthmarketscience.jackcess.expr.Value; +import com.healthmarketscience.jackcess.expr.TemporalConfig; /** @@ -49,16 +49,12 @@ class ExpressionTokenizer private static final char DATE_LIT_QUOTE_CHAR = '#'; private static final char EQUALS_CHAR = '='; - static final String DATE_FORMAT = "M/d/yyyy"; - static final String TIME_FORMAT_24 = "HH:mm:ss"; - static final String TIME_FORMAT_12 = "hh:mm:ss a"; - static final String DATE_TIME_FORMAT_24 = DATE_FORMAT + " " + TIME_FORMAT_24; - static final String DATE_TIME_FORMAT_12 = DATE_FORMAT + " " + TIME_FORMAT_12; private static final int AMPM_SUFFIX_LEN = 3; private static final String AM_SUFFIX = " am"; private static final String PM_SUFFIX = " pm"; // access times are based on this date (not the UTC base) private static final String BASE_DATE = "12/30/1899 "; + private static final String BASE_DATE_FMT = "M/d/yyyy"; private static final byte IS_OP_FLAG = 0x01; private static final byte IS_COMP_FLAG = 0x02; @@ -321,10 +317,11 @@ class ExpressionTokenizer private static Token parseDateLiteralString(ExprBuf buf) { + TemporalConfig cfg = buf.getTemporalConfig(); String dateStr = parseStringUntil(buf, DATE_LIT_QUOTE_CHAR, null); - boolean hasDate = (dateStr.indexOf('/') >= 0); - boolean hasTime = (dateStr.indexOf(':') >= 0); + boolean hasDate = (dateStr.indexOf(cfg.getDateSeparator()) >= 0); + boolean hasTime = (dateStr.indexOf(cfg.getTimeSeparator()) >= 0); boolean hasAmPm = false; if(hasTime) { @@ -451,6 +448,7 @@ class ExpressionTokenizer private DateFormat _dateTimeFmt12; private DateFormat _timeFmt24; private DateFormat _dateTimeFmt24; + private String _baseDate; private final StringBuilder _scratch = new StringBuilder(); private ExprBuf(String str, ParseContext ctx) { @@ -498,9 +496,13 @@ class ExpressionTokenizer return _scratch; } + public TemporalConfig getTemporalConfig() { + return _ctx.getTemporalConfig(); + } + public DateFormat getDateFormat() { if(_dateFmt == null) { - _dateFmt = _ctx.createDateFormat(DATE_FORMAT); + _dateFmt = _ctx.createDateFormat(getTemporalConfig().getDateFormat()); } return _dateFmt; } @@ -508,14 +510,17 @@ class ExpressionTokenizer public DateFormat getTimeFormat12() { if(_timeFmt12 == null) { _timeFmt12 = new TimeFormat( - getDateTimeFormat12(), _ctx.createDateFormat(TIME_FORMAT_12)); + getDateTimeFormat12(), _ctx.createDateFormat( + getTemporalConfig().getTimeFormat12()), + getBaseDate()); } return _timeFmt12; } public DateFormat getDateTimeFormat12() { if(_dateTimeFmt12 == null) { - _dateTimeFmt12 = _ctx.createDateFormat(DATE_TIME_FORMAT_12); + _dateTimeFmt12 = _ctx.createDateFormat( + getTemporalConfig().getDateTimeFormat12()); } return _dateTimeFmt12; } @@ -523,18 +528,39 @@ class ExpressionTokenizer public DateFormat getTimeFormat24() { if(_timeFmt24 == null) { _timeFmt24 = new TimeFormat( - getDateTimeFormat24(), _ctx.createDateFormat(TIME_FORMAT_24)); + getDateTimeFormat24(), _ctx.createDateFormat( + getTemporalConfig().getTimeFormat24()), + getBaseDate()); } return _timeFmt24; } public DateFormat getDateTimeFormat24() { if(_dateTimeFmt24 == null) { - _dateTimeFmt24 = _ctx.createDateFormat(DATE_TIME_FORMAT_24); + _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 IllegalStateException("Could not parse base date", e); + } + } + _baseDate = baseDate + " "; + } + return _baseDate; + } + @Override public String toString() { return "[char " + _pos + "] '" + _str + "'"; @@ -607,13 +633,17 @@ class ExpressionTokenizer private static final class TimeFormat extends DateFormat { private static final long serialVersionUID = 0L; + private final DateFormat _parseDelegate; private final DateFormat _fmtDelegate; + private final String _baseDate; - private TimeFormat(DateFormat parseDelegate, DateFormat fmtDelegate) + private TimeFormat(DateFormat parseDelegate, DateFormat fmtDelegate, + String baseDate) { _parseDelegate = parseDelegate; _fmtDelegate = fmtDelegate; + _baseDate = baseDate; } @Override @@ -625,7 +655,7 @@ class ExpressionTokenizer public Date parse(String source, ParsePosition pos) { // we parse as a full date/time in order to get the correct "base date" // used by access - return _parseDelegate.parse(BASE_DATE + source, pos); + return _parseDelegate.parse(_baseDate + source, pos); } @Override 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 ab45eda..c373289 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -37,6 +37,7 @@ import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.expr.Expression; import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.EvalContext; +import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token; import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType; @@ -62,11 +63,15 @@ public class Expressionator } public interface ParseContext { + public TemporalConfig getTemporalConfig(); public SimpleDateFormat createDateFormat(String formatStr); public Function getExpressionFunction(String name); } public static final ParseContext DEFAULT_PARSE_CONTEXT = new ParseContext() { + public TemporalConfig getTemporalConfig() { + return TemporalConfig.US_TEMPORAL_CONFIG; + } public SimpleDateFormat createDateFormat(String formatStr) { return DatabaseBuilder.createDateFormat(formatStr); } @@ -807,8 +812,8 @@ public class Expressionator case LIKE: Token t = buf.next(); - // FIXME, create LITERAL_STRING TokenType? - if(t.getType() != TokenType.LITERAL) { + if((t.getType() != TokenType.LITERAL) || + (t.getValueType() != Value.Type.STRING)) { throw new IllegalArgumentException("Missing Like pattern " + buf); } String patternStr = t.getValueStr(); diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index ffcf901..7f3fcde 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -22,6 +22,7 @@ import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.TestUtil; import com.healthmarketscience.jackcess.expr.Expression; import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.TemporalConfig; import junit.framework.TestCase; /** @@ -280,6 +281,9 @@ public class ExpressionatorTest extends TestCase private static final class TestContext implements Expressionator.ParseContext { + public TemporalConfig getTemporalConfig() { + return TemporalConfig.US_TEMPORAL_CONFIG; + } public SimpleDateFormat createDateFormat(String formatStr) { SimpleDateFormat sdf = DatabaseBuilder.createDateFormat(formatStr); sdf.setTimeZone(TestUtil.TEST_TZ); -- 2.39.5