diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2018-12-28 04:19:21 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2018-12-28 04:19:21 +0000 |
commit | 0f568a46209da6557cfeab5f351bc6ee432eb3ea (patch) | |
tree | d028bfaf7a201d55acd842e400869d9ed2eb1a03 /src | |
parent | 61024b0227e3bd4455a4e55fae0e498f40fdda5a (diff) | |
download | jackcess-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.java | 9 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java | 75 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java | 47 | ||||
-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.java | 4 | ||||
-rw-r--r-- | src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java | 70 | ||||
-rw-r--r-- | src/test/java/com/healthmarketscience/jackcess/TestUtil.java | 20 |
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 { |