Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for java.time #159

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/main/java/com/j256/ormlite/db/BaseDatabaseType.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,26 @@ public void appendColumnArg(String tableName, StringBuilder sb, FieldType fieldT
appendDateType(sb, fieldType, fieldWidth);
break;

case LOCAL_DATE:
appendLocalDateType(sb, fieldType, fieldWidth);
break;

case LOCAL_TIME:
appendLocalTimeType(sb, fieldType, fieldWidth);
break;

case LOCAL_DATE_TIME:
appendLocalDateTimeType(sb, fieldType, fieldWidth);
break;

case OFFSET_TIME:
appendOffsetTimeType(sb, fieldType, fieldWidth);
break;

case OFFSET_DATE_TIME:
appendOffsetDateTimeType(sb, fieldType, fieldWidth);
break;

case CHAR:
appendCharType(sb, fieldType, fieldWidth);
break;
Expand Down Expand Up @@ -208,6 +228,41 @@ protected void appendDateType(StringBuilder sb, FieldType fieldType, int fieldWi
sb.append("TIMESTAMP");
}

/**
* Output the SQL type for a Java LocalDate.
*/
protected void appendLocalDateType(StringBuilder sb, FieldType fieldType, int fieldWidth) {
sb.append("DATE");
}

/**
* Output the SQL type for a Java LocalTime.
*/
protected void appendLocalTimeType(StringBuilder sb, FieldType fieldType, int fieldWidth) {
sb.append("TIME");
}

/**
* Output the SQL type for a Java LocalDateTime.
*/
protected void appendLocalDateTimeType(StringBuilder sb, FieldType fieldType, int fieldWidth) {
sb.append("TIMESTAMP");
}

/**
* Output the SQL type for a Java OffsetTime.
*/
protected void appendOffsetTimeType(StringBuilder sb, FieldType fieldType, int fieldWidth) {
sb.append("TIME WITH TIME ZONE");
}

/**
* Output the SQL type for a Java OffsetDateTime or Instant.
*/
protected void appendOffsetDateTimeType(StringBuilder sb, FieldType fieldType, int fieldWidth) {
sb.append("TIMESTAMP WITH TIME ZONE");
}

/**
* Output the SQL type for a Java boolean.
*/
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/com/j256/ormlite/field/DataType.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
import com.j256.ormlite.field.types.StringType;
import com.j256.ormlite.field.types.TimeStampType;
import com.j256.ormlite.field.types.UuidType;
import com.j256.ormlite.field.types.LocalDateType;
import com.j256.ormlite.field.types.LocalTimeType;
import com.j256.ormlite.field.types.LocalDateTimeType;
import com.j256.ormlite.field.types.OffsetTimeType;
import com.j256.ormlite.field.types.OffsetDateTimeType;
import com.j256.ormlite.field.types.InstantType;
import com.j256.ormlite.field.types.LocalDateSqlType;
import com.j256.ormlite.field.types.LocalTimeSqlType;
import com.j256.ormlite.field.types.LocalDateTimeSqlType;
import com.j256.ormlite.field.types.OffsetTimeCompatType;

/**
* Data type enumeration that corresponds to a {@link DataPersister}.
Expand Down Expand Up @@ -249,6 +259,60 @@ public enum DataType {
* Marker for fields that are unknown.
*/
UNKNOWN(null),
/**
* Persists the {@link java.time.LocalDate} Java class.
*
*/
LOCAL_DATE(LocalDateType.getSingleton()),
/**
* Persists the {@link java.time.LocalTime} Java class.
*
*/
LOCAL_TIME(LocalTimeType.getSingleton()),
/**
* Persists the {@link java.time.LocalDateTime} Java class.
*
*/
LOCAL_DATE_TIME(LocalDateTimeType.getSingleton()),
/**
* Persists the {@link java.time.OffsetTime} Java class.
*
*/
OFFSET_TIME(OffsetTimeType.getSingleton()),
/**
* Persists the {@link java.time.OffsetDateTime} Java class.
*
*/
OFFSET_DATE_TIME(OffsetDateTimeType.getSingleton()),
/**
* Persists the {@link java.time.Instant} Java class.
*
*/
INSTANT(InstantType.getSingleton()),
/**
* Persists the {@link java.time.LocalDate} Java class. By default this will use
* {@link #LOCAL_DATE} so you will need to specify this using {@link DatabaseField#dataType()}.
*
*/
LOCAL_DATE_SQL(LocalDateSqlType.getSingleton()),
/**
* Persists the {@link java.time.LocalTime} Java class. By default this will use
* {@link #LOCAL_TIME} so you will need to specify this using {@link DatabaseField#dataType()}.
*
*/
LOCAL_TIME_SQL(LocalTimeSqlType.getSingleton()),
/**
* Persists the {@link java.time.LocalDateTime} Java class. By default this will use
* {@link #LOCAL_DATE_TIME} so you will need to specify this using {@link DatabaseField#dataType()}.
*
*/
LOCAL_DATE_TIME_SQL(LocalDateTimeSqlType.getSingleton()),
/**
* Persists the {@link java.time.OffsetTime} Java class. By default this will use
* {@link #OFFSET_TIME} so you will need to specify this using {@link DatabaseField#dataType()}.
*
*/
OFFSET_TIME_COMPAT(OffsetTimeCompatType.getSingleton()),
// end
;

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/j256/ormlite/field/SqlType.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public enum SqlType {
// for other types handled by custom persisters
OTHER,
UNKNOWN,
// java.time
LOCAL_DATE,
LOCAL_TIME,
LOCAL_DATE_TIME,
OFFSET_TIME,
OFFSET_DATE_TIME,
// end
;
}
35 changes: 35 additions & 0 deletions src/main/java/com/j256/ormlite/field/types/BaseLocalDateType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.j256.ormlite.field.types;

import com.j256.ormlite.field.SqlType;

/**
* Base class for all of the java.time class types.
*
* @author graynk
*/
public abstract class BaseLocalDateType extends BaseDataType {
private static final String specificationVersion = System.getProperty("java.specification.version");
private static final boolean javaTimeSupported = !(specificationVersion.equals("1.6") || specificationVersion.equals("1.7"));

protected BaseLocalDateType(SqlType sqlType, Class<?>[] classes) {
super(sqlType, classes);
}

protected BaseLocalDateType(SqlType sqlType) {
super(sqlType);
}

protected static boolean isJavaTimeSupported() {
return javaTimeSupported;
}

@Override
public boolean isValidForVersion() {
return true;
}

@Override
public boolean isArgumentHolderRequired() {
return true;
}
}
67 changes: 67 additions & 0 deletions src/main/java/com/j256/ormlite/field/types/InstantType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.j256.ormlite.field.types;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.misc.SqlExceptionUtil;
import com.j256.ormlite.support.DatabaseResults;

/**
* A custom persister that is able to store the java.time.Instant class in the database as Timestamp With Timezone object.
* This class does not have a SQL backup counter-part, the database should support JDBC 4.2 for it to be used.
* Instant is also not a part of JDBC specification, persister converts Instant to OffsetDateTime with timezone fixed at UTC
*
* @author graynk
*/
public class InstantType extends BaseLocalDateType {

private static final InstantType singleton = isJavaTimeSupported() ? new InstantType() : null;
public static InstantType getSingleton() { return singleton; }
private InstantType() { super(SqlType.OFFSET_DATE_TIME, new Class<?>[] { Instant.class }); }
protected InstantType(SqlType sqlType, Class<?>[] classes) { super(sqlType, classes); }

@Override
public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException {
try {
return OffsetDateTime.parse(defaultStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSS]x"));
} catch (NumberFormatException e) {
throw SqlExceptionUtil.create("Problems with field " + fieldType +
" parsing default Instant value: " + defaultStr, e);
}
}

@Override
public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException {
return results.getOffsetDateTime(columnPos);
}

@Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
OffsetDateTime value = (OffsetDateTime) sqlArg;
return value.toInstant();
}

@Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
Instant instant = (Instant) javaObject;
// ZoneOffset.UTC is evaluated at InstantType creation, fails on Java 6. Using ZoneId.of() instead
return OffsetDateTime.ofInstant(instant, ZoneId.of("UTC"));
}

