Menu

#68 PersistentLocalDate: something goes wrong around centuries

open
nobody
5
2014-08-19
2009-03-31
No

There is a very peculiar behaviour for PersistentLocalDate around the centuries. At those times, it seems to take the time zone into account, whereas LocalDate is supposed to ignore the time zone.

My problem appeared in the following situation:
I have a file containing dates, some of which are invalid. When they are invalid, they are replaced by 1900-01-01. When reading the file and saving the dates, everything seems ok.
Yet, when retrieving the dates from the database, I get 1899-12-31 instead, for the invalid dates.
(PersistentLocalDate uses new LocalDate(...) instead of LocalDate.fromDateFields(...) to convert the java.sql.Date into a LocalDate)

The following piece of code reproduces the issue without a database:
public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
testDates(0, 0, 1);
System.out.println("");
testDates(1, 0, 1);
System.out.println("");
testDates(-100, 0, 1);
}

private static void testDates(int yearFrom1900, int monthFromJanuary, int day) {
    Date fromDataBase = new Date(yearFrom1900, monthFromJanuary, day);

    LocalDate fromFile = new LocalDate(1900 + yearFrom1900, monthFromJanuary+1, day);
    LocalDate convertedUsingConstructor = new LocalDate(fromDataBase);
    LocalDate convertedUsingStatic = LocalDate.fromDateFields(fromDataBase);

    System.out.println("fromFile = " + fromFile);
    System.out.println("convertedUsingLocalDate = " + convertedUsingConstructor);
    System.out.println("convertedUsingStatic = " + convertedUsingStatic);
}

Discussion

  • Frédéric Donckels

    The output of the program is:
    fromFile = 1900-01-01
    convertedUsingLocalDate = 1899-12-31
    convertedUsingStatic = 1900-01-01

    fromFile = 1901-01-01
    convertedUsingLocalDate = 1901-01-01
    convertedUsingStatic = 1901-01-01

    fromFile = 1800-01-01
    convertedUsingLocalDate = 1799-12-31
    convertedUsingStatic = 1800-01-01

     
  • Nobody/Anonymous

    Last week I ran into the same problem. Seems to be an discrepancy in how java.util.Date and LocalDate handle leap seconds/minutes. More exactly: '1885-01-01' gives a java.util.Date object representing '1885-01-01T00:06:32.000'. This is saved in the database as '1885-01-01'. Reading from the database gives a Date object '1885-01-01 00:00:00.000'. Conversion to LocalDate gives the same discrepancy of 6 minutes 32 seconds: '1884-12-31T23:53:28.000'.
    Here is a simple JUnit test to test this:

    public void testDateToLocalDateConversion() {
        Calendar c = new GregorianCalendar(1885, 0, 1);
        Date d = c.getTime();
        LocalDate ld = new LocalDate(1885, 1, 1);
    
        assertEquals(ld, new LocalDate(d));
    }
    
     
  • Stephen Colebourne

    To nobody, your problem is because java.util.Date contains a time zone, the zone that your machine runs on. This has an offset of 6 mins 32 in 1885. So, although you think that the date in your database is '1885-01-01 00:00:00.000' it actually represents a millisecond value with a built in offset.

    LocalDate.fromDateFields() may yield the answer you want.

     
  • Stephen Colebourne

    To Frederic, I think that the same explanation applies. The question is whether PersistentLocalDate should use LocalDate.fromDateFields() or not. Have you tried changing the Joda code to do that?

     
  • Frédéric Donckels

    @scolebourne

    I haven't had the time yet, but this issue starting to be very annoying for us, I'm likely to subclass PersistentLocalDate to change that behaviour.

    I'm not a time expert, so I can't tell if this should be the default behaviour for everybody, but, in our case, it is.

     
  • Nobody/Anonymous

    This the alternative code that we use:

    public class PersistentLocalDateModified extends PersistentLocalDate {

    // ----- class fields -----
    
    private static final LocalTime NOON = new LocalTime(12, 0, 0);
    
    // ----- UserType -----
    
    @Override
    public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index)
            throws HibernateException, SQLException {
        if (value == null) {
            Hibernate.DATE.nullSafeSet(preparedStatement, null, index);
        } else {
            LocalDate localDate = (LocalDate) value;
            DateTime dateTime = localDate.toDateTime(NOON);
            Date date = dateTime.toDate();
            Hibernate.DATE.nullSafeSet(preparedStatement, date, index);
        }
    }
    
    // ----- public methods -----
    
    @Override
    public Object nullSafeGet(ResultSet resultSet, String string)
            throws SQLException {
        Object timestamp = Hibernate.DATE.nullSafeGet(resultSet, string);
        if (timestamp == null) {
            return null;
        }
        LocalDate toReturn = LocalDate.fromDateFields((Date) timestamp);
        return toReturn;
    }
    

    }

    This is far from an elegant solution, but playing with the seconds/minutes leap and so on was becoming slightly complex.

     

Log in to post a comment.

MongoDB Logo MongoDB