aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2018-12-15 06:13:06 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2018-12-15 06:13:06 +0000
commit843a44dfe074a2b587ff4aacbd4089ab22e6f087 (patch)
tree6e60f2cb73c9e113bf3158249f95b32d1b7caad3
parent99af2bc3a62fe0e710bb386365770439bc2984c3 (diff)
downloadjackcess-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
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java15
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java51
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/Value.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java21
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java76
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java23
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java21
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java3
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java8
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java12
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java449
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java15
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java183
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java11
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java8
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java3
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java49
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java4
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java18
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java7
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java33
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
* &lt;time&gt;".
*
* @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() {
ired-to-user-storage-createFerdinand Thiessen2024-10-161-1/+21 | * fix: add PasswordConfirmationRequired to update and delete methodyemkareems2024-10-161-0/+2 | * fix: add PasswordConfirmationRequired to create user storages endpointyemkareems2024-10-161-0/+2 |/ * Merge pull request #48425 from nextcloud/fix/files-renameFerdinand Thiessen2024-10-165-64/+125 |\ | * chore(assets): Recompile assetsfix/files-renamenextcloud-command2024-10-152-3/+3 | * fix(files): Ensure renaming state is correctly resetFerdinand Thiessen2024-10-153-61/+122 * | Merge pull request #48696 from nextcloud/fix/share-notificationsFerdinand Thiessen2024-10-165-28/+49 |\ \ | * | chore(assets): Recompile assetsfix/share-notificationsnextcloud-command2024-10-164-6/+6 | * | fix(files_sharing): Add proper user facing messages on successFerdinand Thiessen2024-10-161-22/+43 |/ / * | Fix(l10n): Update translations from TransifexNextcloud bot2024-10-1650-66/+232 * | Merge pull request #48332 from nextcloud/chore/migrate-encryption-away-from-h...Ferdinand Thiessen2024-10-1523-817/+837 |\ \ | |/ |/| | * chore: Unify user related events to always provide a `getUid` methodchore/migrate-encryption-away-from-hooksFerdinand Thiessen2024-10-156-1/+36 | * refactor(encryption): Migrate away from Hooks to typed eventsFerdinand Thiessen2024-10-1517-816/+801 |/ * Merge pull request #48684 from nextcloud/bug/noid/codeowners-caldav-carddavChristoph Wurst2024-10-151-3/+11 |\ | * chore: update groupware code ownersbug/noid/codeowners-caldav-carddavDaniel Kesselberg2024-10-141-3/+11 * | Merge pull request #48702 from nextcloud/chore/update-sass-loaderFerdinand Thiessen2024-10-15275-8500/+861 |\ \ | * | chore(assets): recompile assetschore/update-sass-loaderGrigorii K. Shartsev2024-10-14