在 SQLite 数据库中存储 java.time.OffsetDateTime 的推荐方法

Posted

技术标签:

【中文标题】在 SQLite 数据库中存储 java.time.OffsetDateTime 的推荐方法【英文标题】:Recommended way to store java.time.OffsetDateTime in SQLite Database 【发布时间】:2017-10-11 20:38:50 【问题描述】:

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

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

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

    按“日期+时间”列排序。 不会丢失小的时间差(毫秒)。 能够在WHERE 中使用BETWEEN 的等效项或范围 不会丢失时区信息(设备可以在全球范围内使用和漫游) 希望能保持一些效率。

目前我将时间戳存储为 ISO 格式的字符串。不确定这是效率还是比较的理想选择。

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

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

【问题讨论】:

【参考方案1】:

将日期时间存储为 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)

【讨论】:

'val' 不是 java 关键字 那你会考虑使用 sqlite 数据类型 text 吗?【参考方案2】:

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 节类型关联断言:

任何列仍然可以存储任何类型的数据。只是有些列,如果可以选择,会更喜欢使用一个存储类而不是 其他。列的首选存储类称为它的 “亲和力”。

例如

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

工作正常。以下是使用光标方法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 从游标中检索。

【讨论】:

谢谢,我知道 SQLite 的非标准数据类型,实际上我在问我的问题之前阅读了那些寻找答案的页面(我可能应该提到的,我道歉)。看来我可能需要存储转换为 UTC 的时间戳并将时区信息保存在单独的列中。【参考方案3】:

UTC

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

程序员和系统管理员应该学会以 UTC 而不是他们自己的狭隘时区来思考问题。将 UTC 视为 唯一的真实时间,所有其他偏移量和区域只是该主题的变体。

将您的 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 谓词进行日期时间范围搜索。一般来说,最佳实践是使用半开放式方法,其中开头是inclusive,而结尾是exclusive。相比之下,BETWEEN 两端都包含,也称为“全封闭”。

考虑另一个数据库

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

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

H2 数据库

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

Tutorial H2 Database vs SQLite on Android

关于java.time

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

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

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。

从哪里获得 java.time 类?

Java SE 8Java SE 9 及更高版本 内置。 标准 Java API 的一部分,带有捆绑实现。 Java 9 添加了一些小功能和修复。 Java SE 6Java SE 7 大部分 java.time 功能都在ThreeTen-Backport 中向后移植到 Java 6 和 7。 Android ThreeTenABP 项目专门为 Android 适配 ThreeTen-Backport(如上所述)。 见How to use ThreeTenABP…

【讨论】:

感谢您的回答,但数据库位于 Android 设备上,因此我将对其进行替换。正如我在问题中提到的,我已经以 ISO (8601) 格式存储。 @AndreArtus 查看带有 H2 数据库信息链接的编辑。

以上是关于在 SQLite 数据库中存储 java.time.OffsetDateTime 的推荐方法的主要内容,如果未能解决你的问题,请参考以下文章

在 icloud 中存储 sqlite 数据库?

在“SQLite”数据库中存储多边形类型的数据

SQLite教程04-SQLite数据类型

我们可以使用 ionic 框架将视频存储在 sqlite 数据库中吗?如果可以,我们如何存储它?

图像不存储在 sqlite

如何将图像存储在 SQLite 数据库中