建议在SQLite数据库中存储java.time.OffsetDateTime的方法

时间:2017-10-11 20:38:51

标签: java android sqlite

我正在寻找将java.time.OffsetDateTime存储在SQLite数据库中的最佳方法(在Android上,如果有帮助的话)。

问题出现了,因为SQLite没有日期+时间的本机数据类型。

标准是我应该能够从以下方面产生明智的结果:

  1. 在“日期+时间”栏中订购。
  2. 不会失去很小的时差(毫秒)。
  3. 能够在BETWEEN
  4. 中使用等效的WHERE或范围
  5. 不丢失时区信息(设备可以全局使用并漫游)
  6. 希望保持一定的效率。
  7. 目前我将时间戳存储为ISO格式的字符串。不确定这对效率还是比较都是理想的。

    也许转换为UTC然后是长(Java)是一个选项,但我找不到OffsetDateTime中的一个函数,它返回自纪元以来的时间(例如,Instant.ofEpochMilli)。

    我应该提一下,数据是在Android设备上存储和使用的。应用程序代码使用时间戳并执行简单的算术,例如“自某个事件以来经过了多少天”。因此,数据正在存储类型和OffsetDateTime之间转换。

3 个答案:

答案 0 :(得分:1)

SQLite可能没有DATE存储/列类型。然而,SQLite确实具有相对灵活的DATE FUNCTIONS,它可以在许多存储/列类型上非常有效地工作。

我建议阅读SQL As Understood By SQLite - Date And Time Functions

例如,此列定义定义了一个列(MYCOLUMN),该列将保存插入行的日期时间(秒,而不是毫秒)。

"CREATE TABLE.......  mycolumn INTEGER DEFAULT (strftime('%s','now')), ......"

您可能还希望阅读Datatypes In SQLite Version 3 ,其中解释了数据类型的可靠性。例如第3节类型亲和力断言:

  

任何列仍然可以存储任何类型的数据。只是根据选择,某些列将优先使用一个存储类   另一个。列的首选存储类称为其   "亲和力"

e.g。

"CREATE TABLE.......  mycolumn BLOB DEFAULT (strftime('%s','now')), ......"

工作正常。使用Cursor方法getStringgetLonggetDouble输出以下内容: -

For Column otherblob Type is STRING value as String is 1507757213 value as long is 1507757213 value as double is 1.507757213E9 

就个人而言,我建议,由于毫秒,使用一种INTEGER并存储UTC并使用游标getLong从光标中检索。

答案 1 :(得分:0)

将日期时间存储为ISO 8601字符串(例如2017-06-11T17:21:05.370-02:00)。 SQLite有日期和时间函数,它们对这种字符串进行操作(可识别时区)。

java.time.OffsetDateTime可从API 26获得,因此我建议您使用ThreeTenABP库。

以下示例:

private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME

val datetime = OffsetDateTime.now()

//###### to ISO 8601 string (with time zone) - store this in DB
val datetimeAsText = datetime.format(formatter) 

//###### from ISO 8601 string (with time zone) to OffsetDateTime
val datetime2 = formatter.parse(datetimeAsText, OffsetDateTime::from)

为了使SQL查询能够识别时区,您需要使用SQLite中构建的日期和时间函数之一,例如 如果您想按日期和时间排序,可以使用datetime()函数:

SELECT * FROM cars ORDER BY datetime(registration_date)

答案 2 :(得分:-1)

UTC

通常,存储或交换日期时间值的最佳做法是将它们调整为UTC。

程序员和系统管理员应该学会用UTC思考而不是自己的狭隘时区。将UTC视为 The One True Time ,所有其他偏移和区域仅仅是该主题的变体。

OffsetDateTime调整为UTC。

OffsetDateTime odtUtc = myOdt.withOffsetSameInstant( ZoneOffset.UTC ) ;
  

odtUtc.toString():2007-12-03T10:15:30.783Z

最简单的方法是转换为Instant,它始终为UTC。

Instant instant = myOdt.toInstant() ;
String instantText = instant.toString() ;  // Generate text in standard ISO 8601 format.
…
myPreparedStatement.setString( instantText ) ;

ISO 8601

鉴于SQLite的局限性,我建议将这些值存储为文本,格式为按字母顺序排序时也是按时间顺序排列的。

您无需发明这样的格式。 ISO 8601标准已经定义了这种格式。标准格式合理,实用,易于通过机器解析,并且易于被不同文化的人阅读。

方便的是,java.time类在解析或生成字符串时默认使用ISO 8601格式。

如果JDBC驱动程序符合JDBC 4.2或更高版本,则可以通过setObjectgetObject将java.time对象直接交换到数据库。

myPreparedStatement.setObject( … , odtUtc ) ;

分辨率

您提到了milliseconds。知道java.time类使用nanoseconds的分辨率,最多九位小数秒。

计算天数

  

简单的算术,比如“自某事件过去了多少天”。

实际上并非如此简单。你的意思是日历日?如果是,请指定时区以确定日期。或者你的意思是24小时?

// Count of generic 24-hour days
String fromDatabase = myResultSet.getString( … ) ;
Instant instant = Instant.parse( fromDatabase ) ;
Instant now = Instant.now() ; 
long days = ChronoUnit.DAYS.between( instant , now ) ;

// Count of calendar days.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
ZonedDateTime zdtNow = now.atZone( z ) ;
long days = ChronoUnit.DAYS.between( zdt.toLocalDate() , zdtNow.toLocalDate() ).

半开

不要将BETWEEN谓词用于日期时间范围搜索。通常,最佳做法是使用半开放方法,其中开头是包含,结尾是独占。相比之下,BETWEEN包含两端,也称为“完全封闭”。

考虑另一个数据库

您的数据库需求可能超出了SQLite的有限目的。如果您需要广泛的日期时间处理或高性能等功能,请考虑升级到严格的数据库引擎。

Richard Hipp是SQLite的创建者,该产品旨在替代纯文本文件,而不是数据库领域的竞争对手。见What I learned about SQLite…at a PostgreSQL conference

H2数据库

对于Android,H2 Database是另一种可能性。有关详细信息,请参阅:

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?