diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2018-12-15 06:13:06 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2018-12-15 06:13:06 +0000 |
commit | 843a44dfe074a2b587ff4aacbd4089ab22e6f087 (patch) | |
tree | 6e60f2cb73c9e113bf3158249f95b32d1b7caad3 | |
parent | 99af2bc3a62fe0e710bb386365770439bc2984c3 (diff) | |
download | jackcess-843a44dfe074a2b587ff4aacbd4089ab22e6f087.tar.gz jackcess-843a44dfe074a2b587ff4aacbd4089ab22e6f087.zip |
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
25 files changed, 395 insertions, 637 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java index a90a80b..7b7a306 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java b/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java index 919d682..b441c88 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java @@ -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) { diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java index 118215e..ded758b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java index 0e52fa4..28785d1 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java @@ -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()); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index 423b19e..273a62a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -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()); } @@ -2147,6 +2116,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { 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: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java index 7f50f68..2fbec97 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java @@ -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; - } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index f132a99..2ff624e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -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(); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java index be3a249..dfcbb9d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java @@ -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; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java index d527c69..1e99e64 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java @@ -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) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java index 299cd2a..cae689d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java @@ -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); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java index 2b172d3..35e6ccf 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java @@ -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) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java index 5131a93..5f63dad 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java @@ -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: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java index e2de36d..f9d1e7b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java @@ -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; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java index a19ab0a..2ac67d2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java @@ -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; } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index 29c0f71..763d1d5 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -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)); } }); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java index 1ec08db..70eb5a9 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java @@ -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: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java index cc0fca4..4db436a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -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 + " "; - } - } - } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java index bee27ca..92da3f6 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -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: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java index 2c475eb..a21cd88 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java @@ -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))); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java index 197d8b5..ca74f35 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java @@ -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 } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java index 3040920..805cc32 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java @@ -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); } /** diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java index 384386e..bb26719 100644 --- a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java @@ -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); diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java index 561f1e8..104b266 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java @@ -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); } } } diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java index b50f2de..895bbed 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java @@ -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)"); diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index c67dfe7..67ad20b 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -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() { |