/* * Copyright 2014 - 2017 Blazebit. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.blazebit.persistence.testsuite; import static org.junit.Assert.assertEquals; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.List; import java.util.TimeZone; import javax.persistence.EntityManager; import javax.persistence.Tuple; import com.blazebit.persistence.testsuite.tx.TxVoidWork; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import com.blazebit.persistence.CriteriaBuilder; import com.blazebit.persistence.testsuite.base.category.NoMySQL; import com.blazebit.persistence.testsuite.entity.Document; import com.blazebit.persistence.testsuite.entity.Person; import com.blazebit.persistence.testsuite.entity.Version; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; /** * * @author Christian Beikov * @author Moritz Becker * @since 1.1.0 */ @RunWith(Parameterized.class) public class DateExtractTest extends AbstractCoreTest { private final TimeZone dbmsTimeZone = TimeZone.getDefault(); private Calendar c1; private Calendar c2; private TimeZone producerTimeZone; private TimeZone clientTimeZone; public DateExtractTest(TimeZone producerTimeZone, TimeZone clientTimeZone) { this.producerTimeZone = producerTimeZone; this.clientTimeZone = clientTimeZone; } @Parameterized.Parameters public static Collection<?> producerConsumerTimezones() { return Arrays.asList( new Object[]{ TimeZone.getTimeZone("GMT+00:00"), TimeZone.getTimeZone("GMT+00:00") }, new Object[]{ TimeZone.getTimeZone("GMT+00:00"), TimeZone.getTimeZone("GMT+01:00") }, new Object[]{ TimeZone.getTimeZone("GMT+01:00"), TimeZone.getTimeZone("GMT+01:00") }, new Object[]{ TimeZone.getTimeZone("GMT+01:00"), TimeZone.getTimeZone("GMT+00:00") }, new Object[]{ TimeZone.getTimeZone("GMT+12:00"), TimeZone.getTimeZone("GMT+06:00") }, new Object[]{ TimeZone.getTimeZone("GMT-12:00"), TimeZone.getTimeZone("GMT-06:00") }, new Object[]{ TimeZone.getTimeZone("GMT+06:00"), TimeZone.getTimeZone("GMT+12:00") }, new Object[]{ TimeZone.getTimeZone("GMT-06:00"), TimeZone.getTimeZone("GMT-12:00") }, new Object[]{ TimeZone.getTimeZone("GMT-12:00"), TimeZone.getTimeZone("GMT+06:00") }, new Object[]{ TimeZone.getTimeZone("GMT+12:00"), TimeZone.getTimeZone("GMT-06:00") }, new Object[]{ TimeZone.getTimeZone("GMT-06:00"), TimeZone.getTimeZone("GMT+12:00") }, new Object[]{ TimeZone.getTimeZone("GMT+06:00"), TimeZone.getTimeZone("GMT-12:00") } ); } // Doing this for every timezone @Before public void setUp() { // Set the producer timezone TimeZone.setDefault(producerTimeZone); resetTimeZoneCaches(); c1 = Calendar.getInstance(); c1.set(2000, 0, 1, 0, 0, 0); c1.set(Calendar.MILLISECOND, 213); c2 = Calendar.getInstance(); c2.set(2000, 0, 1, 1, 1, 1); c2.set(Calendar.MILLISECOND, 412); cleanDatabase(); transactional(new TxVoidWork() { @Override public void work(EntityManager em) { Person p = new Person("Pers1"); p.setAge(20L); em.persist(p); Version v1 = new Version(); em.persist(v1); Document doc1 = new Document("Doc1", p, v1); doc1.setCreationDate(c1); doc1.setLastModified(c2.getTime()); em.persist(doc1); } }); } @After public void after() { TimeZone.setDefault(dbmsTimeZone); resetTimeZoneCaches(); } private static void resetTimeZoneCaches() { // The H2 JDBC driver is not able to handle timezone changes because of an internal cache try { Class.forName("org.h2.util.DateTimeUtils").getMethod("resetCalendar").invoke(null); } catch (Exception e) { // Ignore any exceptions. If it is H2 it will succeed, otherwise will fail on class lookup already } // EclipseLink caches the timezone so we have to purge that cache try { Class<?> helperClass = Class.forName("org.eclipse.persistence.internal.helper.Helper"); Field f = helperClass.getDeclaredField("defaultTimeZone"); f.setAccessible(true); f.set(null, TimeZone.getDefault()); f = helperClass.getDeclaredField("calendarCache"); f.setAccessible(true); f.set(null, helperClass.getMethod("initCalendarCache").invoke(null)); } catch (Exception e) { // Ignore any exceptions. If it is EclipseLink it will succeed, otherwise will fail on class lookup already } } // NOTE: MySQL is strange again https://bugs.mysql.com/bug.php?id=31990 @Test @Category({ NoMySQL.class }) public void testDateExtract() { // Set the client timezone TimeZone.setDefault(clientTimeZone); resetTimeZoneCaches(); CriteriaBuilder<Tuple> criteria = cbf.create(em, Tuple.class) .from(Document.class, "doc") .select("FUNCTION('YEAR', creationDate)") .select("FUNCTION('MONTH', creationDate)") .select("FUNCTION('DAY', creationDate)") .select("FUNCTION('HOUR', creationDate)") .select("FUNCTION('MINUTE', creationDate)") .select("FUNCTION('SECOND', creationDate)") .select("FUNCTION('EPOCH', creationDate)") .select("FUNCTION('YEAR', lastModified)") .select("FUNCTION('MONTH', lastModified)") .select("FUNCTION('DAY', lastModified)") .select("FUNCTION('HOUR', lastModified)") .select("FUNCTION('MINUTE', lastModified)") .select("FUNCTION('SECOND', lastModified)") .select("FUNCTION('EPOCH', lastModified)") ; List<Tuple> list = criteria.getResultList(); assertEquals(1, list.size()); Tuple actual = list.get(0); int offsetInMillis1 = producerTimeZone.getOffset(c1.getTimeInMillis()); int offsetInMillis2 = producerTimeZone.getOffset(c2.getTimeInMillis()); assertEquals(c1.get(Calendar.YEAR), actual.get(0)); assertEquals(c1.get(Calendar.MONTH) + 1, actual.get(1)); assertEquals(c1.get(Calendar.DAY_OF_MONTH), actual.get(2)); assertEquals(c1.get(Calendar.HOUR_OF_DAY), (int) actual.get(3)); assertEquals(c1.get(Calendar.MINUTE), (int) actual.get(4)); assertEquals(c1.get(Calendar.SECOND), (int) actual.get(5)); // But there is an offset in the time in millis as that is the epoch assertEquals((int) (c1.getTimeInMillis() / 1000L), (int) actual.get(6) - (offsetInMillis1 / 1000L)); assertEquals(c2.get(Calendar.YEAR), actual.get(7)); assertEquals(c2.get(Calendar.MONTH) + 1, actual.get(8)); assertEquals(c2.get(Calendar.DAY_OF_MONTH), actual.get(9)); assertEquals(c2.get(Calendar.HOUR_OF_DAY), (int) actual.get(10)); assertEquals(c2.get(Calendar.MINUTE), (int) actual.get(11)); assertEquals(c2.get(Calendar.SECOND), (int) actual.get(12)); assertEquals((int) (c2.getTimeInMillis() / 1000L), (int) actual.get(13) - (offsetInMillis2 / 1000L)); } }