aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2018-12-28 04:19:21 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2018-12-28 04:19:21 +0000
commit0f568a46209da6557cfeab5f351bc6ee432eb3ea (patch)
treed028bfaf7a201d55acd842e400869d9ed2eb1a03 /src
parent61024b0227e3bd4455a4e55fae0e498f40fdda5a (diff)
downloadjackcess-0f568a46209da6557cfeab5f351bc6ee432eb3ea.tar.gz
jackcess-0f568a46209da6557cfeab5f351bc6ee432eb3ea.zip
add system prop for date/time type; rework how date/times are written based on date/time type
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jdk8@1253 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/Database.java9
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java75
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java47
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java (renamed from src/main/java/com/healthmarketscience/jackcess/impl/ZoneContext.java)4
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java4
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java70
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/TestUtil.java20
7 files changed, 190 insertions, 39 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/Database.java b/src/main/java/com/healthmarketscience/jackcess/Database.java
index 13e45a5..4fa8741 100644
--- a/src/main/java/com/healthmarketscience/jackcess/Database.java
+++ b/src/main/java/com/healthmarketscience/jackcess/Database.java
@@ -106,7 +106,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
"com.healthmarketscience.jackcess.brokenNio";
/** system property which can be used to set the default sort order for
- * table columns. Value should be one {@link Table.ColumnOrder} enum
+ * table columns. Value should be one of {@link Table.ColumnOrder} enum
* values.
* @usage _intermediate_field_
*/
@@ -134,6 +134,13 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
public static final String ENABLE_EXPRESSION_EVALUATION_PROPERTY =
"com.healthmarketscience.jackcess.enableExpressionEvaluation";
+ /** system property which can be used to set the default date/Time type.
+ * Value should be one of {@link DateTimeType} enum values.
+ * @usage _general_field_
+ */
+ public static final String DATE_TIME_TYPE_PROPERTY =
+ "com.healthmarketscience.jackcess.dateTimeType";
+
/**
* Enum which indicates which version of Access created the database.
* @usage _general_class_
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
index 6ab88ab..4172874 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
@@ -76,7 +76,7 @@ import org.apache.commons.logging.LogFactory;
* @author Tim McCune
* @usage _intermediate_class_
*/
-public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
+public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeContext
{
protected static final Log LOG = LogFactory.getLog(ColumnImpl.class);
@@ -506,7 +506,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
return getDatabase().getZoneId();
}
- protected DateTimeFactory getDateTimeFactory() {
+ @Override
+ public DateTimeFactory getDateTimeFactory() {
return getDatabase().getDateTimeFactory();
}
@@ -1042,21 +1043,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
* Date/Calendar/Number/Temporal time value.
* @usage _advanced_method_
*/
- private static double toDateDouble(Object value, ZoneContext zc)
- {
- if(value instanceof TemporalAccessor) {
- return toDateDouble(toLocalDateTime((TemporalAccessor)value, zc));
- }
-
- // seems access stores dates in the local timezone. guess you just
- // hope you read it in the same timezone in which it was written!
- long time = toDateLong(value);
- time += getToLocalTimeZoneOffset(time, zc.getTimeZone());
- return toLocalDateDouble(time);
+ private static double toDateDouble(Object value, DateTimeContext dtc) {
+ return dtc.getDateTimeFactory().toDateDouble(value, dtc);
}
private static LocalDateTime toLocalDateTime(
- TemporalAccessor value, ZoneContext zc) {
+ TemporalAccessor value, DateTimeContext dtc) {
// handle some common Temporal types
if(value instanceof LocalDateTime) {
@@ -1065,10 +1057,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
if(value instanceof ZonedDateTime) {
// if the temporal value has a timezone, convert it to this db's timezone
return ((ZonedDateTime)value).withZoneSameInstant(
- zc.getZoneId()).toLocalDateTime();
+ dtc.getZoneId()).toLocalDateTime();
}
if(value instanceof Instant) {
- return LocalDateTime.ofInstant((Instant)value, zc.getZoneId());
+ return LocalDateTime.ofInstant((Instant)value, dtc.getZoneId());
}
if(value instanceof LocalDate) {
return ((LocalDate)value).atTime(BASE_LT);
@@ -1092,7 +1084,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
if(zone != null) {
// the Temporal has a zone, see if it is the right zone. if not,
// adjust it
- ZoneId zoneId = zc.getZoneId();
+ ZoneId zoneId = dtc.getZoneId();
if(!zoneId.equals(zone)) {
return ZonedDateTime.of(ld, lt, zone).withZoneSameInstant(zoneId)
.toLocalDateTime();
@@ -1107,6 +1099,16 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
}
}
+ private static Instant toInstant(TemporalAccessor value, DateTimeContext dtc) {
+ if(value instanceof ZonedDateTime) {
+ return ((ZonedDateTime)value).toInstant();
+ }
+ if(value instanceof Instant) {
+ return (Instant)value;
+ }
+ return toLocalDateTime(value, dtc).atZone(dtc.getZoneId()).toInstant();
+ }
+
static double toLocalDateDouble(long time) {
time += MILLIS_BETWEEN_EPOCH_AND_1900;
@@ -2191,7 +2193,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
}
}
- static DateTimeFactory getDateTimeFactory(DateTimeType type) {
+ protected static DateTimeFactory getDateTimeFactory(DateTimeType type) {
return ((type == DateTimeType.LOCAL_DATE_TIME) ?
LDT_DATE_TIME_FACTORY : DEF_DATE_TIME_FACTORY);
}
@@ -2676,19 +2678,21 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
/**
* Factory which handles date/time values appropriately for a DateTimeType.
*/
- static abstract class DateTimeFactory
+ protected static abstract class DateTimeFactory
{
public abstract DateTimeType getType();
public abstract Object fromDateBits(ColumnImpl col, long dateBits);
+ public abstract double toDateDouble(Object value, DateTimeContext dtc);
+
public abstract Object toInternalValue(DatabaseImpl db, Object value);
}
/**
* Factory impl for legacy Date handling.
*/
- static final class DefaultDateTimeFactory extends DateTimeFactory
+ private static final class DefaultDateTimeFactory extends DateTimeFactory
{
@Override
public DateTimeType getType() {
@@ -2703,6 +2707,23 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
}
@Override
+ public double toDateDouble(Object value, DateTimeContext dtc) {
+ // ZoneId and TimeZone have different rules for older timezones, so we
+ // need to consistently use one or the other depending on the date/time
+ // type
+ long time = 0L;
+ if(value instanceof TemporalAccessor) {
+ time = toInstant((TemporalAccessor)value, dtc).toEpochMilli();
+ } else {
+ time = toDateLong(value);
+ }
+ // seems access stores dates in the local timezone. guess you just
+ // hope you read it in the same timezone in which it was written!
+ time += getToLocalTimeZoneOffset(time, dtc.getTimeZone());
+ return toLocalDateDouble(time);
+ }
+
+ @Override
public Object toInternalValue(DatabaseImpl db, Object value) {
return ((value instanceof Date) ? value :
new Date(toDateLong(value)));
@@ -2712,7 +2733,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
/**
* Factory impl for LocalDateTime handling.
*/
- static final class LDTDateTimeFactory extends DateTimeFactory
+ private static final class LDTDateTimeFactory extends DateTimeFactory
{
@Override
public DateTimeType getType() {
@@ -2725,6 +2746,18 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext
}
@Override
+ public double toDateDouble(Object value, DateTimeContext dtc) {
+ // ZoneId and TimeZone have different rules for older timezones, so we
+ // need to consistently use one or the other depending on the date/time
+ // type
+ if(!(value instanceof TemporalAccessor)) {
+ value = Instant.ofEpochMilli(toDateLong(value));
+ }
+ return ColumnImpl.toDateDouble(
+ toLocalDateTime((TemporalAccessor)value, dtc));
+ }
+
+ @Override
public Object toInternalValue(DatabaseImpl db, Object value) {
if(value instanceof TemporalAccessor) {
return toLocalDateTime((TemporalAccessor)value, db);
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
index 1a34dbd..c005651 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
@@ -88,7 +88,7 @@ import org.apache.commons.logging.LogFactory;
* @author Tim McCune
* @usage _intermediate_class_
*/
-public class DatabaseImpl implements Database, ZoneContext
+public class DatabaseImpl implements Database, DateTimeContext
{
private static final Log LOG = LogFactory.getLog(DatabaseImpl.class);
@@ -346,8 +346,7 @@ public class DatabaseImpl implements Database, ZoneContext
/** shared context for evaluating expressions */
private DBEvalContext _evalCtx;
/** factory for the appropriate date/time type */
- private ColumnImpl.DateTimeFactory _dtf =
- ColumnImpl.getDateTimeFactory(DateTimeType.DATE);
+ private ColumnImpl.DateTimeFactory _dtf;
/**
* Open an existing Database. If the existing file is not writeable or the
@@ -537,8 +536,9 @@ public class DatabaseImpl implements Database, ZoneContext
_allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
_evaluateExpressions = getDefaultEvaluateExpressions();
_fileFormat = fileFormat;
- _pageChannel = new PageChannel(channel, closeChannel, _format, autoSync);
setZoneInfo(timeZone, null);
+ _dtf = ColumnImpl.getDateTimeFactory(getDefaultDateTimeType());
+ _pageChannel = new PageChannel(channel, closeChannel, _format, autoSync);
if(provider == null) {
provider = DefaultCodecProvider.INSTANCE;
}
@@ -710,7 +710,8 @@ public class DatabaseImpl implements Database, ZoneContext
_dtf = ColumnImpl.getDateTimeFactory(dateTimeType);
}
- protected ColumnImpl.DateTimeFactory getDateTimeFactory() {
+ @Override
+ public ColumnImpl.DateTimeFactory getDateTimeFactory() {
return _dtf;
}
@@ -2043,16 +2044,8 @@ public class DatabaseImpl implements Database, ZoneContext
*/
public static Table.ColumnOrder getDefaultColumnOrder()
{
- String coProp = System.getProperty(COLUMN_ORDER_PROPERTY);
- if(coProp != null) {
- coProp = coProp.trim();
- if(coProp.length() > 0) {
- return Table.ColumnOrder.valueOf(coProp);
- }
- }
-
- // use default order
- return DEFAULT_COLUMN_ORDER;
+ return getEnumSystemProperty(Table.ColumnOrder.class, COLUMN_ORDER_PROPERTY,
+ DEFAULT_COLUMN_ORDER);
}
/**
@@ -2101,6 +2094,17 @@ public class DatabaseImpl implements Database, ZoneContext
}
/**
+ * Returns the default DateTimeType. This defaults to
+ * {@link DateTimeType#DATE}, but can be overridden using the system
+ * property {@value com.healthmarketscience.jackcess.Database#DATE_TIME_TYPE_PROPERTY}.
+ * @usage _advanced_method_
+ */
+ public static DateTimeType getDefaultDateTimeType() {
+ return getEnumSystemProperty(DateTimeType.class, DATE_TIME_TYPE_PROPERTY,
+ DateTimeType.DATE);
+ }
+
+ /**
* Copies the given db InputStream to the given channel using the most
* efficient means possible.
*/
@@ -2196,6 +2200,19 @@ public class DatabaseImpl implements Database, ZoneContext
return msg + " (Db=" + dbName + ")";
}
+ private static <E extends Enum<E>> E getEnumSystemProperty(
+ Class<E> enumClass, String propName, E defaultValue)
+ {
+ String prop = System.getProperty(propName);
+ if(prop != null) {
+ prop = prop.trim().toUpperCase();
+ if(!prop.isEmpty()) {
+ return Enum.valueOf(enumClass, prop);
+ }
+ }
+ return defaultValue;
+ }
+
/**
* Utility class for storing table page number and actual name.
*/
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ZoneContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java
index 0134e1f..8045755 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/ZoneContext.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java
@@ -24,9 +24,11 @@ import java.util.TimeZone;
*
* @author James Ahlborn
*/
-interface ZoneContext
+interface DateTimeContext
{
public ZoneId getZoneId();
public TimeZone getTimeZone();
+
+ public ColumnImpl.DateTimeFactory getDateTimeFactory();
}
diff --git a/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java b/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java
index 1aa2483..0a33a99 100644
--- a/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java
+++ b/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java
@@ -995,6 +995,10 @@ public class DatabaseTest extends TestCase
public TimeZone getTimeZone() { return tz; }
@Override
public ZoneId getZoneId() { return null; }
+ @Override
+ public ColumnImpl.DateTimeFactory getDateTimeFactory() {
+ return getDateTimeFactory(DateTimeType.DATE);
+ }
};
SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd");
diff --git a/src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java b/src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java
index 637629a..e17bbfb 100644
--- a/src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java
+++ b/src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java
@@ -62,7 +62,71 @@ public class LocalDateTimeTest extends TestCase
super(name);
}
- public void testAncientDates() throws Exception
+ public void testWriteAndReadLocalDate() throws Exception {
+ for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+ Database db = createMem(fileFormat);
+
+ db.setDateTimeType(DateTimeType.LOCAL_DATE_TIME);
+
+ Table table = new TableBuilder("test")
+ .addColumn(new ColumnBuilder("name", DataType.TEXT))
+ .addColumn(new ColumnBuilder("date", DataType.SHORT_DATE_TIME))
+ .toTable(db);
+
+ // since jackcess does not really store millis, shave them off before
+ // storing the current date/time
+ long curTimeNoMillis = (System.currentTimeMillis() / 1000L);
+ curTimeNoMillis *= 1000L;
+
+ DateFormat df = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
+ List<Date> dates =
+ new ArrayList<Date>(
+ Arrays.asList(
+ df.parse("19801231 00:00:00"),
+ df.parse("19930513 14:43:27"),
+ null,
+ df.parse("20210102 02:37:00"),
+ new Date(curTimeNoMillis)));
+
+ Calendar c = Calendar.getInstance();
+ for(int year = 1801; year < 2050; year +=3) {
+ for(int month = 0; month <= 12; ++month) {
+ for(int day = 1; day < 29; day += 3) {
+ c.clear();
+ c.set(Calendar.YEAR, year);
+ c.set(Calendar.MONTH, month);
+ c.set(Calendar.DAY_OF_MONTH, day);
+ dates.add(c.getTime());
+ }
+ }
+ }
+
+ ((DatabaseImpl)db).getPageChannel().startWrite();
+ try {
+ for(Date d : dates) {
+ table.addRow("row " + d, d);
+ }
+ } finally {
+ ((DatabaseImpl)db).getPageChannel().finishWrite();
+ }
+
+ List<LocalDateTime> foundDates = new ArrayList<LocalDateTime>();
+ for(Row row : table) {
+ foundDates.add(row.getLocalDateTime("date"));
+ }
+
+ assertEquals(dates.size(), foundDates.size());
+ for(int i = 0; i < dates.size(); ++i) {
+ Date expected = dates.get(i);
+ LocalDateTime found = foundDates.get(i);
+ assertSameDate(expected, found);
+ }
+
+ db.close();
+ }
+ }
+
+ public void testAncientLocalDates() throws Exception
{
ZoneId zoneId = ZoneId.of("America/New_York");
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("uuuu-MM-dd");
@@ -130,6 +194,10 @@ public class LocalDateTimeTest extends TestCase
public TimeZone getTimeZone() { return tz; }
@Override
public ZoneId getZoneId() { return zoneId; }
+ @Override
+ public ColumnImpl.DateTimeFactory getDateTimeFactory() {
+ return getDateTimeFactory(DateTimeType.LOCAL_DATE_TIME);
+ }
};
SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd");
diff --git a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java
index b5db277..83b2d7d 100644
--- a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java
+++ b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java
@@ -27,6 +27,10 @@ import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -379,6 +383,22 @@ public class TestUtil
}
}
+ static void assertSameDate(Date expected, LocalDateTime found)
+ {
+ if((expected == null) && (found == null)) {
+ return;
+ }
+ if((expected == null) || (found == null)) {
+ throw new AssertionError("Expected " + expected + ", found " + found);
+ }
+
+ LocalDateTime expectedLdt = LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(expected.getTime()),
+ ZoneId.systemDefault());
+
+ Assert.assertEquals(expectedLdt, found);
+ }
+
static void copyFile(File srcFile, File dstFile)
throws IOException
{