]> source.dussan.org Git - jackcess.git/commitdiff
add support for monthname function; implement better string to date/time conversions
authorJames Ahlborn <jtahlborn@yahoo.com>
Sat, 29 Sep 2018 19:36:51 +0000 (19:36 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Sat, 29 Sep 2018 19:36:51 +0000 (19:36 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1202 f203690c-595d-4dc9-a70b-905162fa7fd2

src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java
src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java
src/main/java/com/healthmarketscience/jackcess/expr/package-info.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java
src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java
src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java

index a7e0ecde02dd69de68819a75621225a9f9c3955e..c82fe4371faaffb82781b06321b2b2c409f213f9 100644 (file)
@@ -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 (file)
index 0000000..8a9a649
--- /dev/null
@@ -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);
+
+}
index 2ea1a126c1b4d3e469d284736f4bcc96608d0a1d..d7061ca6ee1b16917a87566b02e2175c4d9f100f 100644 (file)
@@ -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;
+  }
 }
index 416c96eae5193b632f13f32c9444ecfe2d635452..05c72e6ca1dd8574294a168d0f5154bd29a18705 100644 (file)
@@ -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>
  *
index c8ffc0ffc77d97f4b2867c4b27f3934cd30a70b4..b062a0529cab3012ed041b8df38fec2172aef46f 100644 (file)
@@ -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;
+  }
 }
index f14f934f0e78c305ed8e4a4ab562e873daf1c33a..c75fae00ca5574cc2f3a1ef9c3392b7aee4b821f 100644 (file)
@@ -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;
index f948d15c0c1b3ede81690d752ba4a4e26044de43..d53d6d3afd73ce334e2bd638bd7ac7f48672ba9c 100644 (file)
@@ -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();
index 51b9c6a3899c3725222d50a58f4c9e9d14120729..781faf4df0a6a8b005e222da4fae403b59a49e85 100644 (file)
@@ -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;
           }
index 279a4f0093e036b84db3893606be163dd3aad016..b0924f3c525a05cb2a989a259b6a063c4f59a364 100644 (file)
@@ -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:
index e5d72954706c97a691a78256178581afe1ce49c7..d9f49327034e2bb7f6c54e526302ad32fd741ac2 100644 (file)
@@ -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);
index 6a36199145139588d4976b00ceb8ff2d9a886dcc..4051fde24eba188e5795d89dcb316eb5c72b763c 100644 (file)
@@ -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#)"));