git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jdk8@1238 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-3.0.0
@@ -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 |
@@ -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) { |
@@ -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 |
@@ -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()); |
@@ -40,7 +40,6 @@ import java.time.LocalDateTime; | |||
import java.time.LocalTime; | |||
import java.time.ZoneId; | |||
import java.time.ZonedDateTime; | |||
import java.time.temporal.Temporal; | |||
import java.time.temporal.TemporalAccessor; | |||
import java.time.temporal.TemporalQueries; | |||
import java.util.Calendar; | |||
@@ -108,14 +107,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
static final long MILLIS_BETWEEN_EPOCH_AND_1900 = | |||
25569L * MILLISECONDS_PER_DAY; | |||
static final LocalDate BASE_LD = LocalDate.of(1899, 12, 30); | |||
static final LocalTime BASE_LT = LocalTime.of(0, 0); | |||
static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT); | |||
public static final LocalDate BASE_LD = LocalDate.of(1899, 12, 30); | |||
public static final LocalTime BASE_LT = LocalTime.of(0, 0); | |||
public static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT); | |||
private static final DateTimeFactory DEF_DATE_TIME_FACTORY = | |||
new DefaultDateTimeFactory(); | |||
private static final DateTimeFactory LDT_DATE_TIME_FACTORY = | |||
static final DateTimeFactory LDT_DATE_TIME_FACTORY = | |||
new LDTDateTimeFactory(); | |||
/** | |||
@@ -929,30 +928,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
return fromDateDouble(value, getTimeZone()); | |||
} | |||
/** | |||
* Returns a java long time value converted from an access date double. | |||
* @usage _advanced_method_ | |||
*/ | |||
public static long fromDateDouble(double value, DatabaseImpl db) { | |||
return fromDateDouble(value, db.getTimeZone()); | |||
} | |||
/** | |||
* Returns a java long time value converted from an access date double. | |||
* @usage _advanced_method_ | |||
*/ | |||
@Deprecated | |||
public static long fromDateDouble(double value, Calendar c) { | |||
// FIXME, remove me | |||
return fromDateDouble(value, c.getTimeZone()); | |||
} | |||
public static long fromDateDouble(double value, TimeZone tz) { | |||
private static long fromDateDouble(double value, TimeZone tz) { | |||
long localTime = fromLocalDateDouble(value); | |||
return localTime - getFromLocalTimeZoneOffset(localTime, tz); | |||
} | |||
public static long fromLocalDateDouble(double value) { | |||
static long fromLocalDateDouble(double value) { | |||
long datePart = ((long)value) * MILLISECONDS_PER_DAY; | |||
// the fractional part of the double represents the time. it is always | |||
@@ -968,11 +949,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
} | |||
public static LocalDateTime ldtFromLocalDateDouble(double value) { | |||
Duration dateTimeOffset = durationFromLocalDateDouble1900(value); | |||
Duration dateTimeOffset = durationFromLocalDateDouble(value); | |||
return BASE_LDT.plus(dateTimeOffset); | |||
} | |||
private static Duration durationFromLocalDateDouble1900(double value) { | |||
private static Duration durationFromLocalDateDouble(double value) { | |||
long dateSeconds = ((long)value) * SECONDS_PER_DAY; | |||
// the fractional part of the double represents the time. it is always | |||
@@ -1027,7 +1008,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
* time value. | |||
* @usage _advanced_method_ | |||
*/ | |||
public static double toDateDouble(Object value, DatabaseImpl db) | |||
private static double toDateDouble(Object value, DatabaseImpl db) | |||
{ | |||
return toDateDouble(value, db.getTimeZone(), db.getZoneId()); | |||
} | |||
@@ -1037,23 +1018,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
* Date/Calendar/Number/Temporal time value. | |||
* @usage _advanced_method_ | |||
*/ | |||
@Deprecated | |||
public static double toDateDouble(Object value, Calendar c) { | |||
// FIXME remove me | |||
return toDateDouble(value, c.getTimeZone()); | |||
} | |||
public static double toDateDouble(Object value, TimeZone tz) | |||
{ | |||
return toDateDouble(value, tz, null); | |||
} | |||
/** | |||
* Returns an access date double converted from a java | |||
* Date/Calendar/Number/Temporal time value. | |||
* @usage _advanced_method_ | |||
*/ | |||
public static double toDateDouble(Object value, TimeZone tz, ZoneId zoneId) | |||
private static double toDateDouble(Object value, TimeZone tz, ZoneId zoneId) | |||
{ | |||
if(value instanceof TemporalAccessor) { | |||
return toDateDouble( | |||
@@ -1139,10 +1104,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
public static double toDateDouble(LocalDateTime ldt) { | |||
Duration dateTimeOffset = Duration.between(BASE_LDT, ldt); | |||
return toLocalDateDouble1900(dateTimeOffset); | |||
return toLocalDateDouble(dateTimeOffset); | |||
} | |||
private static double toLocalDateDouble1900(Duration time) { | |||
private static double toLocalDateDouble(Duration time) { | |||
long dateTimeSeconds = time.getSeconds(); | |||
long timeSeconds = dateTimeSeconds % SECONDS_PER_DAY; | |||
if(timeSeconds < 0) { | |||
@@ -1782,6 +1747,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
return ((Boolean)value) ? BigDecimal.valueOf(-1) : BigDecimal.ZERO; | |||
} else if(value instanceof Date) { | |||
return new BigDecimal(toDateDouble(value, db)); | |||
} else if(value instanceof LocalDateTime) { | |||
return new BigDecimal(toDateDouble((LocalDateTime)value)); | |||
} | |||
return new BigDecimal(value.toString()); | |||
} | |||
@@ -1812,6 +1779,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
return ((Boolean)value) ? -1 : 0; | |||
} else if(value instanceof Date) { | |||
return toDateDouble(value, db); | |||
} else if(value instanceof LocalDateTime) { | |||
return toDateDouble((LocalDateTime)value); | |||
} | |||
return Double.valueOf(value.toString()); | |||
} | |||
@@ -2146,6 +2115,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
public static Object toInternalValue(DataType dataType, Object value, | |||
DatabaseImpl db) | |||
throws IOException | |||
{ | |||
return toInternalValue(dataType, value, db, null); | |||
} | |||
static Object toInternalValue(DataType dataType, Object value, | |||
DatabaseImpl db, | |||
ColumnImpl.DateTimeFactory factory) | |||
throws IOException | |||
{ | |||
if(value == null) { | |||
return null; | |||
@@ -2171,7 +2148,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
return ((value instanceof Double) ? value : | |||
toNumber(value, db).doubleValue()); | |||
case SHORT_DATE_TIME: | |||
return db.getDateTimeFactory().toInternalValue(db, value); | |||
if(factory == null) { | |||
factory = db.getDateTimeFactory(); | |||
} | |||
return factory.toInternalValue(db, value); | |||
case TEXT: | |||
case MEMO: | |||
case GUID: |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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; |
@@ -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) { |
@@ -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); | |||
} |
@@ -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) { |
@@ -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: |
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
}); | |||
@@ -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: |
@@ -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 + " "; | |||
} | |||
} | |||
} |
@@ -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: |
@@ -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))); | |||
} | |||
} | |||
@@ -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 | |||
} |
@@ -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); | |||
} | |||
/** |
@@ -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); |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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)"); |
@@ -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() { |