JadiraOrg/jadira

Incorrect offset in DB for java.time.LocalDateTime

Closed this issue · 5 comments

I used JPA2.1 converters for my LocalDateTime fields to convert them to java.sql.Timestamp before save. All working fine until I decided to use hibernate-envers which is not support JPA2.1 converters. Then I switched to hibernate @Type annotation using jadira-usertypes extension.

First it seems working fine but then I notice then all my localdatetimes stored in DB with offset (+3h). When it retrieved it of course converted back (-3) and all seems to working fine. But it is not. It is wrong because LocalDateTime is designed to represent not instant in time but some local datetime for current place (whichever place you located at the moment). So when it saved to DB it must not use any timezone information. It must just be saved AS IS.

For example if I read my DB from another PHP application it must apply (-3) offset to all dates. Or if my database already filled with some data - all local dates will be wrong when read by this jadira mapper.

My DB isq Mysql with date type is DATETIME and the field represent exactly what LocalDateTime was designed for - start of working day - it is timezone agnostic. If Bob starts working at 8:00 it starts working at 8:00 in place where it working even if I observe he's working graphic from another timezone.

This bug also was found on this stackoverflow (not mine): http://stackoverflow.com/questions/31826867/how-to-setup-jadira-persistentlocaldatetime-with-java-time-localdatetime

Setting

jadira.usertype.javaZone=Z
jadira.usertype.databaseZone=Z

or

jadira.usertype.javaZone=jvm
jadira.usertype.databaseZone=jvm

also doesn't help.
(It actually helps in my DEV enveronment with H2 databse, but doesn't help in my production environment with MySQL databse).

In my project I created custom implementation of PersistentLocalDateTime and TimestampColumnLocalDateTimeMapper for it.

public class PersistentLocalDateTime
        extends AbstractVersionableUserType<LocalDateTime, Timestamp, TimestampColumnLocalDateTimeMapper>
        implements ParameterizedType, IntegratorConfiguredType {

    private static final long serialVersionUID = -2195437660203845282L;

    @Override
    public int compare(Object o1, Object o2) {
        return ((LocalDateTime) o1).compareTo((LocalDateTime) o2);
    }

    @Override
    public void applyConfiguration(SessionFactory sessionFactory) {

        super.applyConfiguration(sessionFactory);

        TimestampColumnLocalDateTimeMapper columnMapper = getColumnMapper();
        columnMapper.setDatabaseZone(null);
        //columnMapper.setJavaZone(null);
    }
}
public class TimestampColumnLocalDateTimeMapper extends AbstractTimestampThreeTenColumnMapper<LocalDateTime> implements DatabaseZoneConfigured<ZoneOffset> {

    private static final long serialVersionUID = -2085262909771406619L;

    public static final DateTimeFormatter LOCAL_DATETIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd HH:mm:ss").appendOptional(new DateTimeFormatterBuilder().appendLiteral('.').appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, false).toFormatter()).toFormatter();

    public TimestampColumnLocalDateTimeMapper() {
        super();
    }

    public TimestampColumnLocalDateTimeMapper(ZoneOffset databaseZone) {
        super(databaseZone);
    }

    @Override
    public LocalDateTime fromNonNullString(String s) {
        return LocalDateTime.parse(s);
    }

    @Override
    public LocalDateTime fromNonNullValue(Timestamp value) {
        return value.toLocalDateTime();
    }

    @Override
    public String toNonNullString(LocalDateTime value) {
        return value.toString();
    }

    @Override
    public Timestamp toNonNullValue(LocalDateTime value) {
        return Timestamp.valueOf(value);
    }

    @Override
    public ZoneOffset parseZone(String zoneString) {
        return ZoneOffset.of(zoneString);
    }
}

This mapper works fine and correctly saves LocalDateTime to DB in both environments (DEV with H2 databse and PROD with MySQL).
I don't know if this correct approach and also some code may be redundant (overriding some methods, unused static field) but it works.

With MySQL it is recommended to pass the connection parameters "zeroDateTimeBehavior=convertToNull&useLegacyDatetimeCode=false". Can you confirm you are using these?

I am using zeroDateTimeBehavior=convertToNull but not using useLegacyDatetimeCode=false. May it help? I will try later on monday.

Yes, useLegacyDatetimeCode=false helps me. With this jdbc-connection setting org.jadira.usertype.dateandtime.threeten.PersistentLocalDateTime starts work as intended.
Thanks for help.