在 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')), ......"
工作正常。以下是使用光标方法getString
、getLong
和getDouble
的输出,用于使用上面插入的行:-
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 或更高版本,您可以通过 setObject
和 getObject
将 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.Date
、Calendar
和 SimpleDateFormat
。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
从哪里获得 java.time 类?
Java SE 8、Java SE 9 及更高版本 内置。 标准 Java API 的一部分,带有捆绑实现。 Java 9 添加了一些小功能和修复。 Java SE 6 和 Java 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 的推荐方法的主要内容,如果未能解决你的问题,请参考以下文章