Android 如何创建一个TypeConverter,将LocalDate转换为房间可以理解/保存的格式?

Android 如何创建一个TypeConverter,将LocalDate转换为房间可以理解/保存的格式?,android,android-room,java-time,Android,Android Room,Java Time,我正在使用房间。我需要关于如何将LocalDate从java.time转换为一种格式(可能是Long,TimeStamp,sql.Date或者我甚至不知道还有什么)的指南,以便我可以将日期保存到数据库中 我有一本书: @Entity data class Book( @ColumnInfo(name = "date") val date: LocalDate, @ColumnInfo(name = "count") val count:

我正在使用
房间
。我需要关于如何将
LocalDate
java.time
转换为一种格式(可能是
Long
TimeStamp
sql.Date
或者我甚至不知道还有什么)的指南,以便我可以将日期保存到数据库中

我有一本书:

@Entity
data class Book(
    @ColumnInfo(name = "date") val date: LocalDate,  
    @ColumnInfo(name = "count") val count: Int,
    @PrimaryKey(autoGenerate = true)
    val id: Int? = null
)
我还创作了书刀:

@Dao
interface BookDao {
    @Query("SELECT * FROM book WHERE :date >= book.date")
    fun getBooks(date: LocalDate): List<Book>
}
但是,我得到了一个错误:

error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
    private final java.time.LocalDate date = null;

您遇到的问题是,TypeConverter应该从/转换为Room支持插入/提取数据的一组特定类型

更常见的有Int、Long Byte、Byte[]、Float、Double、Decimal、String、BigInt、BigDecimal

  • 可能还有其他类型,但可以使用的类型有限
因此,消息是说,请转换您的LocalDate或Date,因为它无法处理它们

我建议将日期存储为unix日期(除非您需要微秒精度,因为您可以用毫秒存储时间(在sqlite中使用日期/时间函数时可能会有点尴尬,可能需要强制转换)),即长或Int

在此过程中,您:

  • 将最大限度地减少用于存储值的存储量
  • 将能够利用
  • 可以轻松地将其提取为非常灵活的格式
  • 您可以按原样提取它们,然后使用类函数对它们进行格式转换
  • 您将不需要上述类型转换器
下面的示例显示了一些功能,将它们存储为unix时间戳

首先是使用默认日期的账簿实体版本:-

@Entity
data class Book(
    @ColumnInfo(name = "date", defaultValue = "(strftime('%s','now','localtime'))")
    val date: Long? = null,
    @ColumnInfo(name = "count")
    val count: Int,
    @PrimaryKey(autoGenerate = true)
    val id: Int? = null
)
  • 请参见SQLITE日期和时间函数以了解上述说明。简言之,上面允许您插入一行,并将日期和时间设置为当前日期时间
  • 请参阅insertBook函数,以便仅使用提供的计数进行插入
要随书实体一起使用书刀:-

@Dao
interface BookDao {
    @Insert /* insert via a book object */
    fun insert(book: Book): Long
    @Query("INSERT INTO BOOK (count) VALUES(:count)")

    /* insert supplying just the count id and date will be generated */
    fun insertBook(count: Long): Long
    @Query("SELECT * FROM book")

    /* get all the books  as they are stored into a list of book objects*/
    fun getAllFromBook(): List<Book>

    /* get the dates as a list of dates in yyyy-mm-dd format using the SQLite date function */
    @Query("SELECT date(date, 'unixepoch', 'localtime') AS mydateAsDate FROM book")
    fun getDatesFromBook(): List<String>

    /* get the date + 7 days */
    @Query("SELECT date(date,'unixepoch','localtime','+7 days') FROM book")
    fun getDatesFromBook1WeekLater(): List<String>
}
运行后的日志:-

2021-04-16 14:28:50.225 D/BOOKINFO_BOOK:  Date = 1618547330 Count = 10 ID = 1
2021-04-16 14:28:50.225 D/BOOKINFO_BOOK:  Date = 1618583330 Count = 20 ID = 2
2021-04-16 14:28:50.226 D/BOOKINFO_DATE: 2021-04-16
2021-04-16 14:28:50.227 D/BOOKINFO_DATE: 2021-04-17
2021-04-16 14:28:50.228 D/BOOKINFO_1WEEKLATER: 2021-04-23
2021-04-16 14:28:50.228 D/BOOKINFO_1WEEKLATER: 2021-04-24
  • (请注意,当地时间的调整(尽管使用不当,因此向前跳))
  • 存储在Book对象和数据库中的前2行数据
  • 接下来的两行是通过SQLite日期/时间函数转换为YYYY-MM-DD格式的日期
  • 最后两行是存储日期转换为YYYY-MM-DD后一周的日期
根据数据库检查器检查数据库:-


如果您想使用转换器,可以按照文档中的方法操作。在您的示例中,您尝试转换为SQLdate,但Android建议转换为Long

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}
然后将@TypeConverters注释添加到AppDatabase

对于LocalDate,它应该是这样的:

class Converters {
    @TypeConverter
    fun fromTimestamp(value: Long?): LocalDate? {
        return value?.let { LocalDate.ofEpochDay(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: LocalDate?): Long? {
        val zoneId: ZoneId = ZoneId.systemDefault()
        return date?.atStartOfDay(zoneId)?.toEpochSecond()
    }
}

为什么不在数据库中以毫秒为单位保存时间戳,然后在需要时转换它?@tyczj在没有时间的日期中使用毫秒是没有意义的。此外,如此大的数字并不是真正的人类可读的,因此使调试和特别查询变得复杂。最后,从Unix纪元开始计数假定有一个时区,因此,如果时区发生变化(用户旅行?),您将有可能关闭一个错误。我建议您将
LocalDate
存储为类似
2021-04-16
的文本。
class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}
class Converters {
    @TypeConverter
    fun fromTimestamp(value: Long?): LocalDate? {
        return value?.let { LocalDate.ofEpochDay(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: LocalDate?): Long? {
        val zoneId: ZoneId = ZoneId.systemDefault()
        return date?.atStartOfDay(zoneId)?.toEpochSecond()
    }
}