@Override
public Object moveToNextValue(Object currentValue) {
Instant datetime = (Instant) currentValue;
return datetime.plusNanos(1);
}

@Override
public boolean isValidForField(Field field) {
return (field.getType() == Instant.class);
}
}
53 changes: 53 additions & 0 deletions src/main/java/com/j256/ormlite/field/types/LocalDateSqlType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.j256.ormlite.field.types;

import java.sql.Date;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.misc.SqlExceptionUtil;
import com.j256.ormlite.support.DatabaseResults;

/**
* A custom persister that is able to store the java.time.LocalDate class in the database as Date object.
* This class should be used only when database used does not support JDBC 4.2, since it converts java.time.LocalDate
* to java.sql.Date.
*
* @author graynk
*/
public class LocalDateSqlType extends LocalDateType {

private static LocalDateSqlType singleton = isJavaTimeSupported() ? new LocalDateSqlType() : null;
public static LocalDateSqlType getSingleton() { return singleton; }
private LocalDateSqlType() { super(SqlType.LOCAL_DATE); }
protected LocalDateSqlType(SqlType sqlType, Class<?>[] classes) { super(sqlType, classes); }

@Override
public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException {
try {
return Date.valueOf(LocalDate.parse(defaultStr, DateTimeFormatter.ISO_LOCAL_DATE));
} catch (NumberFormatException e) {
throw SqlExceptionUtil.create("Problems with field " + fieldType +
" parsing default LocalDate value: " + defaultStr, e);
}
}

@Override
public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException {
return results.getDate(columnPos);
}

@Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
LocalDate date = (LocalDate) javaObject;
return Date.valueOf(date);
}

@Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
Date date = (Date) sqlArg;
return date.toLocalDate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.j256.ormlite.field.types;

import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.misc.SqlExceptionUtil;
import com.j256.ormlite.support.DatabaseResults;

/**
* A custom persister that is able to store the java.time.LocalDateTime class in the database as Timestamp object.
* This class should be used only when database used does not support JDBC 4.2, since it converts java.time.LocalDateTime
* to java.sql.Timestamp.
*
* @author graynk
*/
public class LocalDateTimeSqlType extends LocalDateTimeType {

private static final LocalDateTimeSqlType singleton = isJavaTimeSupported() ? new LocalDateTimeSqlType() : null;
public static LocalDateTimeSqlType getSingleton() { return singleton; }
private LocalDateTimeSqlType() { super(SqlType.LOCAL_DATE_TIME); }
protected LocalDateTimeSqlType(SqlType sqlType, Class<?>[] classes) { super(sqlType, classes); }

@Override
public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException {
try {
return Timestamp.valueOf(LocalDateTime.parse(defaultStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSS]")));
Copy link
Collaborator

@Bo98 Bo98 Dec 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caused problems in the tests for me. A better approach here (and ultimately more user friendly) would be variable length second fractions.

An example of this is here: Bo98@00fed82, along with adjustments to the tests to make sure they are consistently truncated (H2 supports milliseconds but the tests were comparing it to microseconds on my machine).

} catch (NumberFormatException e) {
throw SqlExceptionUtil.create("Problems with field " + fieldType +
" parsing default LocalDateTime value: " + defaultStr, e);
}
}

@Override
public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException {
return results.getTimestamp(columnPos);
}

@Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
Timestamp value = (Timestamp) sqlArg;
return value.toLocalDateTime();
}

@Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
LocalDateTime datetime = (LocalDateTime) javaObject;
return Timestamp.valueOf(datetime);
}
}
Loading