Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed date/time handling #110

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/sqlite/core/CoreConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ protected CoreConnection(String url, String fileName, Properties prop) throws SQ
SQLiteConfig config = new SQLiteConfig(prop);
this.dateClass = config.dateClass;
this.dateMultiplier = config.dateMultiplier;
this.dateFormat = FastDateFormat.getInstance(config.dateStringFormat);
this.dateFormat = FastDateFormat.getInstance(config.dateStringFormat, java.util.TimeZone.getTimeZone("UTC"));
this.dateStringFormat = config.dateStringFormat;
this.datePrecision = config.datePrecision;
this.transactionMode = config.getTransactionMode();
Expand Down
111 changes: 9 additions & 102 deletions src/main/java/org/sqlite/jdbc3/JDBC3ResultSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sqlite.core.CoreResultSet;
Expand Down Expand Up @@ -311,46 +312,21 @@ public Date getDate(int col) throws SQLException {
* @see java.sql.ResultSet#getDate(int, java.util.Calendar)
*/
public Date getDate(int col, Calendar cal) throws SQLException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this moves the driver away from the JDBC spec by ignoring the user supplied Calendar/Timezone information.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately there are no sqlite datatype like 'timestamp with timezone' in postgresql, so timezone information is lost during saving in sqlite database.

checkCalendar(cal);

switch (db.column_type(stmt.pointer, markCol(col))) {
case SQLITE_NULL:
return null;

case SQLITE_TEXT:
try {
FastDateFormat dateFormat = FastDateFormat.getInstance(stmt.conn.dateStringFormat, cal.getTimeZone());

return new java.sql.Date(dateFormat.parse(db.column_text(stmt.pointer, markCol(col))).getTime());
}
catch (Exception e) {
SQLException error = new SQLException("Error parsing time stamp");
error.initCause(e);

throw error;
}

case SQLITE_FLOAT:
return new Date(julianDateToCalendar(db.column_double(stmt.pointer, markCol(col)), cal).getTimeInMillis());

default: // SQLITE_INTEGER:
cal.setTimeInMillis(db.column_long(stmt.pointer, markCol(col)) * stmt.conn.dateMultiplier);
return new Date(cal.getTime().getTime());
}
return getDate(col);
}

/**
* @see java.sql.ResultSet#getDate(java.lang.String)
*/
public Date getDate(String col) throws SQLException {
return getDate(findColumn(col), Calendar.getInstance());
return getDate(findColumn(col));
}

/**
* @see java.sql.ResultSet#getDate(java.lang.String, java.util.Calendar)
*/
public Date getDate(String col, Calendar cal) throws SQLException {
return getDate(findColumn(col), cal);
return getDate(findColumn(col));
}

/**
Expand Down Expand Up @@ -474,32 +450,7 @@ public Time getTime(int col) throws SQLException {
* @see java.sql.ResultSet#getTime(int, java.util.Calendar)
*/
public Time getTime(int col, Calendar cal) throws SQLException {
checkCalendar(cal);

switch (db.column_type(stmt.pointer, markCol(col))) {
case SQLITE_NULL:
return null;

case SQLITE_TEXT:
try {
FastDateFormat dateFormat = FastDateFormat.getInstance(stmt.conn.dateStringFormat, cal.getTimeZone());

return new Time(dateFormat.parse(db.column_text(stmt.pointer, markCol(col))).getTime());
}
catch (Exception e) {
SQLException error = new SQLException("Error parsing time");
error.initCause(e);

throw error;
}

case SQLITE_FLOAT:
return new Time(julianDateToCalendar(db.column_double(stmt.pointer, markCol(col)), cal).getTimeInMillis());

default: //SQLITE_INTEGER
cal.setTimeInMillis(db.column_long(stmt.pointer, markCol(col)) * stmt.conn.dateMultiplier);
return new Time(cal.getTime().getTime());
}
return getTime(col);
}

