Browse Source

round LDT date/times to millis; add some initial tests for LDT times

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jdk8@1237 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-3.0.0
James Ahlborn 5 years ago
parent
commit
99af2bc3a6

+ 38
- 7
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java View File

@@ -65,6 +65,7 @@ import com.healthmarketscience.jackcess.complex.ComplexValue;
import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
import com.healthmarketscience.jackcess.expr.Identifier;
import com.healthmarketscience.jackcess.impl.complex.ComplexValueForeignKeyImpl;
import com.healthmarketscience.jackcess.impl.expr.NumberFormatter;
import com.healthmarketscience.jackcess.util.ColumnValidator;
import com.healthmarketscience.jackcess.util.SimpleColumnValidator;
import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -95,6 +96,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
private static final long MILLISECONDS_PER_DAY = (24L * 60L * 60L * 1000L);
private static final long SECONDS_PER_DAY = (24L * 60L * 60L);
private static final long NANOS_PER_SECOND = 1_000_000_000L;
private static final long NANOS_PER_MILLI = 1_000_000L;
private static final long MILLIS_PER_SECOND = 1000L;

/**
* Access starts counting dates at Dec 30, 1899 (note, this strange date
@@ -980,13 +983,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {

double secondsDouble = (Math.abs(value) % 1.0d) * SECONDS_PER_DAY;
long timeSeconds = (long)secondsDouble;
long timeNanos = Math.round((secondsDouble % 1.0d) * NANOS_PER_SECOND);
long timeMillis = (long)(roundToMillis(secondsDouble % 1.0d) *
MILLIS_PER_SECOND);

return Duration.ofSeconds(dateSeconds + timeSeconds, timeNanos);
return Duration.ofSeconds(dateSeconds + timeSeconds,
timeMillis * NANOS_PER_MILLI);
}



/**
* Writes a date value.
*/
@@ -1053,7 +1056,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public static double toDateDouble(Object value, TimeZone tz, ZoneId zoneId)
{
if(value instanceof TemporalAccessor) {
return toDateDouble(toLocalDateTime((Temporal)value, tz, zoneId));
return toDateDouble(
toLocalDateTime((TemporalAccessor)value, tz, zoneId));
}

// seems access stores dates in the local timezone. guess you just
@@ -1147,8 +1151,25 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
long dateSeconds = dateTimeSeconds - timeSeconds;
long timeNanos = time.getNano();

double timeDouble = ((((double)timeNanos / NANOS_PER_SECOND) + timeSeconds)
/ SECONDS_PER_DAY);
// we have a difficult choice to make here between keeping a value which
// most accurately represents the bits saved and rounding to a value that
// would match what the user would expect too see. since we do a double
// to long conversion, we end up in a situation where the value might be
// 19.9999 seconds. access will display this as 20 seconds (access seems
// to only record times to second precision). if we return 19.9999, then
// when the value is written back out it will be exactly the same double
// (good), but will display as 19 seconds (bad because it looks wrong to
// the user). on the flip side, if we round, the value will display
// "correctly" to the user, but if the value is written back out, it will
// be a slightly different double value. this may not be a problem for
// most situations, but may result in incorrect index based lookups. in
// the old date time handling we use DateExt to store the original bits.
// in jdk8, we cannot extend LocalDateTime. for now, we will try
// returning the value rounded to milliseconds (technically still more
// precision than access uses but more likely to round trip to the same
// value).
double timeDouble = ((roundToMillis((double)timeNanos / NANOS_PER_SECOND) +
timeSeconds) / SECONDS_PER_DAY);

double dateDouble = ((double)dateSeconds / SECONDS_PER_DAY);

@@ -1159,6 +1180,16 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return dateDouble + timeDouble;
}

/**
* Rounds the given decimal to milliseconds (3 decimal places) using the
* standard access rounding mode.
*/
private static double roundToMillis(double dbl) {
return ((dbl == 0d) ? dbl :
new BigDecimal(dbl).setScale(3, NumberFormatter.ROUND_MODE)
.doubleValue());
}

