Browse Source

switch expression engine to LocalDateTime

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jdk8@1238 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-3.0.0
James Ahlborn 5 years ago
parent
commit
843a44dfe0
25 changed files with 395 additions and 637 deletions
  1. 7
    8
      src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java
  2. 19
    32
      src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java
  3. 3
    3
      src/main/java/com/healthmarketscience/jackcess/expr/Value.java
  4. 13
    8
      src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java
  5. 28
    48
      src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
  6. 9
    14
      src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java
  7. 1
    20
      src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
  8. 2
    1
      src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java
  9. 3
    3
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java
  10. 1
    1
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java
  11. 4
    4
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java
  12. 3
    3
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java
  13. 6
    6
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java
  14. 187
    262
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java
  15. 6
    9
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java
  16. 1
    1
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java
  17. 31
    152
      src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
  18. 3
    8
      src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java
  19. 4
    4
      src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java
  20. 1
    2
      src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java
  21. 28
    21
      src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java
  22. 2
    2
      src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java
  23. 14
    4
      src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java
  24. 4
    3
      src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java
  25. 15
    18
      src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java

+ 7
- 8
src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java View File

@@ -17,8 +17,8 @@ limitations under the License.
package com.healthmarketscience.jackcess.expr;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

/**
* LocaleContext encapsulates all shared localization state for expression
@@ -35,16 +35,15 @@ public interface LocaleContext
public TemporalConfig getTemporalConfig();

/**
* @return an appropriately configured (i.e. TimeZone and other date/time
* flags) SimpleDateFormat for the given format.
* @return an appropriately configured (i.e. locale) DateTimeFormatter for
* the given format.
*/
public SimpleDateFormat createDateFormat(String formatStr);
public DateTimeFormatter createDateFormatter(String formatStr);

/**
* @return an appropriately configured (i.e. TimeZone and other date/time
* flags) Calendar.
* @return the currently configured ZoneId
*/
public Calendar getCalendar();
public ZoneId getZoneId();

/**
* @return the currently configured NumericConfig (from the

+ 19
- 32
src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java View File

@@ -30,19 +30,18 @@ import java.util.Locale;
*/
public class TemporalConfig
{
public static final String US_DATE_FORMAT = "M/d/yyyy";
public static final String US_DATE_IMPLICIT_YEAR_FORMAT = "M/d";
public static final String US_DATE_FORMAT = "M/d[/uuuu]";
public static final String US_TIME_FORMAT_12_FORMAT = "h:mm:ss a";
public static final String US_TIME_FORMAT_24_FORMAT = "H:mm:ss";
public static final String US_LONG_DATE_FORMAT = "EEEE, MMMM dd, yyyy";
public static final String US_LONG_DATE_FORMAT = "EEEE, MMMM dd, uuuu";

public static final String MEDIUM_DATE_FORMAT = "dd-MMM-yy";
public static final String MEDIUM_DATE_FORMAT = "dd-MMM-uu";
public static final String MEDIUM_TIME_FORMAT = "hh:mm a";
public static final String SHORT_TIME_FORMAT = "HH:mm";

/** default implementation which is configured for the US locale */
public static final TemporalConfig US_TEMPORAL_CONFIG = new TemporalConfig(
US_DATE_FORMAT, US_DATE_IMPLICIT_YEAR_FORMAT, US_LONG_DATE_FORMAT,
US_DATE_FORMAT, US_LONG_DATE_FORMAT,
US_TIME_FORMAT_12_FORMAT, US_TIME_FORMAT_24_FORMAT, '/', ':', Locale.US);

public enum Type {
@@ -133,8 +132,8 @@ public class TemporalConfig
}
}

private final Locale _locale;
private final String _dateFormat;
private final String _dateImplicitYearFormat;
private final String _longDateFormat;
private final String _timeFormat12;
private final String _timeFormat24;
@@ -142,7 +141,7 @@ public class TemporalConfig
private final char _timeSeparator;
private final String _dateTimeFormat12;
private final String _dateTimeFormat24;
private final DateFormatSymbols _symbols;
private final String[] _amPmStrings;

/**
* Instantiates a new TemporalConfig with the given configuration. Note
@@ -151,7 +150,6 @@ public class TemporalConfig
* <time>".
*
* @param dateFormat the date (no time) format
* @param dateImplicitYearFormat the date (no time) with no year format
* @param timeFormat12 the 12 hour time format
* @param timeFormat24 the 24 hour time format
* @param dateSeparator the primary separator used to separate elements in
@@ -163,21 +161,26 @@ public class TemporalConfig
* string. This value should differ from the
* dateSeparator.
*/
public TemporalConfig(String dateFormat, String dateImplicitYearFormat,
String longDateFormat,
public TemporalConfig(String dateFormat, String longDateFormat,
String timeFormat12, String timeFormat24,
char dateSeparator, char timeSeparator, Locale locale)
{
_locale = locale;
_dateFormat = dateFormat;
_dateImplicitYearFormat = dateImplicitYearFormat;
_longDateFormat = longDateFormat;
_timeFormat12 = timeFormat12;
_timeFormat24 = timeFormat24;
_dateSeparator = dateSeparator;
_timeSeparator = timeSeparator;
_dateTimeFormat12 = _dateFormat + " " + _timeFormat12;
_dateTimeFormat24 = _dateFormat + " " + _timeFormat24;
_symbols = DateFormatSymbols.getInstance(locale);
_dateTimeFormat12 = toDateTimeFormat(_dateFormat, _timeFormat12);
_dateTimeFormat24 = toDateTimeFormat(_dateFormat, _timeFormat24);
// there doesn't seem to be a good/easy way to get this in new jave.time
// api, so just use old api
_amPmStrings = DateFormatSymbols.getInstance(locale).getAmPmStrings();
}

public Locale getLocale() {
return _locale;
}

public String getDateFormat() {
@@ -252,24 +255,8 @@ public class TemporalConfig
}
}

public String getImplicitYearDateTimeFormat(Type type) {
switch(type) {
case DATE:
return _dateImplicitYearFormat;
case DATE_TIME:
return toDateTimeFormat(_dateImplicitYearFormat, getDefaultTimeFormat());
case DATE_TIME_12:
return toDateTimeFormat(_dateImplicitYearFormat, getTimeFormat12());
case DATE_TIME_24:
return toDateTimeFormat(_dateImplicitYearFormat, getTimeFormat24());
default:
throw new IllegalArgumentException(
"the given format does not include a date " + type);
}
}

