From 8cd2869f3f0c57c17aae5175017d840de0a94f6a Mon Sep 17 00:00:00 2001 From: Noemi Szemenyei Date: Thu, 25 Nov 2021 18:30:00 +1300 Subject: [PATCH] add faling test case for time properties --- .../server/type/DefaultTypeManager.java | 3 +- .../server/type/DatesAndTimesTest.java | 700 ++++++++++++++++++ .../server/type/DatesAndTimesWithDb2Test.java | 7 + .../type/DatesAndTimesWithJsonMillisTest.java | 11 + .../type/DatesAndTimesWithJsonNanosTest.java | 10 + .../type/DatesAndTimesWithMariaDbTest.java | 7 + .../type/DatesAndTimesWithMysqlTest.java | 10 + .../type/DatesAndTimesWithNanosTest.java | 10 + .../server/type/DatesAndTimesWithPgTest.java | 10 + .../type/DatesAndTimesWithSqlServerTest.java | 7 + .../java/org/tests/model/basic/MDateTime.java | 90 +++ 11 files changed, 863 insertions(+), 2 deletions(-) create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesTest.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithDb2Test.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonMillisTest.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonNanosTest.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMariaDbTest.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMysqlTest.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithNanosTest.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithPgTest.java create mode 100644 ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithSqlServerTest.java create mode 100644 ebean-test/src/test/java/org/tests/model/basic/MDateTime.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 5347a6433..78fad15c0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -629,8 +629,7 @@ public final class DefaultTypeManager implements TypeManager { */ private ScalarTypeEnum createEnumScalarTypeDbValue(Class> enumType, Method method, boolean integerType, int length, boolean withConstraint) { Map nameValueMap = new LinkedHashMap<>(); - Enum[] enumConstants = enumType.getEnumConstants(); - for (Enum enumConstant : enumConstants) { + for (Enum enumConstant : enumType.getEnumConstants()) { try { Object value = method.invoke(enumConstant); nameValueMap.put(enumConstant.name(), value.toString()); diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesTest.java new file mode 100644 index 000000000..f0354c2c7 --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesTest.java @@ -0,0 +1,700 @@ +package io.ebeaninternal.server.type; + +import static java.lang.String.format; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.List; +import java.util.TimeZone; + +import javax.sql.DataSource; + +import org.assertj.core.api.SoftAssertions; +import org.joda.time.DateTimeZone; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.tests.model.basic.MDateTime; + +import io.ebean.Database; +import io.ebean.DatabaseFactory; +import io.ebean.config.DatabaseConfig; +import io.ebean.core.type.ScalarType; +import io.ebean.plugin.ExpressionPath; +import io.ebean.plugin.Property; +import io.ebean.text.PathProperties; +import io.ebean.text.json.JsonWriteOptions; +import io.ebean.util.CamelCaseHelper; +import io.ebeaninternal.server.deploy.BeanProperty; + +@TestInstance(Lifecycle.PER_CLASS) +public class DatesAndTimesTest { + + + String platform="h2"; + + private Database db; + private TimeZone tz; + private DatabaseConfig config; + private SoftAssertions softly; + + private String json; + private String formatted; + private long millis; + + @BeforeEach + public void startTest() { + softly = new SoftAssertions(); + tz = TimeZone.getDefault(); + if (db == null) { + db = createServer("GMT", null, null); // test uses GMT database + } else { + restartServer(null, "GMT"); + } + json = null; + formatted = null; + millis = 0; + } + + @AfterEach + public void stopTest() { + setJavaTimeZone(tz); + softly.assertAll(); + } + + @AfterAll + public void shutdown() { + if (db != null) { + db.find(MDateTime.class).delete(); + db.shutdown(); + } + } + + private void restartServer(String javaTimeZone, String dbTimeZone) { + DataSource existingDs = db.dataSource(); + DataSource existingRoDs = db.readOnlyDataSource(); + db.shutdown(false, false); + if (javaTimeZone != null) { + setJavaTimeZone(TimeZone.getTimeZone(javaTimeZone)); + } + db = createServer(dbTimeZone, existingDs, existingRoDs); + } + + private void setJavaTimeZone(TimeZone newTz) { + TimeZone.setDefault(newTz); + DateTimeZone.setDefault(DateTimeZone.forTimeZone(newTz)); + org.h2.util.DateTimeUtils.resetCalendar(); + } + + private Database createServer(String dbTimeZone, DataSource existingDs, DataSource existingRoDs) { + + config = new DatabaseConfig(); + config.setName(platform); + config.loadFromProperties(); + config.setDdlGenerate(existingDs == null ); + config.setDdlRun(existingDs == null); + config.setReadOnlyDataSource(existingDs); + config.setDdlExtra(false); + config.setDefaultServer(false); + config.setRegister(false); + config.setChangeLogAsync(false); + config.addClass(MDateTime.class); + + config.setDumpMetricsOnShutdown(false); + config.setDataTimeZone(dbTimeZone); + config.setDataSource(existingDs); + reconfigure(config); + + return DatabaseFactory.create(config); + } + + protected void reconfigure(DatabaseConfig config) { + + } + + @Test + public void testLocalTime() { + if (config.isLocalTimeWithNanos()) { + LocalTime lt = LocalTime.of(5, 15, 15,123456789); + doTest("localTime", lt, String.valueOf(lt.toNanoOfDay())); + softly.assertThat(json).isEqualTo("{\"localTime\":\"05:15:15.123456789\"}"); + softly.assertThat(formatted).isEqualTo("05:15:15.123456789"); + return; + } + // localTimes are never converted, when read or written to database + doTest("localTime", LocalTime.of(5, 15, 15), "05:15:15"); + softly.assertThat(json).isEqualTo("{\"localTime\":\"05:15:15\"}"); + softly.assertThat(formatted).isEqualTo("05:15:15"); + + doTest("localTime", LocalTime.of(0, 0, 0), "00:00:00"); + doTest("localTime", LocalTime.of(23, 59, 59), "23:59:59"); + + // it does not matter in which timezone the server or java is! + restartServer("PST", "Europe/Berlin"); + doTest("localTime", LocalTime.of(5, 15, 15), "05:15:15"); + doTest("localTime", LocalTime.of(0, 0, 0), "00:00:00"); + doTest("localTime", LocalTime.of(23, 59, 59), "23:59:59"); + + restartServer("Europe/Berlin", "PST"); + doTest("localTime", LocalTime.of(5, 15, 15), "05:15:15"); + doTest("localTime", LocalTime.of(0, 0, 0), "00:00:00"); + doTest("localTime", LocalTime.of(23, 59, 59), "23:59:59"); + + } + + @Test + public void testJodaLocalTime() { + // localTimes are never converted, when read or written to database + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("05:15:15"), "05:15:15"); + softly.assertThat(json).isEqualTo("{\"jodaLocalTime\":\"05:15:15.000\"}"); + softly.assertThat(formatted).isEqualTo("05:15:15.000"); + + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("00:00:00"), "00:00:00"); + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("23:59:59"), "23:59:59"); + + // it does not matter in which timezone the server or java is! + restartServer("PST", "Europe/Berlin"); + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("05:15:15"), "05:15:15"); + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("00:00:00"), "00:00:00"); + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("23:59:59"), "23:59:59"); + + restartServer("Europe/Berlin", "PST"); + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("05:15:15"), "05:15:15"); + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("00:00:00"), "00:00:00"); + doTest("jodaLocalTime", org.joda.time.LocalTime.parse("23:59:59"), "23:59:59"); + + } + + @Test + public void testLocalDate() { + + // Test with DST and no DST date (in germany) + doTest("localDate", LocalDate.parse("2021-11-21"), "2021-11-21"); + softly.assertThat(formatted).isEqualTo("1637452800000"); + softly.assertThat(millis).isEqualTo(1637452800000L); // 00:00 in GMT + + if (config.getJsonDate() == io.ebean.config.JsonConfig.Date.ISO8601) { + softly.assertThat(json).isEqualTo("{\"localDate\":\"2021-11-21\"}"); + } else { + softly.assertThat(json).isEqualTo("{\"localDate\":1637452800000}"); // 21-nov 00:00 GMT + } + + doTest("localDate", LocalDate.parse("1970-01-01"), "1970-01-01"); + softly.assertThat(formatted).isEqualTo("0"); + softly.assertThat(millis).isEqualTo(0L); + + doTest("localDate", LocalDate.parse("1969-12-31"), "1969-12-31"); + softly.assertThat(formatted).isEqualTo("-86400000"); + softly.assertThat(millis).isEqualTo(-86400000L); + if (config.getJsonDate() == io.ebean.config.JsonConfig.Date.ISO8601) { + softly.assertThat(json).isEqualTo("{\"localDate\":\"1969-12-31\"}"); + } else { + softly.assertThat(json).isEqualTo("{\"localDate\":-86400000}"); + } + + doTest("localDate", LocalDate.parse("2021-08-21"), "2021-08-21"); + + restartServer("PST", "Europe/Berlin"); + doTest("localDate", LocalDate.parse("2021-11-21"), "2021-11-21"); + doTest("localDate", LocalDate.parse("2021-08-21"), "2021-08-21"); + + restartServer("Europe/Berlin", "PST"); + doTest("localDate", LocalDate.parse("2021-11-21"), "2021-11-21"); + doTest("localDate", LocalDate.parse("2021-08-21"), "2021-08-21"); + + } + + @Test + public void testJodaLocalDate() { + + // Test with DST and no DST date (in germany) + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("2021-11-21"), "2021-11-21"); + if (config.getJsonDate() == io.ebean.config.JsonConfig.Date.ISO8601) { + softly.assertThat(json).isEqualTo("{\"jodaLocalDate\":\"2021-11-21\"}"); + } else { + softly.assertThat(json).isEqualTo("{\"jodaLocalDate\":1637452800000}"); // GMT: 00:00 + } + + + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("2021-08-21"), "2021-08-21"); + + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("1970-01-01"), "1970-01-01"); + softly.assertThat(formatted).isEqualTo("0"); + softly.assertThat(millis).isEqualTo(0L); + + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("1969-12-31"), "1969-12-31"); + softly.assertThat(formatted).isEqualTo("-86400000"); + softly.assertThat(millis).isEqualTo(-86400000L); + + restartServer("PST", "Europe/Berlin"); + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("2021-11-21"), "2021-11-21"); + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("2021-08-21"), "2021-08-21"); + + restartServer("Europe/Berlin", "PST"); + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("2021-11-21"), "2021-11-21"); + doTest("jodaLocalDate", org.joda.time.LocalDate.parse("2021-08-21"), "2021-08-21"); + + } + + @Test + public void testCalendar() { + + restartServer("GMT", "GMT"); + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(2021, 8 - 1, 21, 5, 15, 15); // month 0-based! + + softly.assertThat(cal.toInstant()).isEqualTo(Instant.parse("2021-08-21T05:15:15Z")); + + doTest("propCalendar", cal, "2021-08-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"propCalendar\":\"2021-08-21T05:15:15.000Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"propCalendar\":1629522915000}"); + } else { + softly.assertThat(json).isEqualTo("{\"propCalendar\":1629522915.000000000}"); // 05:15 GMT + } + + cal.clear(); + cal.setTimeInMillis(-1); + doTest("propCalendar", cal, "1969-12-31 23:59:59.999"); + softly.assertThat(formatted).isEqualTo("-1"); + softly.assertThat(millis).isEqualTo(-1L); + + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"propCalendar\":\"1969-12-31T23:59:59.999Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"propCalendar\":-1}"); + } else { + softly.assertThat(json).isEqualTo("{\"propCalendar\":-0.001000000}"); + } + + // test in PST time zone + restartServer("PST", "GMT"); + cal = Calendar.getInstance(); + cal.setTimeInMillis(0); // clear + cal.set(2021, 8 - 1, 20, 22, 15, 15); // month 0-based! + + softly.assertThat(cal.toInstant()).isEqualTo(Instant.parse("2021-08-21T05:15:15Z")); + + doTest("propCalendar", cal, "2021-08-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"propCalendar\":\"2021-08-21T05:15:15.000Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"propCalendar\":1629522915000}"); + } else { + softly.assertThat(json).isEqualTo("{\"propCalendar\":1629522915.000000000}"); + } + } + + @Test + public void testInstant() { + + // Test with DST and no DST date (in germany) + doTest("propInstant", Instant.parse("2021-11-21T05:15:15Z"), "2021-11-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"propInstant\":\"2021-11-21T05:15:15Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"propInstant\":1637471715000}"); + } else { + softly.assertThat(json).isEqualTo("{\"propInstant\":1637471715.000000000}"); // 05:15 GMT + } + + doTest("propInstant", Instant.parse("1970-01-01T00:00:00Z"), "1970-01-01 00:00:00"); + softly.assertThat(formatted).isEqualTo("0"); + softly.assertThat(millis).isEqualTo(0L); + + doTest("propInstant", Instant.parse("2021-08-21T05:15:15Z"), "2021-08-21 05:15:15"); + + restartServer("PST", "GMT"); + + doTest("propInstant", Instant.parse("2021-11-21T05:15:15Z"), "2021-11-21 05:15:15"); + doTest("propInstant", Instant.parse("2021-08-21T05:15:15Z"), "2021-08-21 05:15:15"); + } + + @Test + public void testJodaDateTime() { + + // Test with DST and no DST date (in germany) + doTest("jodaDateTime", org.joda.time.DateTime.parse("2021-11-21T05:15:15Z"), "2021-11-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"jodaDateTime\":\"2021-11-21T05:15:15.000Z\"}"); + } else { + softly.assertThat(json).isEqualTo("{\"jodaDateTime\":1637471715000}"); + } + + doTest("jodaDateTime", org.joda.time.DateTime.parse("1970-01-01T00:00:00Z"), "1970-01-01 00:00:00"); + softly.assertThat(formatted).isEqualTo("0"); + softly.assertThat(millis).isEqualTo(0L); + + restartServer("PST", "GMT"); + + doTest("jodaDateTime", org.joda.time.DateTime.parse("2021-11-21T05:15:15Z"), "2021-11-21 05:15:15"); + doTest("jodaDateTime", org.joda.time.DateTime.parse("2021-08-21T05:15:15Z"), "2021-08-21 05:15:15"); + } + + @Test + public void testLocalDateTime() { + + // Test with DST and no DST date (in germany) + doTest("localDateTime", LocalDateTime.parse("2021-11-21T05:15:15"), "2021-11-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"localDateTime\":\"2021-11-21T05:15:15\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"localDateTime\":1637471715000}"); + } else { + softly.assertThat(json).isEqualTo("{\"localDateTime\":1637471715.000000000}"); + } + softly.assertThat(formatted).isEqualTo("2021-11-21T05:15:15"); // WHY is this not formatted in millis + softly.assertThat(millis).isEqualTo(1637471715000L); + + doTest("localDateTime", LocalDateTime.parse("1970-01-01T00:00:00"), "1970-01-01 00:00:00"); + softly.assertThat(formatted).isEqualTo("1970-01-01T00:00"); + softly.assertThat(millis).isEqualTo(0L); + + restartServer("PST", "Europe/Berlin"); + doTest("localDateTime", LocalDateTime.parse("2021-11-21T05:15:15"), "2021-11-21 05:15:15"); + doTest("localDateTime", LocalDateTime.parse("2021-08-21T05:15:15"), "2021-08-21 05:15:15"); + + restartServer("Europe/Berlin", "PST"); + doTest("localDateTime", LocalDateTime.parse("2021-11-21T05:15:15"), "2021-11-21 05:15:15"); + doTest("localDateTime", LocalDateTime.parse("2021-08-21T05:15:15"), "2021-08-21 05:15:15"); + + } + + @Test + public void testJodaLocalDateTime() { + + // Test with DST and no DST date (in germany) + doTest("jodaLocalDateTime", org.joda.time.LocalDateTime.parse("2021-11-21T05:15:15"), "2021-11-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"jodaLocalDateTime\":\"2021-11-21T05:15:15.000\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"jodaLocalDateTime\":1637471715000}"); // 05:15:15 GMT + } else { + softly.assertThat(json).isEqualTo("{\"jodaLocalDateTime\":1637471715.000000000}"); // 05:15:15 GMT + } + + doTest("jodaLocalDateTime", org.joda.time.LocalDateTime.parse("2021-08-21T05:15:15"), "2021-08-21 05:15:15"); + + restartServer("PST", "Europe/Berlin"); + doTest("jodaLocalDateTime", org.joda.time.LocalDateTime.parse("2021-11-21T05:15:15"), "2021-11-21 05:15:15"); + doTest("jodaLocalDateTime", org.joda.time.LocalDateTime.parse("2021-08-21T05:15:15"), "2021-08-21 05:15:15"); + + restartServer("Europe/Berlin", "PST"); + doTest("jodaLocalDateTime", org.joda.time.LocalDateTime.parse("2021-11-21T05:15:15"), "2021-11-21 05:15:15"); + doTest("jodaLocalDateTime", org.joda.time.LocalDateTime.parse("2021-08-21T05:15:15"), "2021-08-21 05:15:15"); + + } + + @Test + public void testJodaDateMidnight() { + + // Test with DST and no DST date (in germany) + doTest("jodaDateMidnight", org.joda.time.DateMidnight.parse("2021-11-21"), "2021-11-21"); + if (config.getJsonDate() == io.ebean.config.JsonConfig.Date.ISO8601) { + softly.assertThat(json).isEqualTo("{\"jodaDateMidnight\":\"2021-11-21\"}"); + } else { + softly.assertThat(json).isEqualTo("{\"jodaDateMidnight\":1637449200000}"); + } + + doTest("jodaDateMidnight", org.joda.time.DateMidnight.parse("2021-08-21"), "2021-08-21"); + + restartServer("PST", "Europe/Berlin"); + doTest("jodaDateMidnight", org.joda.time.DateMidnight.parse("2021-11-21"), "2021-11-21"); + doTest("jodaDateMidnight", org.joda.time.DateMidnight.parse("2021-08-21"), "2021-08-21"); + + restartServer("Europe/Berlin", "PST"); + doTest("jodaDateMidnight", org.joda.time.DateMidnight.parse("2021-11-21"), "2021-11-21"); + doTest("jodaDateMidnight", org.joda.time.DateMidnight.parse("2021-08-21"), "2021-08-21"); + } + + @Test + public void testYearMonth() { + + // Test with DST and no DST date (in germany) + doTest("propYearMonth", YearMonth.of(2020, 2), "2020-02-01"); + if (config.getJsonDate() == io.ebean.config.JsonConfig.Date.ISO8601) { + softly.assertThat(json).isEqualTo("{\"propYearMonth\":\"2020-02-01\"}"); + } else { + softly.assertThat(json).isEqualTo("{\"propYearMonth\":1580515200000}"); // 00:00 GMT + } + doTest("propYearMonth", YearMonth.of(2020, 3), "2020-03-01"); + doTest("propYearMonth", YearMonth.of(2020, 4), "2020-04-01"); + + } + @Test + public void testYear() { + + doTest("propYear", Year.of(2020), "2020"); + + } + + @Test + public void testMonthDay() { + + // Test with DST and no DST date (in germany) + doTest("propMonthDay", MonthDay.of(11, 21), "2000-11-21"); + softly.assertThat(json).isEqualTo("{\"propMonthDay\":\"--11-21\"}"); + doTest("propMonthDay", MonthDay.of(8, 21), "2000-08-21"); + doTest("propMonthDay", MonthDay.of(2, 29), "2000-02-29"); // leap year check + + restartServer("PST", "Europe/Berlin"); + doTest("propMonthDay", MonthDay.of(11, 21), "2000-11-21"); + doTest("propMonthDay", MonthDay.of(8, 21), "2000-08-21"); + doTest("propMonthDay", MonthDay.of(2, 29), "2000-02-29"); // leap year check + + restartServer("Europe/Berlin", "PST"); + doTest("propMonthDay", MonthDay.of(11, 21), "2000-11-21"); + doTest("propMonthDay", MonthDay.of(8, 21), "2000-08-21"); + doTest("propMonthDay", MonthDay.of(2, 29), "2000-02-29"); // leap year check + } + + @Test + public void testSqlDate() { + // localTimes are never converted, when read or written to database + doTest("sqlDate", new java.sql.Date(2021 - 1900, 11 - 1, 21), "2021-11-21"); + if (config.getJsonDate() == io.ebean.config.JsonConfig.Date.ISO8601) { + softly.assertThat(json).isEqualTo("{\"sqlDate\":\"2021-11-21\"}"); + } else { + softly.assertThat(json).isEqualTo("{\"sqlDate\":1637452800000}"); // 00:00 GMT + } + + doTest("sqlDate", new java.sql.Date(2021 - 1900, 8 - 1, 21), "2021-08-21"); + + // it does not matter in which timezone the server or java is! + restartServer("PST", "Europe/Berlin"); + doTest("sqlDate", new java.sql.Date(2021 - 1900, 11 - 1, 21), "2021-11-21"); + doTest("sqlDate", new java.sql.Date(2021 - 1900, 8 - 1, 21), "2021-08-21"); + + restartServer("Europe/Berlin", "PST"); + doTest("sqlDate", new java.sql.Date(2021 - 1900, 11 - 1, 21), "2021-11-21"); + doTest("sqlDate", new java.sql.Date(2021 - 1900, 8 - 1, 21), "2021-08-21"); + + } + + @Test + public void testSqlTime() { + + // Test with DST and no DST date (in germany) + doTest("sqlTime", new java.sql.Time(5, 15, 15), "05:15:15"); + softly.assertThat(json).isEqualTo("{\"sqlTime\":\"05:15:15\"}"); + + doTest("sqlTime", new java.sql.Time(0, 0, 0), "00:00:00"); + doTest("sqlTime", new java.sql.Time(23, 59, 59), "23:59:59"); + + restartServer("PST", "Europe/Berlin"); + doTest("sqlTime", new java.sql.Time(5, 15, 15), "05:15:15"); + doTest("sqlTime", new java.sql.Time(0, 0, 0), "00:00:00"); + doTest("sqlTime", new java.sql.Time(23, 59, 59), "23:59:59"); + + restartServer("Europe/Berlin", "PST"); + doTest("sqlTime", new java.sql.Time(5, 15, 15), "05:15:15"); + doTest("sqlTime", new java.sql.Time(0, 0, 0), "00:00:00"); + doTest("sqlTime", new java.sql.Time(23, 59, 59), "23:59:59"); + } + + @Test + public void testTimestamp() { + restartServer("PST", "PST"); // java & db in same TZ + doTest("propTimestamp", new Timestamp(2021 - 1900, 11 - 1, 21, 5, 15, 15, 0), "2021-11-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"propTimestamp\":\"2021-11-21T13:15:15Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"propTimestamp\":1637500515000}"); + } else { + softly.assertThat(json).isEqualTo("{\"propTimestamp\":1637500515.000000000}"); + } + + + restartServer("Europe/Berlin", "GMT"); // go to germany + doTest("propTimestamp", new Timestamp(2021 - 1900, 11 - 1, 21, 6, 15, 15, 0), "2021-11-21 05:15:15"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 8 - 1, 21, 7, 15, 15, 0), "2021-08-21 05:15:15"); // Check DST + + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 27, 23, 0, 0, 0), "2021-03-27 22:00:00"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 28, 0, 0, 0, 0), "2021-03-27 23:00:00"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 28, 1, 0, 0, 0), "2021-03-28 00:00:00"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 28, 2, 0, 0, 0), "2021-03-28 01:00:00"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 28, 3, 0, 1, 0), "2021-03-28 01:00:01"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 28, 4, 0, 0, 0), "2021-03-28 02:00:00"); + + restartServer("GMT", "Europe/Berlin"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 11 - 1, 21, 4, 15, 15, 0), "2021-11-21 05:15:15"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 8 - 1, 21, 3, 15, 15, 0), "2021-08-21 05:15:15"); // Check DST + + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 27, 1, 0, 0, 0), "2021-03-27 02:00:00"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 27, 23, 0, 0, 0), "2021-03-28 00:00:00"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 28, 0, 0, 0, 0), "2021-03-28 01:00:00"); + doTest("propTimestamp", new Timestamp(2021 - 1900, 3 - 1, 28, 1, 0, 0, 0), "2021-03-28 03:00:00"); + } + + @Test + public void testUtilDate() { + restartServer("PST", "PST"); // java & db in same TZ + doTest("utilDate", new java.util.Date(2021 - 1900, 11 - 1, 21, 5, 15, 15), "2021-11-21 05:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"utilDate\":\"2021-11-21T13:15:15.000Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"utilDate\":1637500515000}"); + } else { + softly.assertThat(json).isEqualTo("{\"utilDate\":1637500515.000000000}"); + } + + + restartServer("Europe/Berlin", "GMT"); // go to germany + doTest("utilDate", new java.util.Date(2021 - 1900, 11 - 1, 21, 6, 15, 15), "2021-11-21 05:15:15"); + doTest("utilDate", new java.util.Date(2021 - 1900, 8 - 1, 21, 7, 15, 15), "2021-08-21 05:15:15"); + + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 27, 23, 0, 0), "2021-03-27 22:00:00"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 28, 0, 0, 0), "2021-03-27 23:00:00"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 28, 1, 0, 0), "2021-03-28 00:00:00"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 28, 2, 0, 0), "2021-03-28 01:00:00"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 28, 3, 0, 1), "2021-03-28 01:00:01"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 28, 4, 0, 0), "2021-03-28 02:00:00"); + + restartServer("GMT", "Europe/Berlin"); + doTest("utilDate", new java.util.Date(2021 - 1900, 11 - 1, 21, 4, 15, 15), "2021-11-21 05:15:15"); + doTest("utilDate", new java.util.Date(2021 - 1900, 8 - 1, 21, 3, 15, 15), "2021-08-21 05:15:15"); + + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 27, 1, 0, 0), "2021-03-27 02:00:00"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 27, 23, 0, 0), "2021-03-28 00:00:00"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 28, 0, 0, 0), "2021-03-28 01:00:00"); + doTest("utilDate", new java.util.Date(2021 - 1900, 3 - 1, 28, 1, 0, 0), "2021-03-28 03:00:00"); + } + + @Test + public void testOffsetDateTime() { + + restartServer("PST", "PST"); // be in the same TZ + doTest("offsetDateTime", OffsetDateTime.parse("2021-11-20T21:15:15-08:00"), "2021-11-20 21:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"offsetDateTime\":\"2021-11-21T05:15:15Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"offsetDateTime\":1637471715000}"); + } else { + softly.assertThat(json).isEqualTo("{\"offsetDateTime\":1637471715.000000000}"); + } + + restartServer("PST", "GMT"); // pass PST offsetDateTime to GMT DB + doTest("offsetDateTime", OffsetDateTime.parse("2021-11-20T21:15:15-08:00"), "2021-11-21 05:15:15"); + + } + + @Test + public void testZonedDateTime() { + + restartServer("PST", "PST"); // be in the same TZ + doTest("zonedDateTime", ZonedDateTime.parse("2021-11-20T21:15:15-08:00"), "2021-11-20 21:15:15"); + if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.ISO8601) { + softly.assertThat(json).isEqualTo("{\"zonedDateTime\":\"2021-11-21T05:15:15Z\"}"); + } else if (config.getJsonDateTime() == io.ebean.config.JsonConfig.DateTime.MILLIS) { + softly.assertThat(json).isEqualTo("{\"zonedDateTime\":1637471715000}"); // 05:15 GMT + } else { + softly.assertThat(json).isEqualTo("{\"zonedDateTime\":1637471715.000000000}"); + } + + restartServer("PST", "GMT"); // pass PST offsetDateTime to GMT DB + doTest("zonedDateTime", ZonedDateTime.parse("2021-11-20T21:15:15-08:00"), "2021-11-21 05:15:15"); + + } +// offsetDateTime : OffsetDateTime +// zonedDateTime : ZonedDateTime + private String simpleClassNameOf(StackTraceElement testStackTraceElement) { + String className = testStackTraceElement.getClassName(); + return className.substring(className.lastIndexOf('.') + 1); + } + + private > void doTest(String property, T javaValue, String sqlValue) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + String testClassName = simpleClassNameOf(stackTrace[2]); + String testName = stackTrace[2].getMethodName(); + int lineNumber = stackTrace[2].getLineNumber(); + String testLoc = format("at %s.%s(%s.java:%s)", testClassName, testName, testClassName, lineNumber); + + db.find(MDateTime.class).delete(); // clear database + String sqlColumn = CamelCaseHelper.toUnderscoreFromCamel(property); + // insert with raw sql + db.sqlUpdate("insert into mdate_time (id, " + sqlColumn + ") values (1, '" + sqlValue + "')").execute(); + + // check findSingleAttributeList + List list = db.find(MDateTime.class).select(property).findSingleAttributeList(); + softly.assertThat(list).as("find " + testLoc).hasSize(1); + T attr = list.get(0); + assertTimeEquals("find attribute " + testLoc, attr, javaValue); + + // check find model + MDateTime model = db.find(MDateTime.class).where().eq(property, javaValue).findOne(); + Property beanProp = db.pluginApi().beanType(MDateTime.class).property(property); + softly.assertThat(model).as("find model " + testLoc).isNotNull(); + if (model != null) { + @SuppressWarnings("unchecked") + T beanValue = (T) beanProp.value(model); + assertTimeEquals("read from model " + testLoc, beanValue, javaValue); + } + // insert with "save" + model = new MDateTime(); + model.setId(2); + ((ExpressionPath) beanProp).pathSet(model, javaValue); + db.save(model); + + // check findCount + int count = db.find(MDateTime.class).where().eq(property, javaValue).findCount(); + softly.assertThat(count).as("find count " + testLoc).isEqualTo(2); + + JsonWriteOptions opts = new JsonWriteOptions(); + opts.setPathProperties(PathProperties.parse(property)); + // check json roundtrip + json = db.json().toJson(model, opts); + model = db.json().toBean(MDateTime.class, json); + T beanValue = (T) beanProp.value(model); + assertTimeEquals("json roundtrip " + testLoc, beanValue, javaValue); + + ScalarType st = (ScalarType) ((BeanProperty) beanProp).scalarType(); + formatted = st.formatValue(javaValue); + assertTimeEquals("parse/format symmetry " + testLoc, st.parse(formatted), javaValue); + if (st instanceof ScalarTypeBaseDate) { + ScalarTypeBaseDate st2 = (ScalarTypeBaseDate) st; + Date date = st2.convertToDate(javaValue); + assertTimeEquals("date convert symmetry " + testLoc, st2.convertFromDate(date), javaValue); + millis = st2.convertToMillis(javaValue); + assertTimeEquals("millis convert symmetry " + testLoc, st2.convertFromMillis(millis), javaValue); + } + if (st instanceof ScalarTypeBaseDateTime) { + ScalarTypeBaseDateTime st2 = (ScalarTypeBaseDateTime) st; + Timestamp ts = st2.convertToTimestamp(javaValue); + assertTimeEquals("timestamp convert symmetry " + testLoc, st2.convertFromTimestamp(ts), javaValue); + millis = st2.convertToMillis(javaValue); + assertTimeEquals("millis convert symmetry " + testLoc, st2.convertFromMillis(millis), javaValue); + assertTimeEquals("instant convert symmetry " + testLoc, st2.convertFromInstant(Instant.ofEpochMilli(millis)), + javaValue); + } + } + + private > void assertTimeEquals(String msg, T value, T expected) { + if (value instanceof java.sql.Date) { + // SQL Dates may not be aligned at 00:00 - so compare toString representation + softly.assertThat(value.toString()).as(msg).isEqualTo(expected.toString()); + } else if (value instanceof OffsetDateTime) { + softly.assertThat((OffsetDateTime) value).as(msg).isAtSameInstantAs((OffsetDateTime) expected); + } else if (value instanceof ZonedDateTime) { + softly.assertThat(((ZonedDateTime) value).toInstant()).as(msg).isEqualTo(((ZonedDateTime) expected).toInstant()); + } else { + softly.assertThat(value).as(msg).isEqualByComparingTo(expected); + } + } + +} diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithDb2Test.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithDb2Test.java new file mode 100644 index 000000000..91ebf91c1 --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithDb2Test.java @@ -0,0 +1,7 @@ +package io.ebeaninternal.server.type; + +public class DatesAndTimesWithDb2Test extends DatesAndTimesTest { + public DatesAndTimesWithDb2Test() { + platform = "db2"; + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonMillisTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonMillisTest.java new file mode 100644 index 000000000..0c3abd7cd --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonMillisTest.java @@ -0,0 +1,11 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.DatabaseConfig; + +public class DatesAndTimesWithJsonMillisTest extends DatesAndTimesTest { + @Override + protected void reconfigure(DatabaseConfig config) { + config.setJsonDate(io.ebean.config.JsonConfig.Date.MILLIS); + config.setJsonDateTime(io.ebean.config.JsonConfig.DateTime.MILLIS); + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonNanosTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonNanosTest.java new file mode 100644 index 000000000..1bc262309 --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithJsonNanosTest.java @@ -0,0 +1,10 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.DatabaseConfig; + +public class DatesAndTimesWithJsonNanosTest extends DatesAndTimesTest { + @Override + protected void reconfigure(DatabaseConfig config) { + config.setJsonDateTime(io.ebean.config.JsonConfig.DateTime.NANOS); + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMariaDbTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMariaDbTest.java new file mode 100644 index 000000000..ec3463588 --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMariaDbTest.java @@ -0,0 +1,7 @@ +package io.ebeaninternal.server.type; + +public class DatesAndTimesWithMariaDbTest extends DatesAndTimesTest { + public DatesAndTimesWithMariaDbTest() { + platform = "mariadb"; + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMysqlTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMysqlTest.java new file mode 100644 index 000000000..45d761216 --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithMysqlTest.java @@ -0,0 +1,10 @@ +package io.ebeaninternal.server.type; + +import org.junit.jupiter.api.Disabled; + +@Disabled +public class DatesAndTimesWithMysqlTest extends DatesAndTimesTest { + public DatesAndTimesWithMysqlTest() { + platform = "mysql"; + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithNanosTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithNanosTest.java new file mode 100644 index 000000000..795f8dc69 --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithNanosTest.java @@ -0,0 +1,10 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.DatabaseConfig; + +public class DatesAndTimesWithNanosTest extends DatesAndTimesTest { + @Override + protected void reconfigure(DatabaseConfig config) { + config.setLocalTimeWithNanos(true); + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithPgTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithPgTest.java new file mode 100644 index 000000000..07272e36a --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithPgTest.java @@ -0,0 +1,10 @@ +package io.ebeaninternal.server.type; + +import org.junit.jupiter.api.Disabled; + +@Disabled +public class DatesAndTimesWithPgTest extends DatesAndTimesTest { + public DatesAndTimesWithPgTest() { + platform = "pg"; + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithSqlServerTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithSqlServerTest.java new file mode 100644 index 000000000..7a3c41bfb --- /dev/null +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/DatesAndTimesWithSqlServerTest.java @@ -0,0 +1,7 @@ +package io.ebeaninternal.server.type; + +public class DatesAndTimesWithSqlServerTest extends DatesAndTimesTest { + public DatesAndTimesWithSqlServerTest() { + platform = "sqlserver"; + } +} \ No newline at end of file diff --git a/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java b/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java new file mode 100644 index 000000000..15aa3cec5 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java @@ -0,0 +1,90 @@ +package org.tests.model.basic; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZonedDateTime; +import java.util.Calendar; + +import javax.annotation.Nullable; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.joda.time.DateTime; + +@Entity +public class MDateTime { + + @Id + private Integer id; + + @Nullable + private LocalTime localTime; + + @Nullable + private LocalDateTime localDateTime; + + @Nullable + private LocalDate localDate; + + @Nullable + private OffsetDateTime offsetDateTime; + + @Nullable + private ZonedDateTime zonedDateTime; + + @Nullable + private YearMonth propYearMonth; + + @Nullable + private MonthDay propMonthDay; + + @Nullable + private Year propYear; + + @Nullable + private Instant propInstant; + + @Nullable + private Calendar propCalendar; + + @Nullable + private Timestamp propTimestamp; + + @Nullable + private java.sql.Date sqlDate; + + @Nullable + private java.sql.Time sqlTime; + + @Nullable + private java.util.Date utilDate; + + @Nullable + private org.joda.time.DateTime jodaDateTime; + + @Nullable + private org.joda.time.LocalDateTime jodaLocalDateTime; + + @Nullable + private org.joda.time.LocalDate jodaLocalDate; + + @Nullable + private org.joda.time.LocalTime jodaLocalTime; + + @Nullable + private org.joda.time.DateMidnight jodaDateMidnight; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } +}