/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * * 14/05/2009 ailitchev - Bug 267929: Oracle 11.1.0.7: TIMESTAMP test '100 Years from now -> 2109-03-10 13:22:28.5 EST5EDT EDT' began to fail after Daylight Saving Time started * Changed the test "100 Years from now" to "Last DST year" in both TIMESTAMPDirectToFieldTester and TIMESTAMPTypeConversionTester: * instead of hitting the current date 100 years ahead * it now tests the current date on the latest year for which Daylight Saving Time is defined in Oracle db (by default lastDSTYear = 2020). * The change was done because "100 Years from now" fails when run during DST (though passes outside of it). * To figure out what is the latest year in your Oracle db for which DST defined, * one of Oracle jdbc people suggested printing out the table which includes entries for each supported year * (so the last entry in this table corresponds to the latest supported year). * Here is the code that prints table with oracle jdbc 11.2.0.0.2 and later: * String sTZ = conn.getSessionTimeZone(); * if (sTZ != null && ZONEIDMAP.isValidRegion(sTZ)) { * System.out.println("Session TZ is " + sTZ); * int regionID = ZONEIDMAP.getID(sTZ); * System.out.println("Session TZ ID is " + regionID); * TIMEZONETAB tzTab = TIMEZONETAB.getInstance(1); * if (tzTab.checkID(regionID)) { * tzTab.updateTable(conn, regionID); * } * tzTab.displayTable(regionID); * } * Here is the code that prints table with oracle jdbc 11.1.0.7 and earlier: * (note that unlike 11.2 the user has to explicitly set time zone * conn.setSessionTimeZone(connTimeZone); * String sTZ = conn.getSessionTimeZone(); * if (sTZ != null) { * System.out.println("Session TZ is " + sTZ); * int regionID = ZONEIDMAP.getID(sTZ); * System.out.println("Session TZ ID is " + regionID); * if (TIMEZONETAB.checkID(regionID)) { * TIMEZONETAB.updateTable(conn, regionID); * } * TIMEZONETAB.displayTable(regionID); * } ******************************************************************************/ package org.eclipse.persistence.testing.tests.types; import java.util.*; import java.sql.*; import oracle.jdbc.*; import org.eclipse.persistence.sessions.*; import org.eclipse.persistence.testing.framework.*; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPHelper; import org.eclipse.persistence.internal.sessions.AbstractSession; /** * This class is the super class of TIMESTAMP, TIMESTAMPTZ and TIMESTAMPLTZ specific * test cases. The current subclasses are TIMESTAMPDirectToFieldTester and TIMESTAMPTypeConversionTester * that test Direct-to-Field or TypeConversion mapping. */ public abstract class TIMESTAMPTester extends TypeTester { public Timestamp tsToTS; public Timestamp tsToTSTZ; public Timestamp tsToTSLTZ; public Calendar calToTSTZ; public Calendar calToTSLTZ; public java.util.Date utilDateToTS; public java.util.Date utilDateToTSTZ; public java.util.Date utilDateToTSLTZ; public java.sql.Date dateToTS; public java.sql.Date dateToTSTZ; public java.sql.Date dateToTSLTZ; public Time timeToTS; public Time timeToTSTZ; public Time timeToTSLTZ; // This tsToTS will be stored in DATE field to make sure Timestamp -> DATE is backward compatible. public Timestamp tsToDate; // This Calendar will be stored in DATE field to make sure Calendar -> DATE is backward compatible. // ** So... why was this commmented out? Is it backward compatible?? // public Calendar calToDate; public String sessionTimeZone; // isTimestampInGmt==true if driverVersion is 11.1.0.7 or later and // oracleConnection's property "oracle.jdbc.timestampTzInGmt" is set to "true". // The flag indicates whether TIMESTAMPTZ keeps its timestamp in GMT. public static boolean isTimestampInGmt; // true if driverVersion is 11.2.0.2 or later. public static boolean isLtzTimestampInGmt; // last year for which Daylight Saving Time is supported. static int lastDSTYear = 2020; public TIMESTAMPTester() { super("NEW"); } public TIMESTAMPTester(String nameOfTest, int year, int month, int date, int hrs, int min, int sec, int nano, int zoneMillis) { super(nameOfTest); calToTSTZ = Calendar.getInstance(); calToTSTZ.getTimeZone().setRawOffset(zoneMillis); calToTSTZ.set(year, month, date, hrs, min, sec); calToTSTZ.set(Calendar.MILLISECOND, nano / 1000000); calToTSLTZ = calToTSTZ; buildUtilDates(calToTSTZ.getTime()); buildTimestamps(utilDateToTS.getTime()); buildDates(Helper.dateFromTimestamp(tsToTS)); buildTimes(Helper.timeFromTimestamp(tsToTS)); // calToDate = c; } public TIMESTAMPTester(String nameOfTest, int year, int month, int date, int hrs, int min, int sec, int nano, String zoneId) { super(nameOfTest); /* TimeZone tz = TimeZone.getDefault(); tz.setID(zoneId); calToTSTZ = Calendar.getInstance(tz);*/ calToTSTZ = Calendar.getInstance(TimeZone.getTimeZone(zoneId)); calToTSTZ.set(year, month, date, hrs, min, sec); calToTSTZ.set(Calendar.MILLISECOND, nano / 1000000); calToTSLTZ = calToTSTZ; buildUtilDates(calToTSTZ.getTime()); buildTimestamps(utilDateToTS.getTime()); buildDates(Helper.dateFromTimestamp(tsToTS)); buildTimes(Helper.timeFromTimestamp(tsToTS)); // calToDate = c; } public TIMESTAMPTester(String nameOfTest, Calendar c) { super(nameOfTest); calToTSTZ = c; calToTSLTZ = calToTSTZ; buildUtilDates(calToTSTZ.getTime()); buildTimestamps(utilDateToTS.getTime()); buildDates(Helper.dateFromTimestamp(tsToTS)); buildTimes(Helper.timeFromTimestamp(tsToTS)); // calToDate = c; } public TIMESTAMPTester(String nameOfTest, long time) { super(nameOfTest); buildTimestamps(time); buildDates(Helper.dateFromTimestamp(tsToTS)); buildTimes(Helper.timeFromTimestamp(tsToTS)); buildUtilDates(new java.util.Date(time)); calToTSTZ = Calendar.getInstance(); calToTSTZ.setTime(utilDateToTS); calToTSLTZ = calToTSTZ; // calToDate = c; } public void setSessionTimezone(String timeZone) { this.sessionTimeZone = timeZone; } protected void setup(Session session) { super.setup(session); } public void buildTimestamps(long time) { tsToTS = new Timestamp(time); tsToTSTZ = tsToTS; tsToTSLTZ = tsToTS; tsToDate = tsToTS; } public void buildUtilDates(java.util.Date date) { utilDateToTS = date; utilDateToTSTZ = date; utilDateToTSLTZ = date; } public void buildDates(java.sql.Date date) { dateToTS = date; dateToTSTZ = date; dateToTSLTZ = date; } public void buildTimes(Time time) { timeToTS = time; timeToTSTZ = time; timeToTSLTZ = time; } public Timestamp getTsToTS() { return tsToTS; } public Timestamp getTsToTSTZ() { return tsToTSTZ; } public Timestamp getTsToTSLTZ() { return tsToTSLTZ; } public Calendar getCalToTSTZ() { return calToTSTZ; } public Calendar getCalToTSLTZ() { return calToTSLTZ; } public Timestamp getTsToDate() { return tsToDate; } public java.util.Date getUtilDateToTS() { return utilDateToTS; } public java.util.Date getUtilDateToTSTZ() { return utilDateToTSTZ; } public java.util.Date getUtilDateToTSLTZ() { return utilDateToTSLTZ; } public java.sql.Date getDateToTS() { return dateToTS; } public java.sql.Date getDateToTSTZ() { return dateToTSTZ; } public java.sql.Date getDateToTSLTZ() { return dateToTSLTZ; } public Time getTimeToTS() { return timeToTS; } public Time getTimeToTSTZ() { return timeToTSTZ; } public Time getTimeToTSLTZ() { return timeToTSLTZ; } public void setTsToTS(java.sql.Timestamp aTimestamp) { tsToTS = aTimestamp; } public void setTsToTSTZ(java.sql.Timestamp aTimestamp) { tsToTSTZ = aTimestamp; } public void setTsToTSLTZ(java.sql.Timestamp aTimestamp) { tsToTSLTZ = aTimestamp; } public void setCalToTSTZ(Calendar calToTSTZ) { this.calToTSTZ = calToTSTZ; } public void setCalToTSLTZ(Calendar calToTSLTZ) { this.calToTSLTZ = calToTSLTZ; } public void setTsToDate(java.sql.Timestamp aTimestamp) { tsToDate = aTimestamp; } public void setUtilDateToTS(java.util.Date aDate) { utilDateToTS = aDate; } public void setUtilDateToTSTZ(java.util.Date aDate) { utilDateToTSTZ = aDate; } public void setUtilDateToTSLTZ(java.util.Date aDate) { utilDateToTSLTZ = aDate; } public void setDateToTS(java.sql.Date aDate) { dateToTS = aDate; } public void setDateToTSTZ(java.sql.Date aDate) { dateToTSTZ = aDate; } public void setDateToTSLTZ(java.sql.Date aDate) { dateToTSLTZ = aDate; } public void setTimeToTS(Time aDate) { timeToTS = aDate; } public void setTimeToTSTZ(Time aDate) { timeToTSTZ = aDate; } public void setTimeToTSLTZ(Time aDate) { timeToTSLTZ = aDate; } /* public Calendar getCalToDate() { return calToDate; } public void setCalToDate(Calendar calToTSTZ) { this.calToDate = calToTSTZ; }*/ public String toString() { return getTestName() + " -> " + TIMESTAMPHelper.printCalendar(calToTSTZ); } // Calendar's time zone is inserted into the db if the test runs either with binding or with native sql - // otherwise time zone is simply not present in the inserting sql and therefore // Calendar-to-TIMESTAMPTZ and Calendar-to-TIMESTAMPLTZ comparisons fail because // the objects are written into the db with connection's sessionTimeZone instead of the calendar's time zone. boolean doesTestInsertCalendarTimeZone(WriteTypeObjectTest test) { return test.shouldBindAllParameters()==null || test.shouldBindAllParameters() || test.shouldUseNativeSQL(); } // In case connection's session is not default and isTimestampInGmt==true // tsToTSTZ, utilDateToTSTZ and timeToTSTZ don't work. // Note that dateToTSTZ and calToTSTZ work. // The reason is Oracle jdbc bug 8206596: // Timestamp for 5pm in default zone (say NewYork) written into TIMESTAMPTZ using setObject // is inserted into the db as 5pm LosAngeles (in case it's set as a session time zone). // That happens with both 11.1.0.6 and 11.1.0.7 ojdbc versions. // When the result is read back 11.1.0.7 driver (in case timestampTzInGmt prop. is set to true - which is default setting) // correctly reads 5pm LA - and fails comparison with 5pm NY, // however version 11.0.0.6 (and 11.1.0.7 with timestampTzInGmt prop. set to false) // incorrectly reads back 5pm in default zone - so the inserted and the read back objects are equal. // Note that this incorrect reading happens only in case the target Java type is Timestamp (util.Date, Time), // Eclipselink corrects the result in case the target type is Calendar. boolean doesTimestampTZWork() { return TimeZone.getDefault().getID().equals(this.sessionTimeZone) || !isTimestampInGmt; } // see the previous comment: // it used to work - due to even number of errors - in 11.1.0.7, // but no longer works in 11.2.0.2. // Again, as in TIMESTAMPTZ field case, // writing Timestamp, or Time, or Date, or util.Date while sessionTimeZone is not equal to default time zone // results in combining of a time in default time zone and session time zone. // Therefore 5p.m. in default zone America/New_York written through America/Los_Angeles sessionTimeZone // is recorded in the db. as 5p.m. America/Los_Angeles. boolean doesTimestampLTZWork() { return TimeZone.getDefault().getID().equals(this.sessionTimeZone) || !isLtzTimestampInGmt; } protected void test(WriteTypeObjectTest testCase) { try { if(this.sessionTimeZone != null) { getConnection((DatabaseSession)testCase.getSession()).setSessionTimeZone(this.sessionTimeZone); } } catch (Exception exception) { throw new TestProblemException("Error setting timezone on connection.", exception); } super.test(testCase); } protected void verify(WriteTypeObjectTest testCase) throws TestException { try { super.verify(testCase); } catch (TestException e) { TIMESTAMPTester fromDatabase = (TIMESTAMPTester)testCase.getObjectFromDatabase(); if (fromDatabase == null) { throw new TestErrorException("Value from database is null: " + e); } String errorMsg = ""; if (!tsToTS.equals(fromDatabase.getTsToTS())) { errorMsg += "The tsToTS should be: " + tsToTS + ", but was read as: " + fromDatabase.getTsToTS() + "\n"; } if(doesTimestampTZWork()) { if (!tsToTSTZ.equals(fromDatabase.getTsToTSTZ())) { errorMsg += "The tsToTSTZ should be: " + tsToTSTZ + ", but was read as: " + fromDatabase.getTsToTSTZ() + "\n"; } } if(doesTimestampLTZWork()) { if (!tsToTSLTZ.equals(fromDatabase.getTsToTSLTZ())) { errorMsg += "The tsToTSLTZ should be: " + tsToTSLTZ + ", but was read as: " + fromDatabase.getTsToTSLTZ() + "\n"; } } if (!utilDateToTS.equals(fromDatabase.getUtilDateToTS())) { errorMsg += "The utilDateToTS should be: " + utilDateToTS + ", but was read as: " + fromDatabase.getUtilDateToTS() + "\n"; } if(doesTimestampTZWork()) { if (!utilDateToTSTZ.equals(fromDatabase.getUtilDateToTSTZ())) { errorMsg += "The utilDateToTSTZ should be: " + utilDateToTSTZ + ", but was read as: " + fromDatabase.getUtilDateToTSTZ() + "\n"; } } if(doesTimestampLTZWork()) { if (!utilDateToTSLTZ.equals(fromDatabase.getUtilDateToTSLTZ())) { errorMsg += "The utilDateToTSLTZ should be: " + utilDateToTSLTZ + ", but was read as: " + fromDatabase.getUtilDateToTSLTZ() + "\n"; } } if (!dateToTS.equals(fromDatabase.getDateToTS())) { errorMsg += "The dateToTS should be: " + dateToTS + ", but was read as: " + fromDatabase.getDateToTS() + "\n"; } if (!dateToTSTZ.equals(fromDatabase.getDateToTSTZ())) { errorMsg += "The dateToTSTZ should be: " + dateToTSTZ + ", but was read as: " + fromDatabase.getDateToTSTZ() + "\n"; } if (!dateToTSLTZ.equals(fromDatabase.getDateToTSLTZ())) { errorMsg += "The dateToTSLTZ should be: " + dateToTSLTZ + ", but was read as: " + fromDatabase.getDateToTSLTZ() + "\n"; } if (!timeToTS.equals(fromDatabase.getTimeToTS())) { errorMsg += "The timeToTS should be: " + timeToTS + ", but was read as: " + fromDatabase.getTimeToTS() + "\n"; } if(doesTimestampTZWork()) { if (!timeToTSTZ.equals(fromDatabase.getTimeToTSTZ())) { errorMsg += "The timeToTSTZ should be: " + timeToTSTZ + ", but was read as: " + fromDatabase.getTimeToTSTZ() + "\n"; } } if(doesTimestampLTZWork()) { if (!timeToTSLTZ.equals(fromDatabase.getTimeToTSLTZ())) { errorMsg += "The timeToTSLTZ should be: " + timeToTSLTZ + ", but was read as: " + fromDatabase.getTimeToTSLTZ() + "\n"; } } String originalCal = TIMESTAMPHelper.printCalendar(calToTSLTZ); String dbCal = TIMESTAMPHelper.printCalendar(fromDatabase.getCalToTSLTZ()); //calToTSLTZ from database should have the sessionTimeZone if (sessionTimeZone!=null && !fromDatabase.getCalToTSLTZ().getTimeZone().getID().trim().equals(sessionTimeZone.trim())) { errorMsg += "The sessionTimeZone should be: " + sessionTimeZone + ", but was read as: " + fromDatabase.getCalToTSLTZ().getTimeZone().getID(); } //Calendar-to-TIMESTAMPLTZ does not work if calendar's time stamp is not inserted into the db. if (doesTestInsertCalendarTimeZone(testCase)) { // 0 if and only if the two calendars refer to the same time int compareCalToTSLTZ = calToTSLTZ.compareTo(fromDatabase.getCalToTSLTZ()); if(compareCalToTSLTZ != 0) { errorMsg += "calToTSLTZ.compareTo(fromDatabase.getCalToTSLTZ()) == " + compareCalToTSLTZ + "\n"; errorMsg += "\t original calToTSLTZ = " + TIMESTAMPHelper.printCalendar(calToTSLTZ) + "\n"; errorMsg += "\tfromDatabase.calToTSLTZ = " + TIMESTAMPHelper.printCalendar(fromDatabase.getCalToTSLTZ()) + "\n"; errorMsg += "\t original calToTSLTZ = " + calToTSLTZ + "\n"; errorMsg += "\tfromDatabase.calToTSLTZ = " + fromDatabase.getCalToTSLTZ() + "\n"; } } //Calendar-to-TIMESTAMPTZ does not work if calendar's time stamp is not inserted into the db. if (doesTestInsertCalendarTimeZone(testCase)) { originalCal = TIMESTAMPHelper.printCalendar(calToTSTZ); dbCal = TIMESTAMPHelper.printCalendar(fromDatabase.getCalToTSTZ()); // indicates whether the original and read back from the db calendars are equal boolean areEqual = true; if(this.isTimestampInGmt) { // 0 if and only if the two calendars refer to the same time int compareCalToTSTZ = calToTSTZ.compareTo(fromDatabase.getCalToTSTZ()); boolean timeZonesEqual = calToTSTZ.getTimeZone().equals(fromDatabase.getCalToTSTZ().getTimeZone()); if(compareCalToTSTZ != 0 || !timeZonesEqual) { areEqual = false; if(compareCalToTSTZ != 0) { errorMsg += "calToTSTZ.compareTo(fromDatabase.getCalToTSTZ()) == " + compareCalToTSTZ + "\n"; } else { errorMsg += "time zones are not equal: \n"; } } } else { if (!originalCal.equals(dbCal)) { areEqual = false; errorMsg += "!originalCal.equals(dbCal)\n"; } } if(!areEqual) { errorMsg += "\t original calToTSTZ = " + originalCal + "\n"; errorMsg += "\tfromDatabase.calToTSTZ = " + dbCal + "\n"; errorMsg += "\t original calToTSTZ = " + calToTSTZ + "\n"; errorMsg += "\tfromDatabase.calToTSTZ = " + fromDatabase.getCalToTSTZ() + "\n"; } } //DATE field does not store milliseconds. Need to compare the original tsToTS without milliseconds with //the one from the database. Timestamp objectTimeInCache = getTsToDate(); int originalTS = objectTimeInCache.getNanos(); //Some original tsToDate without milliseconds fell through the Calendar check and need to excluded from //the following check. // if (originalTS != 0) { // TsToDate always looses nanos, so do not throw an error if the ts match without the nanos. objectTimeInCache.setNanos(0); Timestamp objectTimeInDB = fromDatabase.getTsToDate(); if (objectTimeInCache.equals(objectTimeInDB)) { if (originalTS != objectTimeInDB.getNanos()) { // Nanos don't match, ok, as everything else has been checked, assume this was the reason for the error. if(errorMsg.length() == 0) { return; } } else if (!calToTSLTZ.equals(fromDatabase.getCalToTSLTZ())) { // This seems to be ok, as the timezones are not suppose to come back the same?? // So ok, as everything else has been checked, assume this was the reason for the error. if(errorMsg.length() == 0) { return; } } } else { objectTimeInCache.setNanos(originalTS); errorMsg += "The tsToDate should be: " + objectTimeInCache + " but were read back as: " + objectTimeInDB; } if(errorMsg.length() > 0) { throw new TestErrorException("\n"+errorMsg, e); } else { throw e; } } } private OracleConnection getConnection(DatabaseSession session) { Connection connection = ((AbstractSession)session).getAccessor().getConnection(); return (OracleConnection)session.getServerPlatform().unwrapConnection(connection); } }