/**
Expand Down Expand Up @@ -547,35 +498,7 @@ public Timestamp getTimestamp(int col) throws SQLException {
* @see java.sql.ResultSet#getTimestamp(int, java.util.Calendar)
*/
public Timestamp getTimestamp(int col, Calendar cal) throws SQLException {
if (cal == null) {
return getTimestamp(col);
}

switch (db.column_type(stmt.pointer, markCol(col))) {
case SQLITE_NULL:
return null;

case SQLITE_TEXT:
try {
FastDateFormat dateFormat = FastDateFormat.getInstance(stmt.conn.dateStringFormat, cal.getTimeZone());

return new Timestamp(dateFormat.parse(db.column_text(stmt.pointer, markCol(col))).getTime());
}
catch (Exception e) {
SQLException error = new SQLException("Error parsing time stamp");
error.initCause(e);

throw error;
}

case SQLITE_FLOAT:
return new Timestamp(julianDateToCalendar(db.column_double(stmt.pointer, markCol(col)), cal).getTimeInMillis());

default: //SQLITE_INTEGER
cal.setTimeInMillis(db.column_long(stmt.pointer, markCol(col)) * stmt.conn.dateMultiplier);

return new Timestamp(cal.getTime().getTime());
}
return getTimestamp(col);
}

/**
Expand All @@ -589,7 +512,7 @@ public Timestamp getTimestamp(String col) throws SQLException {
* @see java.sql.ResultSet#getTimestamp(java.lang.String, java.util.Calendar)
*/
public Timestamp getTimestamp(String c, Calendar ca) throws SQLException {
return getTimestamp(findColumn(c), ca);
return getTimestamp(findColumn(c));
}

/**
Expand Down Expand Up @@ -1016,22 +939,16 @@ public boolean rowUpdated() throws SQLException {
return false;
}

/**
* Transforms a Julian Date to java.util.Calendar object.
*/
private Calendar julianDateToCalendar(Double jd) {
return julianDateToCalendar(jd, Calendar.getInstance());
}

/**
* Transforms a Julian Date to java.util.Calendar object.
* Based on Guine Christian's function found here:
* http://java.ittoolbox.com/groups/technical-functional/java-l/java-function-to-convert-julian-date-to-calendar-date-1947446
*/
private Calendar julianDateToCalendar(Double jd, Calendar cal) {
private Calendar julianDateToCalendar(Double jd) {
if (jd == null) {
return null;
}
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));

int yyyy, dd, mm, hh, mn, ss, ms , A;

Expand Down Expand Up @@ -1089,14 +1006,4 @@ private Calendar julianDateToCalendar(Double jd, Calendar cal) {
return cal;
}

public void checkCalendar(Calendar cal) throws SQLException {
if (cal != null)
return;

SQLException e = new SQLException("Expected a calendar instance.");
e.initCause(new NullPointerException());

throw e;
}

}
3 changes: 2 additions & 1 deletion src/test/java/org/sqlite/AllTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@Suite.SuiteClasses({
BackupTest.class,
ConnectionTest.class,
DateTimeTest.class,
DBMetaDataTest.class,
ErrorMessageTest.class,
ExtendedCommandTest.class,
Expand All @@ -34,4 +35,4 @@
})
public class AllTests {
// runs all Tests
}
}
150 changes: 150 additions & 0 deletions src/test/java/org/sqlite/DateTimeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package org.sqlite;

import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.StringTokenizer;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/*
* Created by Alexander Galanin <al@galanin.nnov.ru>
*/

