git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1202 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.2.1
@@ -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 |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -138,7 +138,7 @@ limitations under the License. | |||
* <tr class="TableRowColor"><td>Hour</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>Minute</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>Month</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>MonthName</td><td></td></tr> | |||
* <tr class="TableRowColor"><td>MonthName</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>Now</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>Second</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>Time</td><td>Y</td></tr> | |||
@@ -146,7 +146,7 @@ limitations under the License. | |||
* <tr class="TableRowColor"><td>TimeSerial</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>TimeValue</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>Weekday</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>WeekdayName</td><td></td></tr> | |||
* <tr class="TableRowColor"><td>WeekdayName</td><td>Y</td></tr> | |||
* <tr class="TableRowColor"><td>Year</td><td>Y</td></tr> | |||
* </table> | |||
* |
@@ -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; | |||
} | |||
} |
@@ -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<K,V>(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<TemporalConfig.Type,DateFormat> _dateTimeFmts = | |||
new EnumMap<TemporalConfig.Type,DateFormat>(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; |
@@ -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(); |
@@ -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; | |||
} |
@@ -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: |
@@ -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<Row>(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); |
@@ -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#)")); |