From d6cd15b3f5d14cd08f0e507ce829bce342326f2f Mon Sep 17 00:00:00 2001 From: Alexander Galanin Date: Mon, 23 May 2016 07:01:52 +0300 Subject: [PATCH 1/3] Always format date/time in UTC --- .../java/org/sqlite/core/CoreConnection.java | 2 +- src/test/java/org/sqlite/AllTests.java | 1 + src/test/java/org/sqlite/DateTimeTest.java | 98 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/sqlite/DateTimeTest.java diff --git a/src/main/java/org/sqlite/core/CoreConnection.java b/src/main/java/org/sqlite/core/CoreConnection.java index 026bee42c..be72658fe 100644 --- a/src/main/java/org/sqlite/core/CoreConnection.java +++ b/src/main/java/org/sqlite/core/CoreConnection.java @@ -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(); diff --git a/src/test/java/org/sqlite/AllTests.java b/src/test/java/org/sqlite/AllTests.java index 0835043d2..b8c3a2724 100644 --- a/src/test/java/org/sqlite/AllTests.java +++ b/src/test/java/org/sqlite/AllTests.java @@ -8,6 +8,7 @@ @Suite.SuiteClasses({ BackupTest.class, ConnectionTest.class, + DateTimeTest.class, DBMetaDataTest.class, ExtendedCommandTest.class, ExtensionTest.class, diff --git a/src/test/java/org/sqlite/DateTimeTest.java b/src/test/java/org/sqlite/DateTimeTest.java new file mode 100644 index 000000000..72b0c39ed --- /dev/null +++ b/src/test/java/org/sqlite/DateTimeTest.java @@ -0,0 +1,98 @@ +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 + */ + +public class DateTimeTest +{ + // Mon May 23 06:06:21 MSK 2016 = Mon May 23 03:06:21 GMT 2016 + private static final long DATETIME_UNIX = 1463972781L; + private static final Date DATETIME = new Date(DATETIME_UNIX * 1000); + + 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(); + } + +} From d025a92f48ae1aa3ae1e818f9cf8d96e05d64762 Mon Sep 17 00:00:00 2001 From: Alexander Galanin Date: Tue, 24 May 2016 22:59:17 +0300 Subject: [PATCH 2/3] Fixed date/time test in QueryTest --- src/test/java/org/sqlite/QueryTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/sqlite/QueryTest.java b/src/test/java/org/sqlite/QueryTest.java index 718eace12..348f76917 100644 --- a/src/test/java/org/sqlite/QueryTest.java +++ b/src/test/java/org/sqlite/QueryTest.java @@ -18,6 +18,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Date; +import java.util.TimeZone; import org.sqlite.date.FastDateFormat; @@ -76,7 +77,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 + "')"); From 934a5909dcad730a8a5afde5592a497734298d23 Mon Sep 17 00:00:00 2001 From: Alexander Galanin Date: Tue, 24 May 2016 22:59:37 +0300 Subject: [PATCH 3/3] Always scan date in UTC --- .../java/org/sqlite/jdbc3/JDBC3ResultSet.java | 111 ++---------------- src/test/java/org/sqlite/DateTimeTest.java | 54 ++++++++- 2 files changed, 62 insertions(+), 103 deletions(-) diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3ResultSet.java b/src/main/java/org/sqlite/jdbc3/JDBC3ResultSet.java index b76391064..b53034bd1 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3ResultSet.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3ResultSet.java @@ -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; @@ -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 { - 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)); } /** @@ -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); } /** @@ -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); } /** @@ -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)); } /** @@ -1001,22 +924,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; @@ -1074,14 +991,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; - } - } diff --git a/src/test/java/org/sqlite/DateTimeTest.java b/src/test/java/org/sqlite/DateTimeTest.java index 72b0c39ed..82746b9bb 100644 --- a/src/test/java/org/sqlite/DateTimeTest.java +++ b/src/test/java/org/sqlite/DateTimeTest.java @@ -25,9 +25,12 @@ public class DateTimeTest { - // Mon May 23 06:06:21 MSK 2016 = Mon May 23 03:06:21 GMT 2016 + // 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; @@ -95,4 +98,53 @@ public void setDateText() throws SQLException { 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(); + } + }