Search code examples
kotlinandroid-room

Convert String to Date with null safety


I'm using @TypeConverter in Room to convert string to Date (datetime). Here is the code

public class DateTimeConverter {

    @TypeConverter
    public static Date stringToDate(String value) {
        DateFormat df = new SimpleDateFormat(Constants.SQLITE_DATE_TIMEFORMAT, Locale.US);
        if (value != null) {
            try {
                return df.parse(value);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @TypeConverter
    public static String dateToString(Date value) {
        DateFormat df = new SimpleDateFormat(Constants.SQLITE_DATE_TIMEFORMAT, Locale.US);
        if (value != null) {
            return df.format(value);
        } else {
            return null;
        }
    }
}
@Entity
@TypeConverters(DateTimeConverter::class)
data class Entity(
    var writeDate: Date = Date() // java.util.Date
)

My current issues is

  1. stringtoDate receives value = null which results in Entity.writeDate to be null which is a run-time exception

Question

  1. How to convert string to Date with null safety? The value of writeDate in the table is never null, but stringToDate still receives value = null.

Note:

  1. Using SDK > 23. So can't use DateTimeFormatter.ofPattern

Solution

  • The value of writeDate in the table is never null, but stringToDate still receives value = null.

    You issue would be to determine why the writeDate is being extracted as null and that would be within the functions in classes that are annotated with @Dao.

    However, you could us the following to ensure that nulls are never returned:-

    public class DateTimeConverter {
    
        @TypeConverter
        public static Date stringToDate(String value) {
            Date defaultDate = new Date(0); //1970-01-01 00:00:00
            DateFormat df = new SimpleDateFormat(Constants.SQLITE_DATE_TIMEFORMAT, Locale.US);
            if (value != null) {
                try {
                    return df.parse(value);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
            return defaultDate;
        }
    
        @TypeConverter
        public static String dateToString(Date value) {
            DateFormat df = new SimpleDateFormat(Constants.SQLITE_DATE_TIMEFORMAT, Locale.US);
            if (value != null) {
                return df.format(value);
            } else {
                return "1970-01-01 00:00:00";
            }
        }
    }
    

    Here's a demonstration that uses the above in conjunction with:-

    The @Dao class AllDao :-

    @Dao
    abstract class AllDao {
    
        @Insert(onConflict = IGNORE)
        abstract fun insert(entity: Entity)
    
        /* Delete all rows */
        @Query("DELETE FROM entity")
        abstract fun clear()
    
        /* get inserted data */
        @Query("SELECT * FROM entity")
        abstract fun getAllFromEntity(): List<Entity>
    
        /* purposefully get 2 invalid dates (1 rubbish date, 1 NULL) */
        @Query("SELECT -999 AS id,'invaliddate' AS writeDate UNION SELECT -123 AS id, NULL as writeDate")
        abstract fun getMessedUpDate(): List<Entity>
    
    }
    
    • The getMessedUpDate designed to do as it says and get dates that would result in nulls by the TypeConverters in the question but not by the modified TypeConverters in the Answer.

    and then using :-

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()
        dao.clear()
    
        dao.insert(Entity())
        var dateInt = (System.currentTimeMillis()) 
        dao.insert(Entity(writeDate = Date(dateInt)))
        dao.insert(Entity(1000,Date((System.currentTimeMillis()) - (100 /*days*/ * 24 /*hours*/ * 60 /*mins*/ * 60 /*secs*/ * 1000))))
    
        for (e: Entity in dao.getAllFromEntity()) {
            Log.d("DBINFO","Date is ${e.writeDate} ID is ${e.id}")
        }
        for (e: Entity in dao.getMessedUpDate()) {
            Log.d("DBINFO","Date is ${e.writeDate} ID is ${e.id}")
        }
    

    The log includes :-

    2021-11-06 07:40:38.063 D/DBINFO: Date is Sat Nov 06 07:40:38 GMT+11:00 2021 ID is 1
    2021-11-06 07:40:38.064 D/DBINFO: Date is Sat Nov 06 07:40:38 GMT+11:00 2021 ID is 2
    2021-11-06 07:40:38.064 D/DBINFO: Date is Fri Nov 05 17:46:12 GMT+11:00 2021 ID is 1000
    2021-11-06 07:40:38.069 D/DBINFO: Date is Thu Jan 01 10:00:00 GMT+10:00 1970 ID is -999
    2021-11-06 07:40:38.069 D/DBINFO: Date is Thu Jan 01 10:00:00 GMT+10:00 1970 ID is -123
    
    • i.e. the 4th and 5th lines have returned the "default date" rather than null.