/**
* @return an appropriate Date long value for the given object
*/

+ 160
- 0
src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java View File

@@ -0,0 +1,160 @@
/*
Copyright (c) 2018 James Ahlborn

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.healthmarketscience.jackcess;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;

import com.healthmarketscience.jackcess.impl.ColumnImpl;
import com.healthmarketscience.jackcess.impl.DatabaseImpl;
import com.healthmarketscience.jackcess.impl.RowIdImpl;
import com.healthmarketscience.jackcess.impl.RowImpl;
import com.healthmarketscience.jackcess.impl.TableImpl;
import com.healthmarketscience.jackcess.util.LinkResolver;
import junit.framework.TestCase;
import static com.healthmarketscience.jackcess.TestUtil.*;
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
import static com.healthmarketscience.jackcess.Database.*;

/**
*
* @author James Ahlborn
*/
public class LocalDateTimeTest extends TestCase
{
public LocalDateTimeTest(String name) throws Exception {
super(name);
}

public void testAncientDates() throws Exception
{
ZoneId zoneId = ZoneId.of("America/New_York");
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("uuuu-MM-dd");

List<String> dates = Arrays.asList("1582-10-15", "1582-10-14",
"1492-01-10", "1392-01-10");

for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
Database db = createMem(fileFormat);
db.setZoneId(zoneId);
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);

for(String dateStr : dates) {
LocalDate ld = LocalDate.parse(dateStr, sdf);
table.addRow("row " + dateStr, ld);
}

List<String> foundDates = new ArrayList<String>();
for(Row row : table) {
foundDates.add(sdf.format(row.getLocalDateTime("date")));
}

assertEquals(dates, foundDates);

db.close();
}

for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.OLD_DATES)) {
Database db = openCopy(testDB);
db.setDateTimeType(DateTimeType.LOCAL_DATE_TIME);

Table t = db.getTable("Table1");

List<String> foundDates = new ArrayList<String>();
for(Row row : t) {
foundDates.add(sdf.format(row.getLocalDateTime("DateField")));
}

assertEquals(dates, foundDates);

db.close();
}

}

public void testZoneId() throws Exception
{
ZoneId zoneId = ZoneId.of("America/New_York");
doTestZoneId(zoneId);

zoneId = ZoneId.of("Australia/Sydney");
doTestZoneId(zoneId);
}

private static void doTestZoneId(final ZoneId zoneId) throws Exception
{
final TimeZone tz = TimeZone.getTimeZone(zoneId);
ColumnImpl col = new ColumnImpl(null, null, DataType.SHORT_DATE_TIME, 0, 0, 0) {
@Override
protected TimeZone getTimeZone() { return tz; }
@Override
protected ZoneId getZoneId() { return zoneId; }
};

SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd");
df.setTimeZone(tz);

long startDate = df.parse("2012.01.01").getTime();
long endDate = df.parse("2013.01.01").getTime();

Calendar curCal = Calendar.getInstance(tz);
curCal.setTimeInMillis(startDate);

DateTimeFormatter sdf = DateTimeFormatter.ofPattern("uuuu.MM.dd HH:mm:ss");

while(curCal.getTimeInMillis() < endDate) {
Date curDate = curCal.getTime();
LocalDateTime curLdt = LocalDateTime.ofInstant(
Instant.ofEpochMilli(curDate.getTime()), zoneId);
LocalDateTime newLdt = ColumnImpl.ldtFromLocalDateDouble(
col.toDateDouble(curDate));
if(!curLdt.equals(newLdt)) {
System.out.println("FOO " + curLdt + " " + newLdt);
assertEquals(sdf.format(curLdt), sdf.format(newLdt));
}
curCal.add(Calendar.MINUTE, 30);
}
}

}

Loading…
Cancel
Save