public DateFormatSymbols getDateFormatSymbols() {
return _symbols;
public String[] getAmPmStrings() {
return _amPmStrings;
}

private static String toDateTimeFormat(String dateFormat, String timeFormat) {

+ 3
- 3
src/main/java/com/healthmarketscience/jackcess/expr/Value.java View File

@@ -17,7 +17,7 @@ limitations under the License.
package com.healthmarketscience.jackcess.expr;

import java.math.BigDecimal;
import java.util.Date;
import java.time.LocalDateTime;

/**
* Wrapper for a typed primitive value used within the expression evaluation
@@ -97,9 +97,9 @@ public interface Value
public String getAsString(LocaleContext ctx);

/**
* @return this primitive value converted to a Date
* @return this primitive value converted to a LocalDateTime
*/
public Date getAsDateTime(LocaleContext ctx);
public LocalDateTime getAsLocalDateTime(LocaleContext ctx);

/**
* Since date/time values have different types, it may be more convenient to

+ 13
- 8
src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java View File

@@ -19,8 +19,9 @@ package com.healthmarketscience.jackcess.impl;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Date;
import java.util.EnumMap;
@@ -28,6 +29,7 @@ import java.util.Map;
import javax.script.Bindings;

import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.DateTimeType;
import com.healthmarketscience.jackcess.JackcessException;
import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
@@ -82,12 +84,12 @@ public abstract class BaseEvalContext implements EvalContext
return _dbCtx.getTemporalConfig();
}

public SimpleDateFormat createDateFormat(String formatStr) {
return _dbCtx.createDateFormat(formatStr);
public DateTimeFormatter createDateFormatter(String formatStr) {
return _dbCtx.createDateFormatter(formatStr);
}

public Calendar getCalendar() {
return _dbCtx.getCalendar();
public ZoneId getZoneId() {
return _dbCtx.getZoneId();
}

public NumericConfig getNumericConfig() {
@@ -146,7 +148,10 @@ public abstract class BaseEvalContext implements EvalContext

protected Value toValue(Object val, DataType dType) {
try {
val = ColumnImpl.toInternalValue(dType, val, getDatabase());
// expression engine always uses LocalDateTime, so force that date/time
// type
val = ColumnImpl.toInternalValue(dType, val, getDatabase(),
ColumnImpl.LDT_DATE_TIME_FACTORY);
if(val == null) {
return ValueSupport.NULL_VAL;
}
@@ -158,7 +163,7 @@ public abstract class BaseEvalContext implements EvalContext
case DATE:
case TIME:
case DATE_TIME:
return ValueSupport.toValue(vType, (Date)val);
return ValueSupport.toValue(vType, (LocalDateTime)val);
case LONG:
Integer i = ((val instanceof Integer) ? (Integer)val :
((Number)val).intValue());

+ 28
- 48
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java View File

@@ -40,7 +40,6 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Calendar;
@@ -108,14 +107,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
static final long MILLIS_BETWEEN_EPOCH_AND_1900 =
25569L * MILLISECONDS_PER_DAY;

static final LocalDate BASE_LD = LocalDate.of(1899, 12, 30);
static final LocalTime BASE_LT = LocalTime.of(0, 0);
static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT);
public static final LocalDate BASE_LD = LocalDate.of(1899, 12, 30);
public static final LocalTime BASE_LT = LocalTime.of(0, 0);
public static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT);

private static final DateTimeFactory DEF_DATE_TIME_FACTORY =
new DefaultDateTimeFactory();

private static final DateTimeFactory LDT_DATE_TIME_FACTORY =
static final DateTimeFactory LDT_DATE_TIME_FACTORY =
new LDTDateTimeFactory();

/**
@@ -929,30 +928,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return fromDateDouble(value, getTimeZone());
}

/**
* Returns a java long time value converted from an access date double.
* @usage _advanced_method_
*/
public static long fromDateDouble(double value, DatabaseImpl db) {
return fromDateDouble(value, db.getTimeZone());
}

/**
* Returns a java long time value converted from an access date double.
* @usage _advanced_method_
*/
@Deprecated
public static long fromDateDouble(double value, Calendar c) {
// FIXME, remove me
return fromDateDouble(value, c.getTimeZone());
}

public static long fromDateDouble(double value, TimeZone tz) {
private static long fromDateDouble(double value, TimeZone tz) {
long localTime = fromLocalDateDouble(value);
return localTime - getFromLocalTimeZoneOffset(localTime, tz);
}

public static long fromLocalDateDouble(double value) {
static long fromLocalDateDouble(double value) {
long datePart = ((long)value) * MILLISECONDS_PER_DAY;

// the fractional part of the double represents the time. it is always
@@ -968,11 +949,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}

public static LocalDateTime ldtFromLocalDateDouble(double value) {
Duration dateTimeOffset = durationFromLocalDateDouble1900(value);
Duration dateTimeOffset = durationFromLocalDateDouble(value);
return BASE_LDT.plus(dateTimeOffset);
}

private static Duration durationFromLocalDateDouble1900(double value) {
private static Duration durationFromLocalDateDouble(double value) {
long dateSeconds = ((long)value) * SECONDS_PER_DAY;

// the fractional part of the double represents the time. it is always
@@ -1027,7 +1008,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
* time value.
* @usage _advanced_method_
*/
public static double toDateDouble(Object value, DatabaseImpl db)
private static double toDateDouble(Object value, DatabaseImpl db)
{
return toDateDouble(value, db.getTimeZone(), db.getZoneId());
}
@@ -1037,23 +1018,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
* Date/Calendar/Number/Temporal time value.
* @usage _advanced_method_
*/
@Deprecated
public static double toDateDouble(Object value, Calendar c) {
// FIXME remove me
return toDateDouble(value, c.getTimeZone());
}

public static double toDateDouble(Object value, TimeZone tz)
{
return toDateDouble(value, tz, null);
}

/**
* Returns an access date double converted from a java
* Date/Calendar/Number/Temporal time value.
* @usage _advanced_method_
*/
public static double toDateDouble(Object value, TimeZone tz, ZoneId zoneId)
private static double toDateDouble(Object value, TimeZone tz, ZoneId zoneId)
{
if(value instanceof TemporalAccessor) {
return toDateDouble(
@@ -1139,10 +1104,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {

public static double toDateDouble(LocalDateTime ldt) {
Duration dateTimeOffset = Duration.between(BASE_LDT, ldt);
return toLocalDateDouble1900(dateTimeOffset);
return toLocalDateDouble(dateTimeOffset);
}

private static double toLocalDateDouble1900(Duration time) {
private static double toLocalDateDouble(Duration time) {
long dateTimeSeconds = time.getSeconds();
long timeSeconds = dateTimeSeconds % SECONDS_PER_DAY;
if(timeSeconds < 0) {
@@ -1782,6 +1747,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return ((Boolean)value) ? BigDecimal.valueOf(-1) : BigDecimal.ZERO;
} else if(value instanceof Date) {
return new BigDecimal(toDateDouble(value, db));
} else if(value instanceof LocalDateTime) {
return new BigDecimal(toDateDouble((LocalDateTime)value));
}
return new BigDecimal(value.toString());
}
@@ -1812,6 +1779,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return ((Boolean)value) ? -1 : 0;
} else if(value instanceof Date) {
return toDateDouble(value, db);
} else if(value instanceof LocalDateTime) {
return toDateDouble((LocalDateTime)value);
}
return Double.valueOf(value.toString());
}
@@ -2146,6 +2115,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public static Object toInternalValue(DataType dataType, Object value,
DatabaseImpl db)
throws IOException
{
return toInternalValue(dataType, value, db, null);
}

static Object toInternalValue(DataType dataType, Object value,
DatabaseImpl db,
ColumnImpl.DateTimeFactory factory)
throws IOException
{
if(value == null) {
return null;
@@ -2171,7 +2148,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return ((value instanceof Double) ? value :
toNumber(value, db).doubleValue());
case SHORT_DATE_TIME:
return db.getDateTimeFactory().toInternalValue(db, value);
if(factory == null) {
factory = db.getDateTimeFactory();
}
return factory.toInternalValue(db, value);
case TEXT:
case MEMO:
case GUID:

+ 9
- 14
src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java View File

@@ -17,8 +17,8 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javax.script.Bindings;
import javax.script.SimpleBindings;
@@ -42,7 +42,7 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig

private final DatabaseImpl _db;
private FunctionLookup _funcs = DefaultFunctions.LOOKUP;
private Map<String,SimpleDateFormat> _sdfs;
private Map<String,DateTimeFormatter> _sdfs;
private Map<String,DecimalFormat> _dfs;
private TemporalConfig _temporal = TemporalConfig.US_TEMPORAL_CONFIG;
private NumericConfig _numeric = NumericConfig.US_NUMERIC_CONFIG;
@@ -68,8 +68,8 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
}
}

public Calendar getCalendar() {
return _db.getCalendar();
public ZoneId getZoneId() {
return _db.getZoneId();
}

public NumericConfig getNumericConfig() {
@@ -99,14 +99,13 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
_bindings = bindings;
}

public SimpleDateFormat createDateFormat(String formatStr) {
public DateTimeFormatter createDateFormatter(String formatStr) {
if(_sdfs == null) {
_sdfs = new SimpleCache<String,SimpleDateFormat>(MAX_CACHE_SIZE);
_sdfs = new SimpleCache<String,DateTimeFormatter>(MAX_CACHE_SIZE);
}
SimpleDateFormat sdf = _sdfs.get(formatStr);
DateTimeFormatter sdf = _sdfs.get(formatStr);
if(sdf == null) {
sdf = _db.createDateFormat(formatStr);
sdf.setDateFormatSymbols(_temporal.getDateFormatSymbols());
sdf = DateTimeFormatter.ofPattern(formatStr, _temporal.getLocale());
_sdfs.put(formatStr, sdf);
}
return sdf;
@@ -128,8 +127,4 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
public float getRandom(Integer seed) {
return _rndCtx.getRandom(seed);
}

void resetDateTimeConfig() {
_sdfs = null;
}
}

+ 1
- 20
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java View File

@@ -342,8 +342,6 @@ public class DatabaseImpl implements Database
/** shared state used when enforcing foreign keys */
private final FKEnforcer.SharedState _fkEnforcerSharedState =
FKEnforcer.initSharedState();
/** Calendar for use interpreting dates/times in Columns */
private Calendar _calendar;
/** shared context for evaluating expressions */
private DBEvalContext _evalCtx;
/** factory for the appropriate date/time type */
@@ -535,7 +533,7 @@ public class DatabaseImpl implements Database
_evaluateExpressions = getDefaultEvaluateExpressions();
_fileFormat = fileFormat;
_pageChannel = new PageChannel(channel, closeChannel, _format, autoSync);
_timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone);
setZoneInfo(timeZone, null);
if(provider == null) {
provider = DefaultCodecProvider.INSTANCE;
}
@@ -682,12 +680,6 @@ public class DatabaseImpl implements Database

_timeZone = newTimeZone;
_zoneId = newZoneId;

// clear cached calendar(s) when timezone is changed
_calendar = null;
if(_evalCtx != null) {
_evalCtx.resetDateTimeConfig();
}
}

@Override
@@ -778,17 +770,6 @@ public class DatabaseImpl implements Database
return _fkEnforcerSharedState;
}

/**
* @usage _advanced_method_
*/
Calendar getCalendar() {
if(_calendar == null) {
_calendar = DatabaseBuilder.toCompatibleCalendar(
Calendar.getInstance(_timeZone));
}
return _calendar;
}

public EvalConfig getEvalConfig() {
return getEvalContext();
}

+ 2
- 1
src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java View File

@@ -17,6 +17,7 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@@ -208,7 +209,7 @@ public class PropertyMapImpl implements PropertyMap
type = DataType.FLOAT;
} else if(value instanceof Double) {
type = DataType.DOUBLE;
} else if(value instanceof Date) {
} else if((value instanceof Date) || (value instanceof LocalDateTime)) {
type = DataType.SHORT_DATE_TIME;
} else if(value instanceof byte[]) {
type = DataType.OLE;

+ 3
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java View File

@@ -17,7 +17,7 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.util.Date;
import java.time.LocalDateTime;

import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.Value;
@@ -60,8 +60,8 @@ public abstract class BaseDelayedValue implements Value
return getDelegate().getAsString(ctx);
}

public Date getAsDateTime(LocaleContext ctx) {
return getDelegate().getAsDateTime(ctx);
public LocalDateTime getAsLocalDateTime(LocaleContext ctx) {
return getDelegate().getAsLocalDateTime(ctx);
}

public Value getAsDateTimeValue(LocaleContext ctx) {

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java View File

@@ -44,7 +44,7 @@ public abstract class BaseNumericValue extends BaseValue
@Override
public Value getAsDateTimeValue(LocaleContext ctx) {
Value dateValue = DefaultDateFunctions.numberToDateValue(
ctx, getNumber().doubleValue());
getNumber().doubleValue());
if(dateValue == null) {
throw invalidConversion(Value.Type.DATE_TIME);
}

+ 4
- 4
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java View File

@@ -17,11 +17,11 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.util.Date;
import java.time.LocalDateTime;

import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.Value;

/**
*
@@ -41,8 +41,8 @@ public abstract class BaseValue implements Value
throw invalidConversion(Type.STRING);
}

public Date getAsDateTime(LocaleContext ctx) {
return (Date)getAsDateTimeValue(ctx).get();
public LocalDateTime getAsLocalDateTime(LocaleContext ctx) {
return (LocalDateTime)getAsDateTimeValue(ctx).get();
}

public Value getAsDateTimeValue(LocaleContext ctx) {

+ 3
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java View File

@@ -76,7 +76,7 @@ public class BuiltinOperators
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = -param1.getAsDouble(ctx);
return toDateValue(ctx, mathType, result);
return toDateValueIfPossible(mathType, result);
case LONG:
return toValue(-param1.getAsLongInt(ctx));
case DOUBLE:
@@ -108,7 +108,7 @@ public class BuiltinOperators
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = param1.getAsDouble(ctx) + param2.getAsDouble(ctx);
return toDateValue(ctx, mathType, result);
return toDateValueIfPossible(mathType, result);
case LONG:
return toValue(param1.getAsLongInt(ctx) + param2.getAsLongInt(ctx));
case DOUBLE:
@@ -138,7 +138,7 @@ public class BuiltinOperators
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = param1.getAsDouble(ctx) - param2.getAsDouble(ctx);
return toDateValue(ctx, mathType, result);
return toDateValueIfPossible(mathType, result);
case LONG:
return toValue(param1.getAsLongInt(ctx) - param2.getAsLongInt(ctx));
case DOUBLE:

+ 6
- 6
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java View File

@@ -17,11 +17,11 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.util.Date;
import java.time.LocalDateTime;

import com.healthmarketscience.jackcess.impl.ColumnImpl;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.ColumnImpl;

/**
*
@@ -30,9 +30,9 @@ import com.healthmarketscience.jackcess.expr.Value;
public class DateTimeValue extends BaseValue
{
private final Type _type;
private final Date _val;
private final LocalDateTime _val;

public DateTimeValue(Type type, Date val) {
public DateTimeValue(Type type, LocalDateTime val) {
if(!type.isTemporal()) {
throw new IllegalArgumentException("invalid date/time type");
}
@@ -49,7 +49,7 @@ public class DateTimeValue extends BaseValue
}

protected Double getNumber(LocaleContext ctx) {
return ColumnImpl.toDateDouble(_val, ctx.getCalendar());
return ColumnImpl.toDateDouble(_val);
}

@Override
@@ -64,7 +64,7 @@ public class DateTimeValue extends BaseValue
}

@Override
public Date getAsDateTime(LocaleContext ctx) {
public LocalDateTime getAsLocalDateTime(LocaleContext ctx) {
return _val;
}


+ 187
- 262
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java View File

@@ -17,11 +17,20 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;


import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.Date;
import java.time.DateTimeException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.MonthDay;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.WeekFields;

import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
@@ -44,12 +53,6 @@ public class DefaultDateFunctions
// max, valid, recognizable date: December 31, 9999 A.D. 23:59:59
private static final double MAX_DATE = 2958465.999988426d;

private static final long SECONDS_PER_DAY = 24L * 60L * 60L;
private static final double DSECONDS_PER_DAY = SECONDS_PER_DAY;

private static final long SECONDS_PER_HOUR = 60L * 60L;
private static final long SECONDS_PER_MINUTE = 60L;

private static final String INTV_YEAR = "yyyy";
private static final String INTV_QUARTER = "q";
private static final String INTV_MONTH = "m";
@@ -61,7 +64,8 @@ public class DefaultDateFunctions
private static final String INTV_MINUTE = "n";
private static final String INTV_SECOND = "s";

private enum WeekOpType { GET_WEEK, GET_NUM_WEEKS }
private static final WeekFields SUNDAY_FIRST =
WeekFields.of(DayOfWeek.SUNDAY, 1);

private DefaultDateFunctions() {}

@@ -72,8 +76,7 @@ public class DefaultDateFunctions
public static final Function DATE = registerFunc(new Func0("Date") {
@Override
protected Value eval0(EvalContext ctx) {
double dd = dateOnly(currentTimeDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd);
return ValueSupport.toValue(LocalDate.now());
}
});

@@ -84,8 +87,7 @@ public class DefaultDateFunctions
if(dv.getType() == Value.Type.DATE) {
return dv;
}
double dd = dateOnly(dv.getAsDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd);
return ValueSupport.toValue(dv.getAsLocalDateTime(ctx).toLocalDate());
}
});

@@ -101,15 +103,11 @@ public class DefaultDateFunctions
year += ((year <= 29) ? 2000 : 1900);
}

Calendar cal = ctx.getCalendar();
cal.clear();
// we have to construct incrementatlly to handle out of range values
LocalDate ld = LocalDate.of(year,1,1).plusMonths(month - 1)
.plusDays(day - 1);

cal.set(Calendar.YEAR, year);
// convert to 0 based value
cal.set(Calendar.MONTH, month - 1);
cal.set(Calendar.DAY_OF_MONTH, day);

return ValueSupport.toValue(Value.Type.DATE, cal.getTime());
return ValueSupport.toValue(ld);
}
});

@@ -127,27 +125,27 @@ public class DefaultDateFunctions
String intv = params[0].getAsString(ctx).trim();
int result = -1;
if(intv.equalsIgnoreCase(INTV_YEAR)) {
result = nonNullToCalendarField(ctx, param2, Calendar.YEAR);
result = param2.getAsLocalDateTime(ctx).getYear();
} else if(intv.equalsIgnoreCase(INTV_QUARTER)) {
result = getQuarter(nonNullToCalendar(ctx, param2));
result = getQuarter(param2.getAsLocalDateTime(ctx));
} else if(intv.equalsIgnoreCase(INTV_MONTH)) {
// convert from 0 based to 1 based value
result = nonNullToCalendarField(ctx, param2, Calendar.MONTH) + 1;
result = param2.getAsLocalDateTime(ctx).getMonthValue();
} else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR)) {
result = nonNullToCalendarField(ctx, param2, Calendar.DAY_OF_YEAR);
result = param2.getAsLocalDateTime(ctx).getDayOfYear();
} else if(intv.equalsIgnoreCase(INTV_DAY)) {
result = nonNullToCalendarField(ctx, param2, Calendar.DAY_OF_MONTH);
result = param2.getAsLocalDateTime(ctx).getDayOfMonth();
} else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) {
int dayOfWeek = nonNullToCalendarField(ctx, param2, Calendar.DAY_OF_WEEK);
int dayOfWeek = param2.getAsLocalDateTime(ctx)
.get(SUNDAY_FIRST.dayOfWeek());
result = dayOfWeekToWeekDay(dayOfWeek, firstDay);
} else if(intv.equalsIgnoreCase(INTV_WEEK)) {
result = weekOfYear(ctx, param2, firstDay, firstWeekType);
} else if(intv.equalsIgnoreCase(INTV_HOUR)) {
result = nonNullToCalendarField(ctx, param2, Calendar.HOUR_OF_DAY);
result = param2.getAsLocalDateTime(ctx).getHour();
} else if(intv.equalsIgnoreCase(INTV_MINUTE)) {
result = nonNullToCalendarField(ctx, param2, Calendar.MINUTE);
result = param2.getAsLocalDateTime(ctx).getMinute();
} else if(intv.equalsIgnoreCase(INTV_SECOND)) {
result = nonNullToCalendarField(ctx, param2, Calendar.SECOND);
result = param2.getAsLocalDateTime(ctx).getSecond();
} else {
throw new EvalException("Invalid interval " + intv);
}
@@ -167,33 +165,31 @@ public class DefaultDateFunctions
String intv = param1.getAsString(ctx).trim();
int val = param2.getAsLongInt(ctx);

Calendar cal = nonNullToCalendar(ctx, param3);
LocalDateTime ldt = param3.getAsLocalDateTime(ctx);

if(intv.equalsIgnoreCase(INTV_YEAR)) {
cal.add(Calendar.YEAR, val);
ldt = ldt.plus(val, ChronoUnit.YEARS);
} else if(intv.equalsIgnoreCase(INTV_QUARTER)) {
cal.add(Calendar.MONTH, val * 3);
ldt = ldt.plus(val * 3, ChronoUnit.MONTHS);
} else if(intv.equalsIgnoreCase(INTV_MONTH)) {
cal.add(Calendar.MONTH, val);
} else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR)) {
cal.add(Calendar.DAY_OF_YEAR, val);
} else if(intv.equalsIgnoreCase(INTV_DAY)) {
cal.add(Calendar.DAY_OF_YEAR, val);
} else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) {
cal.add(Calendar.DAY_OF_WEEK, val);
ldt = ldt.plus(val, ChronoUnit.MONTHS);
} else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR) ||
intv.equalsIgnoreCase(INTV_DAY) ||
intv.equalsIgnoreCase(INTV_WEEKDAY)) {
ldt = ldt.plus(val, ChronoUnit.DAYS);
} else if(intv.equalsIgnoreCase(INTV_WEEK)) {
cal.add(Calendar.WEEK_OF_YEAR, val);
ldt = ldt.plus(val, ChronoUnit.WEEKS);
} else if(intv.equalsIgnoreCase(INTV_HOUR)) {
cal.add(Calendar.HOUR, val);
ldt = ldt.plus(val, ChronoUnit.HOURS);
} else if(intv.equalsIgnoreCase(INTV_MINUTE)) {
cal.add(Calendar.MINUTE, val);
ldt = ldt.plus(val, ChronoUnit.MINUTES);
} else if(intv.equalsIgnoreCase(INTV_SECOND)) {
cal.add(Calendar.SECOND, val);
ldt = ldt.plus(val, ChronoUnit.SECONDS);
} else {
throw new EvalException("Invalid interval " + intv);
}

return ValueSupport.toValue(cal);
return ValueSupport.toValue(ldt);
}
});

@@ -212,14 +208,14 @@ public class DefaultDateFunctions

String intv = params[0].getAsString(ctx).trim();

Calendar cal1 = nonNullToCalendar(ctx, param2);
Calendar cal2 = nonNullToCalendar(ctx, param3);
LocalDateTime ldt1 = param2.getAsLocalDateTime(ctx);
LocalDateTime ldt2 = param3.getAsLocalDateTime(ctx);

int sign = 1;
if(cal1.after(cal2)) {
Calendar tmp = cal1;
cal1 = cal2;
cal2 = tmp;
if(ldt1.isAfter(ldt2)) {
LocalDateTime tmp = ldt1;
ldt1 = ldt2;
ldt2 = tmp;
sign = -1;
}

@@ -229,22 +225,22 @@ public class DefaultDateFunctions

int result = -1;
if(intv.equalsIgnoreCase(INTV_YEAR)) {
result = cal2.get(Calendar.YEAR) - cal1.get(Calendar.YEAR);
result = ldt2.getYear() - ldt1.getYear();
} else if(intv.equalsIgnoreCase(INTV_QUARTER)) {
int y1 = cal1.get(Calendar.YEAR);
int q1 = getQuarter(cal1);
int y2 = cal2.get(Calendar.YEAR);
int q2 = getQuarter(cal2);
int y1 = ldt1.getYear();
int q1 = getQuarter(ldt1);
int y2 = ldt2.getYear();
int q2 = getQuarter(ldt2);
while(y2 > y1) {
q2 += 4;
--y2;
}
result = q2 - q1;
} else if(intv.equalsIgnoreCase(INTV_MONTH)) {
int y1 = cal1.get(Calendar.YEAR);
int m1 = cal1.get(Calendar.MONTH);
int y2 = cal2.get(Calendar.YEAR);
int m2 = cal2.get(Calendar.MONTH);
int y1 = ldt1.getYear();
int m1 = ldt1.getMonthValue();
int y2 = ldt2.getYear();
int m2 = ldt2.getMonthValue();
while(y2 > y1) {
m2 += 12;
--y2;
@@ -252,30 +248,30 @@ public class DefaultDateFunctions
result = m2 - m1;
} else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR) ||
intv.equalsIgnoreCase(INTV_DAY)) {
result = getDayDiff(cal1, cal2);
result = getDayDiff(ldt1, ldt2);
} else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) {
// this calulates number of 7 day periods between two dates
result = getDayDiff(cal1, cal2) / 7;
result = getDayDiff(ldt1, ldt2) / 7;
} else if(intv.equalsIgnoreCase(INTV_WEEK)) {
// this counts number of "week of year" intervals between two dates
int w1 = weekOfYear(cal1, firstDay, firstWeekType);
int y1 = getWeekOfYearYear(cal1, w1);
int w2 = weekOfYear(cal2, firstDay, firstWeekType);
int y2 = getWeekOfYearYear(cal2, w2);
WeekFields weekFields = weekFields(firstDay, firstWeekType);
int w1 = ldt1.get(weekFields.weekOfWeekBasedYear());
int y1 = ldt1.get(weekFields.weekBasedYear());
int w2 = ldt2.get(weekFields.weekOfWeekBasedYear());
int y2 = ldt2.get(weekFields.weekBasedYear());
while(y2 > y1) {
cal2.add(Calendar.YEAR, -1);
w2 += weeksInYear(cal2, firstDay, firstWeekType);
y2 = cal2.get(Calendar.YEAR);
--y2;
w2 += weeksInYear(y2, weekFields);
}
result = w2 - w1;
} else if(intv.equalsIgnoreCase(INTV_HOUR)) {
result = getHourDiff(cal1, cal2);
result = getHourDiff(ldt1, ldt2);
} else if(intv.equalsIgnoreCase(INTV_MINUTE)) {
result = getMinuteDiff(cal1, cal2);
result = getMinuteDiff(ldt1, ldt2);
} else if(intv.equalsIgnoreCase(INTV_SECOND)) {
int s1 = cal1.get(Calendar.SECOND);
int s2 = cal2.get(Calendar.SECOND);
int minuteDiff = getMinuteDiff(cal1, cal2);
int s1 = ldt1.getSecond();
int s2 = ldt2.getSecond();
int minuteDiff = getMinuteDiff(ldt1, ldt2);
result = (s2 + (60 * minuteDiff)) - s1;
} else {
throw new EvalException("Invalid interval " + intv);
@@ -288,15 +284,15 @@ public class DefaultDateFunctions
public static final Function NOW = registerFunc(new Func0("Now") {
@Override
protected Value eval0(EvalContext ctx) {
return ValueSupport.toValue(Value.Type.DATE_TIME, new Date());
return ValueSupport.toValue(Value.Type.DATE_TIME,
LocalDateTime.now(ctx.getZoneId()));
}
});

public static final Function TIME = registerFunc(new Func0("Time") {
@Override
protected Value eval0(EvalContext ctx) {
double dd = timeOnly(currentTimeDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
return ValueSupport.toValue(LocalTime.now(ctx.getZoneId()));
}
});

@@ -307,15 +303,15 @@ public class DefaultDateFunctions
if(dv.getType() == Value.Type.TIME) {
return dv;
}
double dd = timeOnly(dv.getAsDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
return ValueSupport.toValue(dv.getAsLocalDateTime(ctx).toLocalTime());
}
});

public static final Function TIMER = registerFunc(new Func0("Timer") {
@Override
protected Value eval0(EvalContext ctx) {
double dd = timeOnly(currentTimeDouble(ctx)) * DSECONDS_PER_DAY;
double dd = LocalTime.now(ctx.getZoneId())
.get(ChronoField.MILLI_OF_DAY) / 1000d;
return ValueSupport.toValue(dd);
}
});
@@ -327,59 +323,46 @@ public class DefaultDateFunctions
int minutes = param2.getAsLongInt(ctx);
int seconds = param3.getAsLongInt(ctx);

long totalSeconds = (hours * SECONDS_PER_HOUR) +
(minutes * SECONDS_PER_MINUTE) + seconds;
if(totalSeconds < 0L) {
do {
totalSeconds += SECONDS_PER_DAY;
} while(totalSeconds < 0L);
} else if(totalSeconds > SECONDS_PER_DAY) {
totalSeconds %= SECONDS_PER_DAY;
}
// we have to construct incrementatlly to handle out of range values
LocalTime lt = ColumnImpl.BASE_LT.plusHours(hours).plusMinutes(minutes)
.plusSeconds(seconds);

double dd = totalSeconds / DSECONDS_PER_DAY;
return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
return ValueSupport.toValue(lt);
}
});

public static final Function HOUR = registerFunc(new Func1NullIsNull("Hour") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(
nonNullToCalendarField(ctx, param1, Calendar.HOUR_OF_DAY));
return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getHour());
}
});

public static final Function MINUTE = registerFunc(new Func1NullIsNull("Minute") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(
nonNullToCalendarField(ctx, param1, Calendar.MINUTE));
return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getMinute());
}
});

public static final Function SECOND = registerFunc(new Func1NullIsNull("Second") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(
nonNullToCalendarField(ctx, param1, Calendar.SECOND));
return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getSecond());
}
});

public static final Function YEAR = registerFunc(new Func1NullIsNull("Year") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(
nonNullToCalendarField(ctx, param1, Calendar.YEAR));
return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getYear());
}
});

public static final Function MONTH = registerFunc(new Func1NullIsNull("Month") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
// convert from 0 based to 1 based value
return ValueSupport.toValue(
nonNullToCalendarField(ctx, param1, Calendar.MONTH) + 1);
return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getMonthValue());
}
});

@@ -390,16 +373,12 @@ public class DefaultDateFunctions
if(param1.isNull()) {
return ValueSupport.NULL_VAL;
}
// convert from 1 based to 0 based value
int month = param1.getAsLongInt(ctx) - 1;

boolean abbreviate = getOptionalBooleanParam(ctx, params, 1);
Month month = Month.of(param1.getAsLongInt(ctx));

DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols();
String[] monthNames = (abbreviate ?
syms.getShortMonths() : syms.getMonths());
// note, the array is 1 based
return ValueSupport.toValue(monthNames[month]);
TextStyle textStyle = getTextStyle(ctx, params, 1);
String monthName = month.getDisplayName(
textStyle, ctx.getTemporalConfig().getLocale());
return ValueSupport.toValue(monthName);
}
});

@@ -407,7 +386,7 @@ public class DefaultDateFunctions
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(
nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_MONTH));
param1.getAsLocalDateTime(ctx).getDayOfMonth());
}
});

@@ -418,7 +397,8 @@ public class DefaultDateFunctions
if(param1.isNull()) {
return ValueSupport.NULL_VAL;
}
int dayOfWeek = nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_WEEK);
int dayOfWeek = param1.getAsLocalDateTime(ctx)
.get(SUNDAY_FIRST.dayOfWeek());

int firstDay = getFirstDayParam(ctx, params, 1);

@@ -435,31 +415,17 @@ public class DefaultDateFunctions
}
int weekday = param1.getAsLongInt(ctx);

boolean abbreviate = getOptionalBooleanParam(ctx, params, 1);
TextStyle textStyle = getTextStyle(ctx, params, 1);

int firstDay = getFirstDayParam(ctx, params, 2);

int dayOfWeek = weekDayToDayOfWeek(weekday, firstDay);

DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols();
String[] weekdayNames = (abbreviate ?
syms.getShortWeekdays() : syms.getWeekdays());
// note, the array is 1 based
return ValueSupport.toValue(weekdayNames[dayOfWeek]);
String weekdayName = dayOfWeek(dayOfWeek).getDisplayName(
textStyle, ctx.getTemporalConfig().getLocale());
return ValueSupport.toValue(weekdayName);
}
});

private static int nonNullToCalendarField(EvalContext ctx, Value param,
int field) {
return nonNullToCalendar(ctx, param).get(field);
}

private static Calendar nonNullToCalendar(EvalContext ctx, Value param) {
Calendar cal = ctx.getCalendar();
cal.setTime(param.getAsDateTime(ctx));
return cal;
}

static Value stringToDateValue(LocaleContext ctx, String valStr) {
// see if we can coerce to date/time
TemporalConfig.Type valTempType = ExpressionTokenizer.determineDateType(
@@ -467,26 +433,31 @@ public class DefaultDateFunctions

if(valTempType != null) {

DateFormat parseDf = ExpressionTokenizer.createParseDateTimeFormat(
valTempType, ctx);
DateTimeFormatter parseDf = ctx.createDateFormatter(
ctx.getTemporalConfig().getDateTimeFormat(valTempType));

try {
Date dateVal = ExpressionTokenizer.parseComplete(parseDf, valStr);
return ValueSupport.toValue(valTempType.getValueType(), dateVal);
} catch(java.text.ParseException pe) {
TemporalAccessor parsedInfo = parseDf.parse(valStr);

LocalDate ld = ColumnImpl.BASE_LD;
if(valTempType.includesDate()) {
// the date may not include a year value, in which case it means
// to use the "current" year. see if this is an implicit year date
parseDf = ExpressionTokenizer.createParseImplicitYearDateTimeFormat(
valTempType, ctx);
try {
Date dateVal = ExpressionTokenizer.parseComplete(parseDf, valStr);
return ValueSupport.toValue(valTempType.getValueType(), dateVal);
} catch(java.text.ParseException pe2) {
// guess not, continue on to failure
// the year may not be explicitly specified
if(parsedInfo.isSupported(ChronoField.YEAR)) {
ld = LocalDate.from(parsedInfo);
} else {
ld = MonthDay.from(parsedInfo).atYear(
Year.now(ctx.getZoneId()).getValue());
}
}

LocalTime lt = ColumnImpl.BASE_LT;
if(valTempType.includesTime()) {
lt = LocalTime.from(parsedInfo);
}

return ValueSupport.toValue(LocalDateTime.of(ld, lt));
} catch(DateTimeException de) {
// note a valid date/time
}
}

@@ -494,35 +465,18 @@ public class DefaultDateFunctions
return null;
}

static Value numberToDateValue(LocaleContext ctx, double dd) {
if((dd < MIN_DATE) || (dd > MAX_DATE)) {
static boolean isValidDateDouble(double dd) {
return ((dd >= MIN_DATE) && (dd <= MAX_DATE));
}

static Value numberToDateValue(double dd) {
if(!isValidDateDouble(dd)) {
// outside valid date range
return null;
}

boolean hasDate = (dateOnly(dd) != 0.0d);
boolean hasTime = (timeOnly(dd) != 0.0d);

Value.Type type = (hasDate ? (hasTime ? Value.Type.DATE_TIME :
Value.Type.DATE) :
Value.Type.TIME);
return ValueSupport.toDateValue(ctx, type, dd);
}

private static double dateOnly(double dd) {
// the integral part of the date/time double is the date value. discard
// the fractional portion
return (long)dd;
}

private static double timeOnly(double dd) {
// the fractional part of the date/time double is the time value. discard
// the integral portion
return new BigDecimal(dd).remainder(BigDecimal.ONE).doubleValue();
}

private static double currentTimeDouble(LocaleContext ctx) {
return ColumnImpl.toDateDouble(System.currentTimeMillis(), ctx.getCalendar());
LocalDateTime ldt = ColumnImpl.ldtFromLocalDateDouble(dd);
return ValueSupport.toValue(ldt);
}

private static int dayOfWeekToWeekDay(int day, int firstDay) {
@@ -551,114 +505,85 @@ public class DefaultDateFunctions
return getOptionalIntParam(ctx, params, idx, 1, 0);
}

private static int weekOfYear(EvalContext ctx, Value param, int firstDay,
int firstWeekType) {
return doWeekOp(nonNullToCalendar(ctx, param), firstDay, firstWeekType,
WeekOpType.GET_WEEK);
}
private static WeekFields weekFields(int firstDay, int firstWeekType) {

int minDays = 1;
switch(firstWeekType) {
case 1:
// vbUseSystem 0
// vbFirstJan1 1 (default)
break;
case 2:
// vbFirstFourDays 2
minDays = 4;
break;
case 3:
// vbFirstFullWeek 3
minDays = 7;
break;
default:
throw new EvalException("Invalid first week of year type " +
firstWeekType);
}

private static int weekOfYear(Calendar cal, int firstDay, int firstWeekType) {
return doWeekOp(cal, firstDay, firstWeekType, WeekOpType.GET_WEEK);
return WeekFields.of(dayOfWeek(firstDay), minDays);
}

private static int weeksInYear(Calendar cal, int firstDay, int firstWeekType) {
return doWeekOp(cal, firstDay, firstWeekType, WeekOpType.GET_NUM_WEEKS);
private static DayOfWeek dayOfWeek(int dayOfWeek) {
return DayOfWeek.SUNDAY.plus(dayOfWeek - 1);
}

private static int doWeekOp(Calendar cal, int firstDay, int firstWeekType,
WeekOpType opType) {
// need to mess with some calendar settings, but they need to be restored
// when done because the Calendar instance may be shared
int origFirstDay = cal.getFirstDayOfWeek();
int origMinDays = cal.getMinimalDaysInFirstWeek();
try {

int minDays = 1;
switch(firstWeekType) {
case 1:
// vbUseSystem 0
// vbFirstJan1 1 (default)
break;
case 2:
// vbFirstFourDays 2
minDays = 4;
break;
case 3:
// vbFirstFullWeek 3
minDays = 7;
break;
default:
throw new EvalException("Invalid first week of year type " +
firstWeekType);
}

cal.setFirstDayOfWeek(firstDay);
cal.setMinimalDaysInFirstWeek(minDays);
private static TextStyle getTextStyle(EvalContext ctx, Value[] params,
int idx) {
boolean abbreviate = getOptionalBooleanParam(ctx, params, 1);
return (abbreviate ? TextStyle.SHORT : TextStyle.FULL);
}

switch(opType) {
case GET_WEEK:
return cal.get(Calendar.WEEK_OF_YEAR);
case GET_NUM_WEEKS:
return cal.getActualMaximum(Calendar.WEEK_OF_YEAR);
default:
throw new RuntimeException("Unknown op type " + opType);
}
private static int weekOfYear(EvalContext ctx, Value param, int firstDay,
int firstWeekType) {
return weekOfYear(param.getAsLocalDateTime(ctx), firstDay, firstWeekType);
}

} finally {
cal.setFirstDayOfWeek(origFirstDay);
cal.setMinimalDaysInFirstWeek(origMinDays);
}
private static int weekOfYear(LocalDateTime ldt, int firstDay,
int firstWeekType) {
WeekFields weekFields = weekFields(firstDay, firstWeekType);
return ldt.get(weekFields.weekOfWeekBasedYear());
}

private static int getQuarter(Calendar cal) {
// month is 0 based
int month = cal.get(Calendar.MONTH);
return (month / 3) + 1;
private static int weeksInYear(int year, WeekFields weekFields) {
return (int)LocalDate.of(year,2,1).range(weekFields.weekOfWeekBasedYear())
.getMaximum();
}

private static int getWeekOfYearYear(Calendar cal, int weekOfYear) {
// the "week of year" gets weird at the beginning/end of the year.
// e.g. 12/31 might be int the first week of the next year, and 1/1 might
// be in the last week of the previous year. we need to detect this and
// adjust the intervals accordingly
if(cal.get(Calendar.MONTH) == Calendar.JANUARY) {
if(weekOfYear >= 52) {
// this week of year is effectively for the previous year
cal.add(Calendar.YEAR, -1);
}
} else {
if(weekOfYear == 1) {
// this week of year is effectively for next year
cal.add(Calendar.YEAR, 1);
}
}
return cal.get(Calendar.YEAR);
private static int getQuarter(LocalDateTime ldt) {
int month = ldt.getMonthValue() - 1;
return (month / 3) + 1;
}

private static int getDayDiff(Calendar cal1, Calendar cal2) {
int y1 = cal1.get(Calendar.YEAR);
int d1 = cal1.get(Calendar.DAY_OF_YEAR);
int y2 = cal2.get(Calendar.YEAR);
int d2 = cal2.get(Calendar.DAY_OF_YEAR);
while(y2 > y1) {
cal2.add(Calendar.YEAR, -1);
d2 += cal2.getActualMaximum(Calendar.DAY_OF_YEAR);
y2 = cal2.get(Calendar.YEAR);
private static int getDayDiff(LocalDateTime ldt1, LocalDateTime ldt2) {
int y1 = ldt1.getYear();
int d1 = ldt1.getDayOfYear();
int y2 = ldt2.getYear();
int d2 = ldt2.getDayOfYear();
while(y2 > y1) {
ldt2 = ldt2.minusYears(1);
d2 += ldt2.range(ChronoField.DAY_OF_YEAR).getMaximum();
y2 = ldt2.getYear();
}
return d2 - d1;
}

private static int getHourDiff(Calendar cal1, Calendar cal2) {
int h1 = cal1.get(Calendar.HOUR_OF_DAY);
int h2 = cal2.get(Calendar.HOUR_OF_DAY);
int dayDiff = getDayDiff(cal1, cal2);
private static int getHourDiff(LocalDateTime ldt1, LocalDateTime ldt2) {
int h1 = ldt1.getHour();
int h2 = ldt2.getHour();
int dayDiff = getDayDiff(ldt1, ldt2);
return (h2 + (24 * dayDiff)) - h1;
}

private static int getMinuteDiff(Calendar cal1, Calendar cal2) {
int m1 = cal1.get(Calendar.MINUTE);
int m2 = cal2.get(Calendar.MINUTE);
int hourDiff = getHourDiff(cal1, cal2);
private static int getMinuteDiff(LocalDateTime ldt1, LocalDateTime ldt2) {
int m1 = ldt1.getMinute();
int m2 = ldt2.getMinute();
int hourDiff = getHourDiff(ldt1, ldt2);
return (m2 + (60 * hourDiff)) - m1;
}
}

+ 6
- 9
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java View File

@@ -18,10 +18,9 @@ package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Date;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@@ -310,16 +309,14 @@ public class DefaultFunctions
return ValueSupport.NULL_VAL;
}

Date d = param1.getAsDateTime(ctx);
LocalDateTime ldt = param1.getAsLocalDateTime(ctx);

int fmtType = getOptionalIntParam(ctx, params, 1, 0);
TemporalConfig.Type tempType = null;
switch(fmtType) {
case 0:
// vbGeneralDate
Calendar cal = ctx.getCalendar();
cal.setTime(d);
Value.Type valType = ValueSupport.getDateTimeType(cal);
Value.Type valType = ValueSupport.getDateTimeType(ldt);
switch(valType) {
case DATE:
tempType = TemporalConfig.Type.SHORT_DATE;
@@ -351,9 +348,9 @@ public class DefaultFunctions
throw new EvalException("Unknown format " + fmtType);
}

DateFormat sdf = ctx.createDateFormat(
DateTimeFormatter dtf = ctx.createDateFormatter(
ctx.getTemporalConfig().getDateTimeFormat(tempType));
return ValueSupport.toValue(sdf.format(d));
return ValueSupport.toValue(dtf.format(ldt));
}
});


+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java View File

@@ -49,7 +49,7 @@ public class DefaultNumberFunctions
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = Math.abs(param1.getAsDouble(ctx));
return ValueSupport.toDateValue(ctx, mathType, result);
return ValueSupport.toDateValueIfPossible(mathType, result);
case LONG:
return ValueSupport.toValue(Math.abs(param1.getAsLongInt(ctx)));
case DOUBLE:

+ 31
- 152
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java View File

@@ -17,26 +17,27 @@ 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.ParsePosition;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.AbstractMap;
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;
import java.util.Set;
import java.util.TimeZone;

import static com.healthmarketscience.jackcess.impl.expr.Expressionator.*;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.ParseException;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.ColumnImpl;


/**
@@ -53,15 +54,6 @@ class ExpressionTokenizer
private static final char DATE_LIT_QUOTE_CHAR = '#';
private static final char EQUALS_CHAR = '=';

// access times are based on this date (not the UTC base)
static final int BASE_DATE_YEAR = 1899;
static final int BASE_DATE_MONTH = 12;
static final int BASE_DATE_DAY = 30;
private static final String BASE_DATE_PREFIX = "1899/12/30 ";
private static final String BASE_DATE_FMT_PREFIX = "yyyy/M/d ";

private static final String IMPLICIT_YEAR_FMT_PREFIX = "yyyy ";

private static final byte IS_OP_FLAG = 0x01;
private static final byte IS_COMP_FLAG = 0x02;
private static final byte IS_DELIM_FLAG = 0x04;
@@ -305,14 +297,26 @@ class ExpressionTokenizer

// note that although we may parse in the time "24" format, we will
// display as the default time format
DateFormat parseDf = buf.getParseDateTimeFormat(type);
DateTimeFormatter parseDf = buf.getParseDateTimeFormat(type);

try {
return new Token(TokenType.LITERAL, parseComplete(parseDf, dateStr),
TemporalAccessor parsedInfo = parseDf.parse(dateStr);

LocalDate ld = ColumnImpl.BASE_LD;
if(type.includesDate()) {
ld = LocalDate.from(parsedInfo);
}

LocalTime lt = ColumnImpl.BASE_LT;
if(type.includesTime()) {
lt = LocalTime.from(parsedInfo);
}

return new Token(TokenType.LITERAL, LocalDateTime.of(ld, lt),
dateStr, type.getValueType());
} catch(java.text.ParseException pe) {
} catch(DateTimeException de) {
throw new ParseException(
"Invalid date/time literal " + dateStr + " " + buf, pe);
"Invalid date/time literal " + dateStr + " " + buf, de);
}
}

@@ -325,7 +329,7 @@ class ExpressionTokenizer
boolean hasAmPm = false;

if(hasTime) {
String[] amPmStrs = cfg.getDateFormatSymbols().getAmPmStrings();
String[] amPmStrs = cfg.getAmPmStrings();
String amStr = " " + amPmStrs[0];
String pmStr = " " + amPmStrs[1];
hasAmPm = (hasSuffix(dateStr, amStr) || hasSuffix(dateStr, pmStr));
@@ -352,23 +356,6 @@ class ExpressionTokenizer
suffStr, 0, suffStrLen));
}

static DateFormat createParseDateTimeFormat(TemporalConfig.Type type,
LocaleContext ctx)
{
if(type.isTimeOnly()) {
return new ParseTimeFormat(type, ctx);
}

TemporalConfig cfg = ctx.getTemporalConfig();
return ctx.createDateFormat(cfg.getDateTimeFormat(type));
}

static DateFormat createParseImplicitYearDateTimeFormat(
TemporalConfig.Type type, LocaleContext ctx)
{
return new ParseImplicitYearFormat(type, ctx);
}

private static Token maybeParseNumberLiteral(char firstChar, ExprBuf buf) {
StringBuilder sb = buf.getScratchBuffer().append(firstChar);
boolean hasDigit = isDigit(firstChar);
@@ -464,29 +451,14 @@ 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 final Map<TemporalConfig.Type,DateFormat> _dateTimeFmts =
new EnumMap<TemporalConfig.Type,DateFormat>(TemporalConfig.Type.class);
private final Map<TemporalConfig.Type,DateTimeFormatter> _dateTimeFmts =
new EnumMap<TemporalConfig.Type,DateTimeFormatter>(
TemporalConfig.Type.class);
private final StringBuilder _scratch = new StringBuilder();

private ExprBuf(String str, ParseContext ctx) {
@@ -538,10 +510,11 @@ class ExpressionTokenizer
return _ctx;
}

public DateFormat getParseDateTimeFormat(TemporalConfig.Type type) {
DateFormat df = _dateTimeFmts.get(type);
public DateTimeFormatter getParseDateTimeFormat(TemporalConfig.Type type) {
DateTimeFormatter df = _dateTimeFmts.get(type);
if(df == null) {
df = createParseDateTimeFormat(type, _ctx);
df = _ctx.createDateFormatter(
_ctx.getTemporalConfig().getDateTimeFormat(type));
_dateTimeFmts.put(type, df);
}
return df;
@@ -605,98 +578,4 @@ class ExpressionTokenizer
}
}

/**
* Base DateFormat implementation for parsing date/time formats where
* additional information is added on to the format in order for it to be
* parsed correctly.
*/
private static abstract class ParsePrefixFormat extends DateFormat
{
private static final long serialVersionUID = 0L;

private final DateFormat _parseDelegate;

private ParsePrefixFormat(String formatPrefix, String formatStr,
LocaleContext ctx) {
_parseDelegate = ctx.createDateFormat(formatPrefix + formatStr);
}

@Override
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition fieldPosition) {
throw new UnsupportedOperationException();
}

@Override
public Date parse(String source, ParsePosition pos) {
String prefix = getPrefix();

Date result = _parseDelegate.parse(prefix + source, pos);

// adjust index for original string
pos.setIndex(pos.getIndex() - prefix.length());

return result;
}

@Override
public Calendar getCalendar() {
return _parseDelegate.getCalendar();
}

@Override
public TimeZone getTimeZone() {
return _parseDelegate.getTimeZone();
}

protected abstract String getPrefix();
}

/**
* Special date/time format which will parse time-only strings "correctly"
* according to how access handles time-only values.
*/
private static final class ParseTimeFormat extends ParsePrefixFormat
{
private static final long serialVersionUID = 0L;

private ParseTimeFormat(TemporalConfig.Type timeType, LocaleContext ctx) {
super(BASE_DATE_FMT_PREFIX,
ctx.getTemporalConfig().getDateTimeFormat(timeType), ctx);
}

@Override
protected String getPrefix() {
// we parse as a full date/time in order to get the correct "base date"
// used by access
return BASE_DATE_PREFIX;
}
}

/**
* Special date/time format which will parse dates with implicit (current)
* years.
*/
private static final class ParseImplicitYearFormat extends ParsePrefixFormat
{
private static final long serialVersionUID = 0L;

private ParseImplicitYearFormat(TemporalConfig.Type type,
LocaleContext ctx) {
super(IMPLICIT_YEAR_FMT_PREFIX,
ctx.getTemporalConfig().getImplicitYearDateTimeFormat(type),
ctx);
}

@Override
protected String getPrefix() {
// need to get the current year
Calendar cal = getCalendar();
cal.setTimeInMillis(System.currentTimeMillis());
int year = cal.get(Calendar.YEAR);
// return a value matching IMPLICIT_YEAR_FMT_PREFIX
return year + " ";
}
}

}

+ 3
- 8
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java View File

@@ -17,13 +17,11 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -43,7 +41,6 @@ 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;
import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
@@ -68,8 +65,6 @@ public class Expressionator
}

public interface ParseContext extends LocaleContext {
public TemporalConfig getTemporalConfig();
public SimpleDateFormat createDateFormat(String formatStr);
public FunctionLookup getFunctionLookup();
}

@@ -1318,7 +1313,7 @@ public class Expressionator
case DATE:
case TIME:
case DATE_TIME:
return ValueSupport.toValue(valType, (Date)value);
return ValueSupport.toValue(valType, (LocalDateTime)value);
case LONG:
return ValueSupport.toValue((Integer)value);
case DOUBLE:
@@ -2081,7 +2076,7 @@ public class Expressionator
case DATE:
case TIME:
case DATE_TIME:
return val.getAsDateTime(ctx);
return val.getAsLocalDateTime(ctx);
case LONG:
return val.getAsLongInt(ctx);
case DOUBLE:

+ 4
- 4
src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java View File

@@ -17,9 +17,9 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

@@ -172,10 +172,10 @@ public class FormatUtil

@Override
public Value format(EvalContext ctx, Value expr, String fmtStr,
int firstDay, int firstWeekType) {
DateFormat sdf = ctx.createDateFormat(
int firstDay, int firstWeekType) {
DateTimeFormatter dtf = ctx.createDateFormatter(
ctx.getTemporalConfig().getDateTimeFormat(_type));
return ValueSupport.toValue(sdf.format(expr.getAsDateTime(ctx)));
return ValueSupport.toValue(dtf.format(expr.getAsLocalDateTime(ctx)));
}
}


+ 1
- 2
src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java View File

@@ -17,7 +17,6 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormatSymbols;

import com.healthmarketscience.jackcess.expr.EvalException;
@@ -84,7 +83,7 @@ public class StringValue extends BaseValue
// numberToDateValue may return null for out of range numbers)
try {
dateValue = DefaultDateFunctions.numberToDateValue(
ctx, getNumber(ctx).doubleValue());
getNumber(ctx).doubleValue());
} catch(EvalException ignored) {
// not a number, not a date/time
}

+ 28
- 21
src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java View File

@@ -18,9 +18,10 @@ package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern;

import com.healthmarketscience.jackcess.expr.EvalException;
@@ -99,36 +100,42 @@ public class ValueSupport
return new BigDecimalValue(normalize(s));
}

public static Value toDateValue(LocaleContext ctx, Value.Type type, double dd)
{
return toValue(type, new Date(
ColumnImpl.fromDateDouble(dd, ctx.getCalendar())));
static Value toDateValueIfPossible(Value.Type dateType, double dd) {
if(DefaultDateFunctions.isValidDateDouble(dd)) {
return ValueSupport.toValue(
dateType, ColumnImpl.ldtFromLocalDateDouble(dd));
}
return ValueSupport.toValue(dd);
}

public static Value toValue(LocalDate ld) {
return new DateTimeValue(
Value.Type.DATE, LocalDateTime.of(ld, ColumnImpl.BASE_LT));
}

public static Value toValue(Calendar cal) {
return new DateTimeValue(getDateTimeType(cal), cal.getTime());
public static Value toValue(LocalTime lt) {
return new DateTimeValue(
Value.Type.TIME, LocalDateTime.of(ColumnImpl.BASE_LD, lt));
}

public static Value.Type getDateTimeType(Calendar cal) {
boolean hasTime = ((cal.get(Calendar.HOUR_OF_DAY) != 0) ||
(cal.get(Calendar.MINUTE) != 0) ||
(cal.get(Calendar.SECOND) != 0));
public static Value toValue(LocalDateTime ldt) {
return new DateTimeValue(getDateTimeType(ldt), ldt);
}

boolean hasDate =
((cal.get(Calendar.YEAR) != ExpressionTokenizer.BASE_DATE_YEAR) ||
((cal.get(Calendar.MONTH) + 1) != ExpressionTokenizer.BASE_DATE_MONTH) ||
(cal.get(Calendar.DAY_OF_MONTH) != ExpressionTokenizer.BASE_DATE_DAY));
public static Value.Type getDateTimeType(LocalDateTime ldt) {
boolean hasDate = !ColumnImpl.BASE_LD.equals(ldt.toLocalDate());
boolean hasTime = !ColumnImpl.BASE_LT.equals(ldt.toLocalTime());

return (hasDate ?
(hasTime ? Value.Type.DATE_TIME : Value.Type.DATE) :
Value.Type.TIME);
}

public static Value toValue(Value.Type type, Date d) {
return new DateTimeValue(type, d);
public static Value toValue(Value.Type type, LocalDateTime ldt) {
return new DateTimeValue(type, ldt);
}

public static DateFormat getDateFormatForType(LocaleContext ctx, Value.Type type) {
public static DateTimeFormatter getDateFormatForType(LocaleContext ctx, Value.Type type) {
String fmtStr = null;
switch(type) {
case DATE:
@@ -143,7 +150,7 @@ public class ValueSupport
default:
throw new EvalException("Unexpected date/time type " + type);
}
return ctx.createDateFormat(fmtStr);
return ctx.createDateFormatter(fmtStr);
}

/**

+ 2
- 2
src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java View File

@@ -294,8 +294,8 @@ public class PropertyExpressionTest extends TestCase

public static void testCustomEvalConfig() throws Exception
{
TemporalConfig tempConf = new TemporalConfig("yyyy/M/d", "M/d",
"yyyy-MMM-d",
TemporalConfig tempConf = new TemporalConfig("[uuuu/]M/d",
"uuuu-MMM-d",
"hh.mm.ss a",
"HH.mm.ss", '/', '.',
Locale.US);

+ 14
- 4
src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java View File

@@ -16,6 +16,9 @@ limitations under the License.

package com.healthmarketscience.jackcess.impl;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -53,7 +56,7 @@ public class DatabaseReadWriteTest extends TestCase
db.close();
}
}
public void testWriteAndReadInMem() throws Exception {
for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
Database db = createMem(fileFormat);
@@ -61,7 +64,7 @@ public class DatabaseReadWriteTest extends TestCase
db.close();
}
}
private static void doTestWriteAndRead(Database db) throws Exception {
createTestTable(db);
Object[] row = createTestRow();
@@ -117,7 +120,7 @@ public class DatabaseReadWriteTest extends TestCase
}
}

public void testUpdateRow() throws Exception
public void testUpdateRow() throws Exception
{
for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
Database db = createMem(fileFormat);
@@ -250,11 +253,18 @@ public class DatabaseReadWriteTest extends TestCase
final long timeRange = 100000000L;
final long timeStep = 37L;

for(long time = testTime - timeRange; time < testTime + timeRange;
for(long time = testTime - timeRange; time < testTime + timeRange;
time += timeStep) {
double accTime = ColumnImpl.toLocalDateDouble(time);
long newTime = ColumnImpl.fromLocalDateDouble(accTime);
assertEquals(time, newTime);

Instant inst = Instant.ofEpochMilli(time);
LocalDateTime ldt = LocalDateTime.ofInstant(inst, ZoneOffset.UTC);

accTime = ColumnImpl.toDateDouble(ldt);
LocalDateTime newLdt = ColumnImpl.ldtFromLocalDateDouble(accTime);
assertEquals(ldt, newLdt);
}
}
}

+ 4
- 3
src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java View File

@@ -17,6 +17,7 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;

@@ -78,9 +79,9 @@ public class DefaultFunctionsTest extends TestCase
eval("=CSng(\"57.12345\")"));
assertEval("9786", "=CStr(9786)");
assertEval("-42", "=CStr(-42)");
assertEval(new Date(1041483600000L), "=CDate('01/02/2003')");
assertEval(new Date(1041508800000L), "=CDate('01/02/2003 7:00:00 AM')");
assertEval(new Date(-1948781520000L), "=CDate(3013.45)");
assertEval(LocalDateTime.of(2003,1,2,0,0), "=CDate('01/02/2003')");
assertEval(LocalDateTime.of(2003,1,2,7,0), "=CDate('01/02/2003 7:00:00 AM')");
assertEval(LocalDateTime.of(1908,3,31,10,48), "=CDate(3013.45)");


assertEval(-1, "=IsNull(Null)");

+ 15
- 18
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java View File

@@ -20,14 +20,13 @@ import java.io.BufferedReader;
import java.io.FileReader;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import javax.script.Bindings;
import javax.script.SimpleBindings;

import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.DatabaseBuilder;
import com.healthmarketscience.jackcess.TestUtil;
import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.Expression;
@@ -38,7 +37,6 @@ import com.healthmarketscience.jackcess.expr.ParseException;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.BaseEvalContext;
import com.healthmarketscience.jackcess.impl.expr.NumberFormatter;
import junit.framework.TestCase;

/**
@@ -318,11 +316,11 @@ public class ExpressionatorTest extends TestCase

public void testDateArith() throws Exception
{
assertEquals(new Date(1041508800000L), eval("=#01/02/2003# + #7:00:00 AM#"));
assertEquals(new Date(1041458400000L), eval("=#01/02/2003# - #7:00:00 AM#"));
assertEquals(new Date(1044680400000L), eval("=#01/02/2003# + '37'"));
assertEquals(new Date(1044680400000L), eval("='37' + #01/02/2003#"));
assertEquals(new Date(1041508800000L), eval("=#01/02/2003 7:00:00 AM#"));
assertEquals(LocalDateTime.of(2003,1,2,7,0), eval("=#01/02/2003# + #7:00:00 AM#"));
assertEquals(LocalDateTime.of(2003,1,1,17,0), eval("=#01/02/2003# - #7:00:00 AM#"));
assertEquals(LocalDateTime.of(2003,2,8,0,0), eval("=#01/02/2003# + '37'"));
assertEquals(LocalDateTime.of(2003,2,8,0,0), eval("='37' + #01/02/2003#"));
assertEquals(LocalDateTime.of(2003,1,2,7,0), eval("=#01/02/2003 7:00:00 AM#"));

assertEquals("2/8/2003", eval("=CStr(#01/02/2003# + '37')"));
assertEquals("9:24:00 AM", eval("=CStr(#7:00:00 AM# + 0.1)"));
@@ -404,7 +402,7 @@ public class ExpressionatorTest extends TestCase
assertEquals("foo37", eval("=\"foo\" + (12 + 25)"));
assertEquals("25foo12", eval("=\"25foo\" + 12"));

assertEquals(new Date(1485579600000L), eval("=#1/1/2017# + 27"));
assertEquals(LocalDateTime.of(2017,1,28,0,0), eval("=#1/1/2017# + 27"));
assertEquals(128208, eval("=#1/1/2017# * 3"));
}

@@ -590,15 +588,14 @@ public class ExpressionatorTest extends TestCase
return TemporalConfig.US_TEMPORAL_CONFIG;
}

public SimpleDateFormat createDateFormat(String formatStr) {
SimpleDateFormat sdf = DatabaseBuilder.createDateFormat(formatStr);
sdf.setTimeZone(TestUtil.TEST_TZ);
return sdf;
public DateTimeFormatter createDateFormatter(String formatStr) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(
formatStr, TemporalConfig.US_TEMPORAL_CONFIG.getLocale());
return dtf;
}

public Calendar getCalendar() {
return createDateFormat(getTemporalConfig().getDefaultDateTimeFormat())
.getCalendar();
public ZoneId getZoneId() {
return TestUtil.TEST_TZ.toZoneId();
}

public NumericConfig getNumericConfig() {

Loading…
Cancel
Save