public class DateTimeTest
{
// Mon May 23 06:06:21.123 MSK 2016 = Mon May 23 03:06:21.123 GMT 2016
private static final long DATETIME_UNIX = 1463972781L;
private static final long DATETIME_MILLISECONDS = 123;
private static final long DATETIME_UNIX_HIPRECISION = DATETIME_UNIX * 1000 + DATETIME_MILLISECONDS;
private static final Date DATETIME = new Date(DATETIME_UNIX * 1000);
private static final Date DATETIME_HIPRECISION = new Date(DATETIME_UNIX_HIPRECISION);

private Connection conn;
private PreparedStatement stat;

@After
public void close() throws SQLException {
stat.close();
conn.close();
}

@Test
public void setDateUnix() throws SQLException {
SQLiteConfig config = new SQLiteConfig();

config.setReadOnly(true);
config.setDateClass("INTEGER");
config.setDatePrecision("SECONDS");

conn = DriverManager.getConnection("jdbc:sqlite:", config.toProperties());
stat = conn.prepareStatement("select strftime('%s', ?, 'unixepoch')");
stat.setDate(1, DATETIME);

ResultSet rs = stat.executeQuery();
assertTrue(rs.next());
assertEquals(rs.getLong(1), DATETIME_UNIX);
rs.close();
}

@Test
public void setDateJulian() throws SQLException {
SQLiteConfig config = new SQLiteConfig();

config.setReadOnly(true);
config.setDateClass("REAL");

conn = DriverManager.getConnection("jdbc:sqlite:", config.toProperties());
stat = conn.prepareStatement("select strftime('%s', ?)");
stat.setDate(1, DATETIME);

ResultSet rs = stat.executeQuery();
assertTrue(rs.next());
assertEquals(rs.getLong(1), DATETIME_UNIX);
rs.close();
}

/**
* Driver MUST format date/time in UTC because SQLite's internal date/time format is UTC.
*
* To test this in UTC timezone use java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("GMT+3"));
*/
@Test
public void setDateText() throws SQLException {
SQLiteConfig config = new SQLiteConfig();

config.setReadOnly(true);
config.setDateClass("TEXT");

conn = DriverManager.getConnection("jdbc:sqlite:", config.toProperties());
stat = conn.prepareStatement("select strftime('%s', ?)");
stat.setDate(1, DATETIME);

ResultSet rs = stat.executeQuery();
assertTrue(rs.next());
assertEquals(rs.getLong(1), DATETIME_UNIX);
rs.close();
}

@Test
public void getDateInt() throws SQLException {
SQLiteConfig config = new SQLiteConfig();

config.setReadOnly(true);
config.setDateClass("INTEGER");
config.setDatePrecision("SECONDS");

conn = DriverManager.getConnection("jdbc:sqlite:", config.toProperties());
stat = conn.prepareStatement("select " + String.valueOf(DATETIME_UNIX));

ResultSet rs = stat.executeQuery();
assertTrue(rs.next());
assertEquals(rs.getDate(1), DATETIME);
rs.close();
}

/**
* Driver MUST scan date/time in UTC because SQLite's internal date/time format is UTC.
*
* To test this in UTC timezone use java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("GMT+3"));
*/
@Test
public void getDateJulian() throws SQLException {
conn = DriverManager.getConnection("jdbc:sqlite:");
stat = conn.prepareStatement("select julianday(" + String.valueOf(DATETIME_UNIX) + ", 'unixepoch', '+' || (" + String.valueOf(DATETIME_MILLISECONDS) + " / 1000.0) || ' seconds')");

ResultSet rs = stat.executeQuery();
assertTrue(rs.next());
assertEquals(rs.getDate(1), DATETIME_HIPRECISION);
rs.close();
}

/**
* Driver MUST scan date/time in UTC because SQLite's internal date/time format is UTC.
*
* To test this in UTC timezone use java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("GMT+3"));
*/
@Test
public void getDateString() throws SQLException {
conn = DriverManager.getConnection("jdbc:sqlite:");
stat = conn.prepareStatement("select strftime('%Y-%m-%d %H:%M:%f', " + String.valueOf(DATETIME_UNIX) + ", 'unixepoch', '+' || (" + String.valueOf(DATETIME_MILLISECONDS) + " / 1000.0) || ' seconds')");

ResultSet rs = stat.executeQuery();
assertTrue(rs.next());
assertEquals(rs.getDate(1), DATETIME_HIPRECISION);
rs.close();
}

}
3 changes: 2 additions & 1 deletion src/test/java/org/sqlite/QueryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.TimeZone;

import org.junit.Test;
import org.sqlite.date.FastDateFormat;
Expand Down Expand Up @@ -89,7 +90,7 @@ public void dateTimeTest() throws Exception {
conn.createStatement().execute("create table sample (start_time datetime)");

Date now = new Date();
String date = FastDateFormat.getInstance(SQLiteConfig.DEFAULT_DATE_STRING_FORMAT).format(now);
String date = FastDateFormat.getInstance(SQLiteConfig.DEFAULT_DATE_STRING_FORMAT, TimeZone.getTimeZone("UTC")).format(now);

conn.createStatement().execute("insert into sample values(" + now.getTime() + ")");
conn.createStatement().execute("insert into sample values('" + date + "')");
Expand Down