我正在寻找将java.time.OffsetDateTime
存储在SQLite数据库中的最佳方法(在Android上,如果有帮助的话)。
问题出现了,因为SQLite没有日期+时间的本机数据类型。
标准是我应该能够从以下方面产生明智的结果:
BETWEEN
WHERE
或范围
目前我将时间戳存储为ISO格式的字符串。不确定这对效率还是比较都是理想的。
也许转换为UTC然后是长(Java)是一个选项,但我找不到OffsetDateTime中的一个函数,它返回自纪元以来的时间(例如,Instant.ofEpochMilli
)。
我应该提一下,数据是在Android设备上存储和使用的。应用程序代码使用时间戳并执行简单的算术,例如“自某个事件以来经过了多少天”。因此,数据正在存储类型和OffsetDateTime之间转换。
答案 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方法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
从光标中检索。
答案 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视为 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 ) ;
鉴于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
谓词用于日期时间范围搜索。通常,最佳做法是使用半开放方法,其中开头是包含,结尾是独占。相比之下,BETWEEN
包含两端,也称为“完全封闭”。
您的数据库需求可能超出了SQLite的有限目的。如果您需要广泛的日期时间处理或高性能等功能,请考虑升级到严格的数据库引擎。
Richard Hipp是SQLite的创建者,该产品旨在替代纯文本文件,而不是数据库领域的竞争对手。见What I learned about SQLite…at a PostgreSQL conference。
对于Android,H2 Database是另一种可能性。有关详细信息,请参阅:
java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和& SimpleDateFormat
现在位于Joda-Time的maintenance mode项目建议迁移到java.time类。
要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310。
从哪里获取java.time类?