diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
index 568b5227d..8ef3780b5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
@@ -612,6 +612,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
typeMap.put(Month.class, new ScalarTypeMonth());
typeMap.put(Year.class, new ScalarTypeYear());
typeMap.put(YearMonth.class, new ScalarTypeYearMonthDate());
+ typeMap.put(MonthDay.class, new ScalarTypeMonthDay());
typeMap.put(OffsetTime.class, new ScalarTypeOffsetTime());
typeMap.put(ZoneId.class, new ScalarTypeZoneId());
typeMap.put(ZoneOffset.class, new ScalarTypeZoneOffset());
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java
new file mode 100644
index 000000000..417f80bc2
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java
@@ -0,0 +1,132 @@
+package com.avaje.ebeaninternal.server.type;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.sql.Date;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.time.LocalDate;
+import java.time.MonthDay;
+
+/**
+ * ScalarType for MonthDay stored as a SQL Date using a fixed year.
+ *
+ * Note that the year used should be a leap year.
+ *
+ */
+public class ScalarTypeMonthDay extends ScalarTypeBase {
+
+ protected int year;
+
+ /**
+ * Construct with a year of 2000 (which is a leap year).
+ */
+ public ScalarTypeMonthDay() {
+ this(2000); // Year 2000 is a leap year
+ }
+
+ /**
+ * Construct with a base year. The year should be a leap year to
+ * allow the 29th Feb value.
+ */
+ public ScalarTypeMonthDay(int year) {
+ super(MonthDay.class, false, Types.DATE);
+ this.year = year;
+ }
+
+ private MonthDay convertFromDate(Date ts) {
+ LocalDate localDate = ts.toLocalDate();
+ return MonthDay.of(localDate.getMonthValue(), localDate.getDayOfMonth());
+ }
+
+ private Date convertToDate(MonthDay value) {
+ return Date.valueOf(LocalDate.of(2000, value.getMonthValue(), value.getDayOfMonth()));
+ }
+
+ @Override
+ public MonthDay read(DataReader dataReader) throws SQLException {
+ Date ts = dataReader.getDate();
+ return ts == null ? null : convertFromDate(ts);
+ }
+
+
+ @Override
+ public void bind(DataBind b, MonthDay value) throws SQLException {
+ if (value == null) {
+ b.setNull(Types.DATE);
+ } else {
+ b.setDate(convertToDate(value));
+ }
+ }
+
+ @Override
+ public Object toJdbcType(Object value) {
+ if (value instanceof Date) return value;
+ return convertToDate((MonthDay) value);
+ }
+
+ @Override
+ public MonthDay toBeanType(Object value) {
+ if (value instanceof MonthDay) return (MonthDay) value;
+ return convertFromDate((Date) value);
+ }
+
+ @Override
+ public String formatValue(MonthDay value) {
+ return value.toString();
+ }
+
+ @Override
+ public MonthDay parse(String value) {
+ return MonthDay.parse(value);
+ }
+
+ @Override
+ public boolean isDateTimeCapable() {
+ return false;
+ }
+
+ @Override
+ public MonthDay convertFromMillis(long dateTime) {
+ throw new RuntimeException("Not supported on this type");
+ }
+
+ @Override
+ public MonthDay readData(DataInput dataInput) throws IOException {
+ if (!dataInput.readBoolean()) {
+ return null;
+ } else {
+ int month = dataInput.readInt();
+ int day = dataInput.readInt();
+ return MonthDay.of(month, day);
+ }
+ }
+
+ @Override
+ public void writeData(DataOutput dataOutput, MonthDay value) throws IOException {
+ if (value == null) {
+ dataOutput.writeBoolean(false);
+ } else {
+ dataOutput.writeBoolean(true);
+ dataOutput.write(value.getMonthValue());
+ dataOutput.write(value.getDayOfMonth());
+ }
+ }
+
+ @Override
+ public MonthDay jsonRead(JsonParser ctx, JsonToken event) throws IOException {
+ return parse(ctx.getValueAsString());
+ }
+
+ @Override
+ public void jsonWrite(JsonGenerator ctx, String name, MonthDay value) throws IOException {
+ ctx.writeStringField(name, format(value));
+ }
+
+
+}
diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDayTest.java b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDayTest.java
new file mode 100644
index 000000000..8f69584e3
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDayTest.java
@@ -0,0 +1,75 @@
+package com.avaje.ebeaninternal.server.type;
+
+import org.junit.Test;
+
+import java.sql.Date;
+import java.time.LocalDate;
+import java.time.MonthDay;
+
+import static org.junit.Assert.*;
+
+public class ScalarTypeMonthDayTest {
+
+ ScalarTypeMonthDay type = new ScalarTypeMonthDay();
+
+ @Test
+ public void testToJdbcType() throws Exception {
+
+ MonthDay value = MonthDay.of(4, 29);
+ Date date = Date.valueOf(LocalDate.of(2000, 4, 29));
+
+ Object val1 = type.toJdbcType(value);
+ Object val2 = type.toJdbcType(date);
+
+ assertEquals(date, val1);
+ assertEquals(date, val2);
+ }
+
+ @Test
+ public void testToBeanType() throws Exception {
+
+ MonthDay value = MonthDay.of(4, 29);
+ Date date = Date.valueOf(LocalDate.of(2000, 4, 29));
+
+ MonthDay val1 = type.toBeanType(value);
+ MonthDay val2 = type.toBeanType(date);
+
+ assertEquals(value, val1);
+ assertEquals(value, val2);
+ }
+
+ @Test
+ public void testFormatParse() throws Exception {
+
+ MonthDay value = MonthDay.of(4, 29);
+ String val1 = type.formatValue(value);
+ MonthDay monthDay = type.parse(val1);
+
+ assertEquals("--04-29", val1);
+ assertEquals(value, monthDay);
+ }
+
+
+ @Test
+ public void testIsDateTimeCapable() throws Exception {
+ assertFalse(type.isDateTimeCapable());
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testConvertFromMillis() throws Exception {
+ type.convertFromMillis(1203);
+ }
+
+ @Test
+ public void testJson() throws Exception {
+
+ MonthDay value = MonthDay.of(4, 29);
+ JsonTester jsonTester = new JsonTester(type);
+ jsonTester.test(value);
+ }
+
+ @Test
+ public void testReadWriteData() throws Exception {
+
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/tests/model/types/SomePeriodBean.java b/src/test/java/com/avaje/tests/model/types/SomePeriodBean.java
index c93cd4f9c..398e41017 100644
--- a/src/test/java/com/avaje/tests/model/types/SomePeriodBean.java
+++ b/src/test/java/com/avaje/tests/model/types/SomePeriodBean.java
@@ -16,6 +16,8 @@ public class SomePeriodBean {
Period period;
+ MonthDay anniversary;
+
public Long getId() {
return id;
}
@@ -39,4 +41,12 @@ public class SomePeriodBean {
public void setPeriod(Period period) {
this.period = period;
}
+
+ public MonthDay getAnniversary() {
+ return anniversary;
+ }
+
+ public void setAnniversary(MonthDay anniversary) {
+ this.anniversary = anniversary;
+ }
}
diff --git a/src/test/java/com/avaje/tests/types/TestPeriodType.java b/src/test/java/com/avaje/tests/types/TestPeriodType.java
index c1838fcd5..4378f46f6 100644
--- a/src/test/java/com/avaje/tests/types/TestPeriodType.java
+++ b/src/test/java/com/avaje/tests/types/TestPeriodType.java
@@ -5,7 +5,11 @@ import com.avaje.ebean.Ebean;
import com.avaje.tests.model.types.SomePeriodBean;
import org.junit.Test;
+import java.sql.Date;
+import java.time.LocalDate;
+import java.time.MonthDay;
import java.time.Period;
+import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -19,10 +23,12 @@ public class TestPeriodType extends BaseTestCase {
SomePeriodBean bean = new SomePeriodBean();
bean.setPeriod(Period.of(3, 4, 5));
+ bean.setAnniversary(MonthDay.of(4, 29));
Ebean.save(bean);
SomePeriodBean bean1 = Ebean.find(SomePeriodBean.class, bean.getId());
assertEquals(bean.getPeriod(), bean1.getPeriod());
+ assertEquals(bean.getAnniversary(), bean1.getAnniversary());
// insert fetch null value
SomePeriodBean bean2 = new SomePeriodBean();
@@ -30,6 +36,37 @@ public class TestPeriodType extends BaseTestCase {
SomePeriodBean bean3 = Ebean.find(SomePeriodBean.class, bean2.getId());
assertNull(bean3.getPeriod());
+ assertNull(bean3.getAnniversary());
+
+ List anniversaryList = Ebean.find(SomePeriodBean.class)
+ .where()
+ .eq("anniversary", MonthDay.of(4, 29))
+ .eq("period_years", 3)
+ .eq("period_months", 4)
+ .findList();
+
+ assertEquals(1, anniversaryList.size());
+
+ // must use year 2000 for range predicates
+ // ... using 2001 here so not finding anything
+ anniversaryList = Ebean.find(SomePeriodBean.class)
+ .where()
+ .gt("anniversary", Date.valueOf(LocalDate.of(2001,4, 29)))
+ .findList();
+
+ assertEquals(0, anniversaryList.size());
+
+ // can use year 2000 for range predicates
+ // ... and can use LocalDate to bind
+ anniversaryList = Ebean.find(SomePeriodBean.class)
+ .where()
+ .gt("anniversary", LocalDate.of(2000,4, 22))
+ .findList();
+
+ assertEquals(1, anniversaryList.size());
+
+ Ebean.delete(bean);
+ Ebean.delete(bean2);
}
}