From 4cec878242ec599264e015d574f60cfc9742fcdc Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 26 Apr 2016 16:41:11 +1200 Subject: [PATCH] #662 - Postgres Timestamp with time zone testing --- .../com/avaje/ebean/config/ServerConfig.java | 23 ++++ .../ebeaninternal/api/SpiEbeanServer.java | 6 + .../server/core/BeanRequest.java | 112 ++++++++++-------- .../server/core/DefaultServer.java | 11 +- .../server/core/InternalConfiguration.java | 35 +++++- .../core/timezone/CloneDataTimeZone.java | 22 ++++ .../server/core/timezone/DataTimeZone.java | 14 +++ .../server/core/timezone/NoDataTimeZone.java | 15 +++ .../core/timezone/SimpleDataTimeZone.java | 22 ++++ .../ebeaninternal/server/persist/Binder.java | 23 +++- .../server/persist/ExeCallableSql.java | 3 +- .../server/persist/ExeOrmUpdate.java | 2 +- .../server/persist/ExeUpdateSql.java | 2 +- .../server/persist/dml/DeleteHandler.java | 12 +- .../server/persist/dml/DmlHandler.java | 7 ++ .../server/persist/dml/InsertHandler.java | 5 +- .../server/persist/dml/UpdateHandler.java | 4 +- .../ebeaninternal/server/query/CQuery.java | 6 +- .../server/query/CQueryDelete.java | 2 +- .../server/query/CQueryFetchIds.java | 5 +- .../server/query/CQueryPlan.java | 16 ++- .../server/query/CQueryPlanRawSql.java | 4 +- .../server/query/CQueryPredicates.java | 5 + .../server/query/CQueryRowCount.java | 3 +- .../query/DefaultRelationalQueryEngine.java | 2 +- .../ebeaninternal/server/type/DataBind.java | 15 ++- .../server/type/RsetDataReader.java | 15 ++- .../server/type/RsetDataReaderIndexed.java | 6 +- .../ebean/server/type/TestTypeManager.java | 2 +- .../ebeaninternal/api/TDSpiEbeanServer.java | 6 + .../avaje/tests/timezone/TimezoneTests.java | 39 +++--- .../java/com/avaje/tests/timezone/TzBean.java | 53 +++++++++ .../com/avaje/tests/timezone/TzBeanTests.java | 94 +++++++++++++++ src/test/resources/ebean.properties | 1 + 34 files changed, 472 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/timezone/CloneDataTimeZone.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/timezone/DataTimeZone.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/timezone/NoDataTimeZone.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/timezone/SimpleDataTimeZone.java create mode 100644 src/test/java/com/avaje/tests/timezone/TzBean.java create mode 100644 src/test/java/com/avaje/tests/timezone/TzBeanTests.java diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index a376aee37..f1c12a2e7 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -224,6 +224,11 @@ public class ServerConfig { private boolean eagerFetchLobs; + /** + * Timezone used to get/set Timestamp values via JDBC. + */ + private String dataTimeZone; + private boolean ddlGenerate; private boolean ddlRun; @@ -841,6 +846,23 @@ public class ServerConfig { this.migrationConfig = migrationConfig; } + /** + * Return the time zone to use when reading/writing Timestamps via JDBC. + *

+ * When set a Calendar object is used in JDBC calls when reading/writing Timestamp objects. + *

+ */ + public String getDataTimeZone() { + return System.getProperty("ebean.dataTimeZone", dataTimeZone); + } + + /** + * Set the time zone to use when reading/writing Timestamps via JDBC. + */ + public void setDataTimeZone(String dataTimeZone) { + this.dataTimeZone = dataTimeZone; + } + /** * Return the suffix appended to the base table to derive the view that contains the union * of the base table and the history table in order to support asOf queries. @@ -2369,6 +2391,7 @@ public class ServerConfig { changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts); expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop); + dataTimeZone = p.get("dataTimeZone", dataTimeZone); asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix); asOfSysPeriod = p.get("asOfSysPeriod", asOfSysPeriod); historyTableSuffix = p.get("historyTableSuffix", historyTableSuffix); diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java index a01e2e5bd..42b56053c 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java @@ -16,6 +16,7 @@ import com.avaje.ebean.event.readaudit.ReadAuditPrepare; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.query.CQuery; import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import java.util.List; @@ -187,4 +188,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL * user context information (user id, user ip address etc). */ ReadAuditPrepare getReadAuditPrepare(); + + /** + * Return the DataTimeZone to use when reading/writing timestamps via JDBC. + */ + DataTimeZone getDataTimeZone(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java index 028f521d6..b5907bfe4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.core; import com.avaje.ebean.EbeanServer; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,35 +13,35 @@ import org.slf4j.LoggerFactory; public abstract class BeanRequest { private static final Logger log = LoggerFactory.getLogger(BeanRequest.class); - - /** - * The server processing the request. - */ - protected final SpiEbeanServer ebeanServer; - /** - * The transaction this is part of. - */ - protected SpiTransaction transaction; + /** + * The server processing the request. + */ + protected final SpiEbeanServer ebeanServer; - protected boolean createdTransaction; + /** + * The transaction this is part of. + */ + protected SpiTransaction transaction; - public BeanRequest(SpiEbeanServer ebeanServer, SpiTransaction t) { - this.ebeanServer = ebeanServer; - this.transaction = t; - } + protected boolean createdTransaction; - /** - * A helper method for creating an implicit transaction is it is required. - *

- * A transaction may have been passed in or active in the thread local. If - * not then create one implicitly to handle the request. - *

+ public BeanRequest(SpiEbeanServer ebeanServer, SpiTransaction t) { + this.ebeanServer = ebeanServer; + this.transaction = t; + } + + /** + * A helper method for creating an implicit transaction is it is required. + *

+ * A transaction may have been passed in or active in the thread local. If + * not then create one implicitly to handle the request. + *

* * @return True if a transaction was set (from current or created). - */ - public boolean createImplicitTransIfRequired() { - if (transaction != null) { + */ + public boolean createImplicitTransIfRequired() { + if (transaction != null) { return false; } transaction = ebeanServer.getCurrentServerTransaction(); @@ -50,7 +51,7 @@ public abstract class BeanRequest { createdTransaction = true; } return true; - } + } /** * Commit this transaction if it was created for this request. @@ -61,40 +62,40 @@ public abstract class BeanRequest { } } - /** - * Rollback the transaction if it was created for this request. - */ - public void rollbackTransIfRequired() { - if (createdTransaction) { - try { - transaction.rollback(); - } catch (Exception e) { - // Just log this and carry on. A previous exception has been - // thrown and if this rollback throws exception it likely means - // that the connection is broken (and the datasource and db will cleanup) - log.error("Error trying to rollback a transaction (after a prior exception thrown)", e); - } - } - } + /** + * Rollback the transaction if it was created for this request. + */ + public void rollbackTransIfRequired() { + if (createdTransaction) { + try { + transaction.rollback(); + } catch (Exception e) { + // Just log this and carry on. A previous exception has been + // thrown and if this rollback throws exception it likely means + // that the connection is broken (and the datasource and db will cleanup) + log.error("Error trying to rollback a transaction (after a prior exception thrown)", e); + } + } + } - /** - * Return the server processing the request. Made available for - * BeanController and BeanFinder. - */ - public EbeanServer getEbeanServer() { - return ebeanServer; - } + /** + * Return the server processing the request. Made available for + * BeanController and BeanFinder. + */ + public EbeanServer getEbeanServer() { + return ebeanServer; + } public SpiEbeanServer getServer() { return ebeanServer; } - /** - * Return the Transaction associated with this request. - */ - public SpiTransaction getTransaction() { - return transaction; - } + /** + * Return the Transaction associated with this request. + */ + public SpiTransaction getTransaction() { + return transaction; + } /** * Return true if SQL should be logged for this transaction. @@ -109,4 +110,11 @@ public abstract class BeanRequest { public boolean isLogSummary() { return transaction.isLogSummary(); } + + /** + * Return the DataTimeZone to use. + */ + public DataTimeZone getDataTimeZone() { + return ebeanServer.getDataTimeZone(); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 1a10db6e9..a227e2c16 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -64,6 +64,7 @@ import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; import com.avaje.ebeaninternal.server.transaction.TransactionManager; import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import com.avaje.ebeaninternal.util.ParamTypeHelper; import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo; import com.avaje.ebeanservice.docstore.api.DocStoreIntegration; @@ -108,6 +109,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final TransactionScopeManager transactionScopeManager; + private final DataTimeZone dataTimeZone; + private final CallStackFactory callStackFactory = new DefaultCallStackFactory(); private final int maxCallStack; @@ -228,6 +231,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { this.beanLoader = new DefaultBeanLoader(this); this.jsonContext = config.createJsonContext(this); + this.dataTimeZone = config.getDataTimeZone(); DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this); this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor()); @@ -284,7 +288,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { public DatabasePlatform getDatabasePlatform() { return databasePlatform; } - + + @Override + public DataTimeZone getDataTimeZone() { + return dataTimeZone; + } + @Override public MetaInfoManager getMetaInfoManager() { return metaInfoManager; diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index 7a792ea43..373eba960 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -22,6 +22,9 @@ import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogPrepare; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogRegister; import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.core.timezone.CloneDataTimeZone; +import com.avaje.ebeaninternal.server.core.timezone.NoDataTimeZone; +import com.avaje.ebeaninternal.server.core.timezone.SimpleDataTimeZone; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; import com.avaje.ebeaninternal.server.deploy.DeployOrmXml; import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory; @@ -29,6 +32,7 @@ import com.avaje.ebeaninternal.server.deploy.parse.DeployCreateProperties; import com.avaje.ebeaninternal.server.deploy.parse.DeployInherit; import com.avaje.ebeaninternal.server.deploy.parse.DeployUtil; import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import org.avaje.datasource.DataSourcePool; import com.avaje.ebeaninternal.server.persist.Binder; import com.avaje.ebeaninternal.server.persist.DefaultPersister; @@ -79,6 +83,8 @@ public class InternalConfiguration { private final TypeManager typeManager; + private final DataTimeZone dataTimeZone; + private final Binder binder; private final DeployCreateProperties deployCreateProperties; @@ -136,7 +142,8 @@ public class InternalConfiguration { DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform(); - this.binder = getBinder(typeManager, databasePlatform); + this.dataTimeZone = initDataTimeZone(); + this.binder = getBinder(typeManager, databasePlatform, dataTimeZone); this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap); } @@ -218,15 +225,15 @@ public class InternalConfiguration { /** * For 'As Of' queries return the number of bind variables per predicate. */ - private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform) { + private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform, DataTimeZone dataTimeZone) { JsonExpressionHandler jsonHandler = getJsonExpressionHandler(databasePlatform); DbHistorySupport historySupport = databasePlatform.getHistorySupport(); if (historySupport == null) { - return new Binder(typeManager, 0, false, jsonHandler); + return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone); } - return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler); + return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler, dataTimeZone); } /** @@ -381,4 +388,24 @@ public class InternalConfiguration { return new DefaultTransactionScopeManager(transactionManager); } } + + /** + * Create the DataTimeZone implementation to use. + */ + private DataTimeZone initDataTimeZone() { + + String tz = serverConfig.getDataTimeZone(); + if (tz == null) { + return new NoDataTimeZone(); + } + if (getDatabasePlatform().getName().toLowerCase().startsWith("oracle")) { + return new CloneDataTimeZone(tz); + } else { + return new SimpleDataTimeZone(tz); + } + } + + public DataTimeZone getDataTimeZone() { + return dataTimeZone; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/timezone/CloneDataTimeZone.java b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/CloneDataTimeZone.java new file mode 100644 index 000000000..e0cd52f6c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/CloneDataTimeZone.java @@ -0,0 +1,22 @@ +package com.avaje.ebeaninternal.server.core.timezone; + +import java.util.Calendar; + +/** + * Implementation of DataTimeZone that clones the Calendar instance. + *

+ * Used with Oracle JDBC driver as that wants to mutate the Calender. + *

+ */ +public class CloneDataTimeZone extends SimpleDataTimeZone { + + public CloneDataTimeZone(String zoneId) { + super(zoneId); + } + + @Override + public Calendar getTimeZone() { + // return cloned copy for Oracle to muck around with + return (Calendar)zone.clone(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/timezone/DataTimeZone.java b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/DataTimeZone.java new file mode 100644 index 000000000..2509fd4a2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/DataTimeZone.java @@ -0,0 +1,14 @@ +package com.avaje.ebeaninternal.server.core.timezone; + +import java.util.Calendar; + +/** + * Define if a Calendar representing the time zone should be used in JDBC calls. + */ +public interface DataTimeZone { + + /** + * Return the Calendar to use for Timezone information. + */ + Calendar getTimeZone(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/timezone/NoDataTimeZone.java b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/NoDataTimeZone.java new file mode 100644 index 000000000..735af0202 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/NoDataTimeZone.java @@ -0,0 +1,15 @@ +package com.avaje.ebeaninternal.server.core.timezone; + +import java.util.Calendar; + +/** + * Implementation of DataTimeZone when no time zone is specified. + */ +public class NoDataTimeZone implements DataTimeZone { + + @Override + public Calendar getTimeZone() { + // return null so Calendar is not used + return null; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/timezone/SimpleDataTimeZone.java b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/SimpleDataTimeZone.java new file mode 100644 index 000000000..83c32260e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/timezone/SimpleDataTimeZone.java @@ -0,0 +1,22 @@ +package com.avaje.ebeaninternal.server.core.timezone; + +import java.util.Calendar; +import java.util.TimeZone; + +/** + * Implementation of DataTimeZone when single Calendar instance is used. + */ +public class SimpleDataTimeZone implements DataTimeZone { + + protected final Calendar zone; + + public SimpleDataTimeZone(String zoneId) { + this.zone = Calendar.getInstance(TimeZone.getTimeZone(zoneId)); + } + + @Override + public Calendar getTimeZone() { + // return null so Calendar is not used + return zone; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java index fdaacacdf..5c57c1066 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.persist; import java.math.BigDecimal; import java.sql.CallableStatement; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; @@ -13,6 +14,7 @@ import com.avaje.ebean.config.dbplatform.DbType; import com.avaje.ebeaninternal.api.BindParams; import com.avaje.ebeaninternal.server.core.JsonExpressionHandler; import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.ScalarType; import com.avaje.ebeaninternal.server.type.TypeManager; @@ -34,14 +36,19 @@ public class Binder { private final JsonExpressionHandler jsonExpressionHandler; + private final DataTimeZone dataTimeZone; + /** * Set the PreparedStatement with which to bind variables to. */ - public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause, JsonExpressionHandler jsonExpressionHandler) { + public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause, + JsonExpressionHandler jsonExpressionHandler, DataTimeZone dataTimeZone) { + this.typeManager = typeManager; this.asOfBindCount = asOfBindCount; this.bindAsOfWithFromClause = bindAsOfWithFromClause; this.jsonExpressionHandler = jsonExpressionHandler; + this.dataTimeZone = dataTimeZone; } /** @@ -91,6 +98,13 @@ public class Binder { } } + /** + * Bind the parameters to the preparedStatement returning the bind log. + */ + public String bind(BindParams bindParams, PreparedStatement statement) throws SQLException { + return bind(bindParams, new DataBind(dataTimeZone, statement)); + } + /** * Bind the list of positionedParameters in BindParams. */ @@ -392,4 +406,11 @@ public class Binder { public JsonExpressionHandler getJsonExpressionHandler() { return jsonExpressionHandler; } + + /** + * Create and return a DataBind for the statement. + */ + public DataBind dataBind(PreparedStatement stmt) { + return new DataBind(dataTimeZone, stmt); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java index d3cd9683c..89435be6b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java @@ -4,7 +4,6 @@ import com.avaje.ebeaninternal.api.BindParams; import com.avaje.ebeaninternal.api.SpiCallableSql; import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; -import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.util.BindParamsParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,7 +95,7 @@ public class ExeCallableSql { String bindLog = null; if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(cstmt)); + bindLog = binder.bind(bindParams, cstmt); } request.setBindLog(bindLog); diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java index bdbdba166..2b3480543 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java @@ -113,7 +113,7 @@ public class ExeOrmUpdate { String bindLog = null; if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(pstmt)); + bindLog = binder.bind(bindParams, pstmt); } request.setBindLog(bindLog); diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java index ebc58002e..377ce08fa 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java @@ -100,7 +100,7 @@ public class ExeUpdateSql { String bindLog = null; if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(pstmt)); + bindLog = binder.bind(bindParams, pstmt); } request.setBindLog(bindLog); diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java index 9d5991c9c..5f240a81f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java @@ -1,14 +1,12 @@ package com.avaje.ebeaninternal.server.persist.dml; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import javax.persistence.OptimisticLockException; - import com.avaje.ebeaninternal.api.DerivedRelationshipData; import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.type.DataBind; + +import javax.persistence.OptimisticLockException; +import java.sql.PreparedStatement; +import java.sql.SQLException; /** * Delete bean handler. @@ -37,7 +35,7 @@ public class DeleteHandler extends DmlHandler { } else { pstmt = getPstmt(t, sql, false); } - dataBind = new DataBind(pstmt); + dataBind = bind(pstmt); meta.bind(persistRequest, this); logSql(sql); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java index f85168f26..db0c8261a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java @@ -74,6 +74,13 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { return persistRequest; } + /** + * Bind to the statement returning the DataBind. + */ + protected DataBind bind(PreparedStatement stmt) { + return new DataBind(persistRequest.getDataTimeZone(), stmt); + } + /** * Get the sql and bind the statement. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java index 386edec12..efb9bf877 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java @@ -9,7 +9,6 @@ import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.persist.DmlUtil; -import com.avaje.ebeaninternal.server.type.DataBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -98,9 +97,7 @@ public class InsertHandler extends DmlHandler { } else { pstmt = getPstmt(t, sql, useGeneratedKeys); } - dataBind = new DataBind(pstmt); - - // bind the bean property values + dataBind = bind(pstmt); meta.bind(this, bean, withId, persistRequest.isPublish()); logSql(sql); diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java index 388331753..a945ff86d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java @@ -4,7 +4,6 @@ import com.avaje.ebeaninternal.api.DerivedRelationshipData; import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.api.SpiUpdatePlan; import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.type.DataBind; import javax.persistence.OptimisticLockException; import java.sql.PreparedStatement; @@ -47,8 +46,7 @@ public class UpdateHandler extends DmlHandler { } else { pstmt = getPstmt(t, sql, false); } - dataBind = new DataBind(pstmt); - + dataBind = bind(pstmt); meta.bind(persistRequest, this, updatePlan); setUpdateGenValues(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java index 523981f58..e8f96c930 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -335,11 +335,7 @@ public class CQuery implements DbReadContext, CancelableQuery { pstmt.setFetchSize(query.getBufferFetchSizeHint()); } - DataBind dataBind = new DataBind(pstmt); - - // bind keys for encrypted properties - queryPlan.bindEncryptedProperties(dataBind); - + DataBind dataBind = queryPlan.bindEncryptedProperties(pstmt); bindLog = predicates.bind(dataBind); // executeQuery diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java index a83362ffd..f99f4c6f8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java @@ -105,7 +105,7 @@ public class CQueryDelete { pstmt.setQueryTimeout(query.getTimeout()); } - bindLog = predicates.bind(new DataBind(pstmt)); + bindLog = predicates.bind(pstmt); rowCount = pstmt.executeUpdate(); long exeNano = System.nanoTime() - startNano; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java index a0e0a9dbb..7a7a3332e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java @@ -12,7 +12,6 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.DbReadContext; -import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.DataReader; import com.avaje.ebeaninternal.server.type.RsetDataReader; import org.slf4j.Logger; @@ -144,10 +143,10 @@ public class CQueryFetchIds { pstmt.setQueryTimeout(query.getTimeout()); } - bindLog = predicates.bind(new DataBind(pstmt)); + bindLog = predicates.bind(pstmt); ResultSet rset = pstmt.executeQuery(); - dataReader = new RsetDataReader(rset); + dataReader = new RsetDataReader(request.getDataTimeZone(), rset); boolean hitMaxRows = false; boolean hasMoreRows = false; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java index ba084fb19..2764cfb22 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java @@ -9,11 +9,13 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.DataReader; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import com.avaje.ebeaninternal.server.type.RsetDataReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.MessageDigest; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +67,8 @@ public class CQueryPlan { private final Class beanType; + protected final DataTimeZone dataTimeZone; + /** * Key used to identify the query plan in audit logging. */ @@ -76,6 +80,7 @@ public class CQueryPlan { public CQueryPlan(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) { this.server = request.getServer(); + this.dataTimeZone = server.getDataTimeZone(); this.beanType = request.getBeanDescriptor().getBeanType(); this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins()); this.planKey = request.getQueryPlanKey(); @@ -100,6 +105,7 @@ public class CQueryPlan { boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { this.server = request.getServer(); + this.dataTimeZone = server.getDataTimeZone(); this.beanType = request.getBeanDescriptor().getBeanType(); this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins()); this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql); @@ -127,17 +133,21 @@ public class CQueryPlan { } public DataReader createDataReader(ResultSet rset) { - - return new RsetDataReader(rset); + return new RsetDataReader(dataTimeZone, rset); } - public void bindEncryptedProperties(DataBind dataBind) throws SQLException { + /** + * Bind keys for encrypted properties if necessary returning the DataBind. + */ + public DataBind bindEncryptedProperties(PreparedStatement stmt) throws SQLException { + DataBind dataBind = new DataBind(dataTimeZone, stmt); if (encryptedProps != null) { for (int i = 0; i < encryptedProps.length; i++) { String key = encryptedProps[i].getEncryptKey().getStringValue(); dataBind.setString(key); } } + return dataBind; } public boolean isAutoTuned() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java index 6d8f427cf..cb04aa0da 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java @@ -24,9 +24,9 @@ public class CQueryPlanRawSql extends CQueryPlan { this.rsetIndexPositions = createIndexPositions(request, sqlTree); } + @Override public DataReader createDataReader(ResultSet rset) { - - return new RsetDataReaderIndexed(rset, rsetIndexPositions, isRowNumberIncluded()); + return new RsetDataReaderIndexed(dataTimeZone, rset, rsetIndexPositions, isRowNumberIncluded()); } private int[] createIndexPositions(OrmQueryRequest request, SqlTree sqlTree) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java index c42ec239f..ef8ac205f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -17,6 +17,7 @@ import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; import java.util.HashSet; @@ -125,6 +126,10 @@ public class CQueryPredicates { this.idValue = query.getId(); } + public String bind(PreparedStatement stmt) throws SQLException { + return bind(binder.dataBind(stmt)); + } + public String bind(DataBind dataBind) throws SQLException { if (query.isVersionsBetween() && binder.isBindAsOfWithFromClause()) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java index 445156ee9..0a35be81f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java @@ -116,8 +116,7 @@ public class CQueryRowCount { pstmt.setQueryTimeout(query.getTimeout()); } - bindLog = predicates.bind(new DataBind(pstmt)); - + bindLog = predicates.bind(pstmt); rset = pstmt.executeQuery(); if (!rset.next()) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java index e351629c1..733f35f6b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -81,7 +81,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(pstmt)); + bindLog = binder.bind(bindParams, pstmt); } if (request.isLogSql()) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java b/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java index 021379201..03a8c7d91 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java @@ -1,5 +1,7 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Reader; @@ -9,16 +11,20 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; +import java.util.Calendar; public class DataBind { + private final DataTimeZone dataTimeZone; + private final PreparedStatement pstmt; private final StringBuilder bindLog = new StringBuilder(); private int pos; - public DataBind(PreparedStatement pstmt) { + public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt) { + this.dataTimeZone = dataTimeZone; this.pstmt = pstmt; } @@ -108,7 +114,12 @@ public class DataBind { } public void setTimestamp(Timestamp v) throws SQLException { - pstmt.setTimestamp(++pos, v); + Calendar timeZone = dataTimeZone.getTimeZone(); + if (timeZone != null) { + pstmt.setTimestamp(++pos, v, timeZone); + } else { + pstmt.setTimestamp(++pos, v); + } } public void setTime(Time v) throws SQLException { diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java index c3c80c14e..da81e01b1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.type; import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -14,6 +15,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; +import java.util.Calendar; public class RsetDataReader implements DataReader { @@ -23,11 +25,14 @@ public class RsetDataReader implements DataReader { static final int stringInitialSize = 512; + private final DataTimeZone dataTimeZone; + private final ResultSet rset; protected int pos; - public RsetDataReader(ResultSet rset) { + public RsetDataReader(DataTimeZone dataTimeZone, ResultSet rset) { + this.dataTimeZone = dataTimeZone; this.rset = rset; } @@ -149,9 +154,13 @@ public class RsetDataReader implements DataReader { return rset.getTime(pos()); } - public Timestamp getTimestamp() throws SQLException { - return rset.getTimestamp(pos()); + Calendar cal = dataTimeZone.getTimeZone(); + if (cal != null) { + return rset.getTimestamp(pos(), cal); + } else { + return rset.getTimestamp(pos()); + } } public String getStringFromStream() throws SQLException { diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java index 0b774949a..36af385cf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java @@ -1,13 +1,15 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; + import java.sql.ResultSet; public class RsetDataReaderIndexed extends RsetDataReader { private final int[] rsetIndexPositions; - public RsetDataReaderIndexed(ResultSet rset, int[] rsetIndexPositions, boolean rowNumberIncluded) { - super(rset); + public RsetDataReaderIndexed(DataTimeZone dataTimeZone, ResultSet rset, int[] rsetIndexPositions, boolean rowNumberIncluded) { + super(dataTimeZone, rset); if (!rowNumberIncluded) { this.rsetIndexPositions = rsetIndexPositions; } else { diff --git a/src/test/java/com/avaje/ebean/server/type/TestTypeManager.java b/src/test/java/com/avaje/ebean/server/type/TestTypeManager.java index 0dd9ac0d9..835b6ba0e 100644 --- a/src/test/java/com/avaje/ebean/server/type/TestTypeManager.java +++ b/src/test/java/com/avaje/ebean/server/type/TestTypeManager.java @@ -89,7 +89,7 @@ public class TestTypeManager extends BaseTestCase { String val; public DummyDataReader(String val) { - super(null); + super(null, null); this.val = val; } diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index 7fea06173..663d80847 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -17,6 +17,7 @@ import com.avaje.ebean.text.json.JsonContext; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.query.CQuery; import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; +import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; @@ -44,6 +45,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } + @Override + public DataTimeZone getDataTimeZone() { + return null; + } + @Override public SpiServer getPluginApi() { return null; diff --git a/src/test/java/com/avaje/tests/timezone/TimezoneTests.java b/src/test/java/com/avaje/tests/timezone/TimezoneTests.java index f31b15502..f70574345 100644 --- a/src/test/java/com/avaje/tests/timezone/TimezoneTests.java +++ b/src/test/java/com/avaje/tests/timezone/TimezoneTests.java @@ -13,17 +13,14 @@ import java.sql.Timestamp; import java.util.Calendar; import java.util.TimeZone; +/** + * Must run this manually with various JVM timezones for insert and fetch. + */ public class TimezoneTests { - private final long now = 1460000000000L; - - private final Timestamp nowTs = new Timestamp(now); - - private final Calendar utcCalendar; + private final Timestamp nowTs = new Timestamp(1460000000000L); public TimezoneTests() { - utcCalendar = Calendar.getInstance(); - utcCalendar.setTimeZone(TimeZone.getTimeZone("UTC")); } @Ignore @@ -34,12 +31,12 @@ public class TimezoneTests { // insert("UTC"); // insert("America/Los_Angeles"); - System.out.println("Local"); - fetch(); +// System.out.println("Local"); +// fetch(); - System.out.println("UTC"); - setZone("UTC"); - fetch(); +// System.out.println("UTC"); +// setZone("UTC"); +// fetch(); System.out.println("LA"); setZone("America/Los_Angeles"); @@ -49,14 +46,15 @@ public class TimezoneTests { private void fetch() throws SQLException { Transaction transaction = Ebean.beginTransaction(); Connection connection = transaction.getConnection(); + PreparedStatement statement = connection.prepareStatement("select * from tztest"); ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { System.out.println(" zone:"+resultSet.getString("zone")); System.out.println(" ts:"+tsof(resultSet.getTimestamp("ts"))); System.out.println(" tstz:"+tsof(resultSet.getTimestamp("tstz"))); - System.out.println(" ts1:"+tsof(resultSet.getTimestamp("ts1", utcCal()))); - System.out.println("tstz1:"+tsof(resultSet.getTimestamp("tstz1", utcCal()))); + System.out.println(" ts1:"+tsof(resultSet.getTimestamp("ts1", cal()))); + System.out.println("tstz1:"+tsof(resultSet.getTimestamp("tstz1", cal()))); } System.out.println(""); resultSet.close(); @@ -65,7 +63,7 @@ public class TimezoneTests { } private String tsof(Timestamp timestamp) { - return ""+timestamp.getTime()+","+timestamp.toString(); + return ""+timestamp.getTime()+", "+timestamp.toString()+", "+timestamp.toInstant(); } private void setZone(String zone) { @@ -86,14 +84,17 @@ public class TimezoneTests { statement.setString(1, zone); statement.setTimestamp(2, nowTs); statement.setTimestamp(3, nowTs); - statement.setTimestamp(4, nowTs, utcCal()); - statement.setTimestamp(5, nowTs, utcCal()); + statement.setTimestamp(4, nowTs, cal()); + statement.setTimestamp(5, nowTs, cal()); statement.executeUpdate(); transaction.commit(); } - private Calendar utcCal() { - return (Calendar) utcCalendar.clone(); + private Calendar cal() { + + Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + //Calendar instance = Calendar.getInstance(TimeZone.getDefault()); + return (Calendar) instance.clone(); } } diff --git a/src/test/java/com/avaje/tests/timezone/TzBean.java b/src/test/java/com/avaje/tests/timezone/TzBean.java new file mode 100644 index 000000000..f7940f71f --- /dev/null +++ b/src/test/java/com/avaje/tests/timezone/TzBean.java @@ -0,0 +1,53 @@ +package com.avaje.tests.timezone; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.sql.Timestamp; + +@Entity +@Table(name="tz_bean") +public class TzBean { + + @Id + Long id; + + @Column(name = "mode") + String mode; + + Timestamp ts; + Timestamp tstz; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public Timestamp getTs() { + return ts; + } + + public void setTs(Timestamp ts) { + this.ts = ts; + } + + public Timestamp getTstz() { + return tstz; + } + + public void setTstz(Timestamp tstz) { + this.tstz = tstz; + } +} diff --git a/src/test/java/com/avaje/tests/timezone/TzBeanTests.java b/src/test/java/com/avaje/tests/timezone/TzBeanTests.java new file mode 100644 index 000000000..d03a20fa1 --- /dev/null +++ b/src/test/java/com/avaje/tests/timezone/TzBeanTests.java @@ -0,0 +1,94 @@ +package com.avaje.tests.timezone; + +import com.avaje.ebean.Ebean; +import org.junit.Ignore; +import org.junit.Test; + +import java.sql.Timestamp; +import java.util.List; +import java.util.TimeZone; +//import java.util.TimeZone; + + + +// Postgres +// manually create table +// create table tz_bean (id serial, mode varchar(40), ts timestamp, tstz timestamp with time zone); + +// MySql +// manually create table (note there is no timestamp with time zone type) +// create table tz_bean (id serial, mode varchar(40), ts timestamp, tstz timestamp); + +// Oracle +// create table tz_bean (id integer, run_mode varchar(40), ts timestamp, tstz timestamp); + +/** + * Must run this test manually with various time zones for insert and fetch. + */ +public class TzBeanTests { + + private final Timestamp nowTs = new Timestamp(1460000000000L); + + public TzBeanTests() { + + } + + /** + * Run this 5 times with different modes. + */ + @Ignore + @Test + public void insert_one_at_a_time() { + + //String mode = "noCal + local tz"; + +// String mode = "noCal + LA"; +// TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); + +// String mode = "noCal + UTC"; +// TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + +// System.setProperty("ebean.dataTimeZone", "UTC"); +// TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); +// String mode = "UTC + LA"; + + System.setProperty("ebean.dataTimeZone", "UTC"); + String mode = "UTC + NZST"; + + TzBean bean = new TzBean(); + bean.setMode(mode); + bean.setTs(nowTs); + bean.setTstz(nowTs); + + Ebean.save(bean); + } + + /** + * In separate JVM/execution fetch the beans with various timezones etc. + */ + @Ignore + @Test + public void fetch_beans() { + + + // set the jvm timezone + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); +// TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); + + // set the Calendar time zone to use in JDBC calls + System.setProperty("ebean.dataTimeZone", "UTC"); + + List list = Ebean.find(TzBean.class) + .findList(); + + for (TzBean bean : list) { + System.out.println(" mode:"+bean.getMode()); + System.out.println(" ts:"+tsof(bean.getTs())); + System.out.println(" tstz:"+tsof(bean.getTstz())); + } + } + + private String tsof(Timestamp timestamp) { + return ""+timestamp.getTime()+", "+timestamp.toString()+", "+timestamp.toInstant(); + } +} diff --git a/src/test/resources/ebean.properties b/src/test/resources/ebean.properties index ec8cca282..460eaf35d 100644 --- a/src/test/resources/ebean.properties +++ b/src/test/resources/ebean.properties @@ -102,6 +102,7 @@ datasource.hsqldb.databaseDriver=org.hsqldb.jdbcDriver datasource.mysql.username=unit datasource.mysql.password=unit datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:3306/unit +#datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:3306/unit?useLegacyDatetimeCode=false datasource.mysql.databaseDriver=com.mysql.jdbc.Driver