Android - 如何在 SQLite 中“正确”存储日期和时间?
Posted
技术标签:
【中文标题】Android - 如何在 SQLite 中“正确”存储日期和时间?【英文标题】:Android - How to "properly" store Date and Time in SQLite? 【发布时间】:2016-09-17 11:57:04 【问题描述】:我已经阅读了一些关于这个问题的其他帖子。大多数答案建议将日期/时间数据转换为某种格式(UTC)并将其存储在字符串数据类型中。
将它们以整数格式存储并为每个数据(年、月、日、小时、分钟)存储 1 列是否“合适”?或者这是不可接受的?因为这会使与当前日期/时间的比较更容易。我打算做的是一行的数据是否超过当前日期。
Java 代码:
public void saveExam()
date = (DatePicker)findViewById(R.id.examDate);
Integer day = date.getDayOfMonth();
Integer month = date.getMonth();
Integer year = date.getYear();
StringBuilder temp = new StringBuilder();
temp.append(day.toString()).append("/").append(month.toString()).append("/").append(year.toString());
String dateStr = temp.toString();
time = (TimePicker)findViewById(R.id.examTime);
Integer hour, minutes;
if (Build.VERSION.SDK_INT >= 23 )
hour = time.getHour();
minutes = time.getMinute();
else
hour = time.getCurrentHour();
minutes = time.getCurrentMinute();
temp = new StringBuilder();
temp.append(hour.toString()).append(":").append(minutes.toString());
String timeStr = temp.toString();
Toast.makeText(add_exam.this, dateStr + ", " + timeStr, Toast.LENGTH_LONG).show();
【问题讨论】:
SQLite 旨在替代编写文本文件,而不是成为一个严肃的数据库。如果您想要一个严肃的数据库,包括日期时间数据类型,请考虑纯 Java H2 Database Engine。见this page on using H2 on android, with comparison to SQLite。 【参考方案1】:根据Sqlite documentation (section 2.2),您应该以下列方式之一存储您的日期:
TEXT 作为 ISO8601 字符串 ("YYYY-MM-DD HH:MM:SS.SSS")。 REAL 作为儒略日数。 INTEGER 作为 Unix 时间,自 1970-01-01 00:00:00 UTC 以来的秒数。可以使用strftime 函数解决比较问题,该函数允许您计算日期,而不管其存储类型(TEXT、REAL 或 INTEGER)。
假设您想要获取数据库中日期为 Jenuary 的所有行。你可以这样做:
SELECT * FROM table
WHERE strftime('%m', your_date_column) == '01'
或者您可能想获取时间为 09:40 的所有行:
SELECT * FROM table
WHERE strftime('%H:%M', your_date_column) == '09:40'
也可以:
SELECT * FROM table
WHERE strftime('%s', your_date_column) < strftime('%s','now')
将返回当前日期(现在)之前日期的所有行。
SUMMARY:函数strftime
抽象了数据库的存储模式,所以它实际上并不决定你使用哪种模式。
【讨论】:
(A)YYYY-MM-DD HH:MM:SS.SSS
不 ISO 8601。YYYY-MM-DDTHH:MM:SS.S…
符合标准。中间的T
只能通过发送方和接收方之间的特殊协议替换为SPACE。 SPACE 由早于 ISO 8601 的 SQL 标准使用。(B) ISO 8601 标准规定可以使用小数秒中的任意数量的数字。在实践中,最多九位数的纳秒是面向业务(非科学/工程)应用程序中的最大值。无论如何,标准不要求像三位数这样的固定数字。
official SQLite guide 明确不使用 T。正如您所说,在信息交换合作伙伴的相互同意下,可以删除 T。由于我们在谈论数据库,因此他们不需要与任何人达成一致。这只是一种存储数据的方式。当您从数据库中检索日期时,您应该根据您的交换伙伴使用适当的格式。关于三位数......同样的故事
是的,我已经阅读了 SQLite 文档。 doc 完全错误,混淆了 ISO 8601 格式的 SQL 日期时间格式。这就是我的观点。而且我不是迂腐。从 SQLite 中提取此字符串后,您很可能在 Java 环境中转身解析为 java.time 类型。默认情况下,java.time 类可以解析和生成 ISO 8601 字符串,但只有真正严格定义的 ISO 8601。这些 SQL 格式的字符串将失败,这会使天真的程序员感到困惑,因为它们被错误标记为 ISO 8601。SQLite 团队真的应该在他们的文档中更正这个错误。【参考方案2】:
tl;博士
用途:
严格定义的ISO 8601字符串格式 调整为UTCT
在中间(没有空格字符)
Z
最后:
YYYY-MM-DDTHH:MM:SS.S…Z
2016-09-17T18:28:27Z
严格的 ISO 8601 格式
Answer by GVillani82 是正确的,应该被接受。唯一的问题是引用了错误声明的 SQLite 文档:
TEXT 作为 ISO8601 字符串 ("YYYY-MM-DD HH:MM:SS.SSS")
该语句有两点错误:中间的空格和小数部分的三位数字。
T
在中间
严格来说,中间有空格的格式不符合ISO 8601。 ISO 8601 标准要求在中间使用T
,而不是空格。引用 the Wikipedia page 总结 ISO 8601 标准:
…连接完整的日期表达式、作为分隔符的字母 T 和有效的时间表达式。例如,“2007-04-05T14:30”。
T
只能在数据发送者和接收者的特殊安排下被替换为空格。
更令人困惑的是,中间有空格的格式确实符合 SQL 标准对日期时间格式的定义。 SQL 规范早于 ISO 8601。ISO 8601 正迅速成为首选格式,因为它继续在全球范围内被商业世界以及现代计算机协议采用。
小数部分
ISO 8601 标准允许秒的小数部分中包含任意数量的数字。引用的文档暗示需要三个数字,但实际上允许任何数字,包括零(根本没有数字,整秒)。
三位代表milliseconds,六位代表microseconds,九位代表nanoseconds。这些都是主流的面向业务的应用程序中常用的。任何更精细的部分只能在科学/工程类型的应用中看到。
java.time 类使用nanosecond 分辨率,而旧的日期时间Java 类仅限于毫秒。
实用性
这两个问题不仅仅是学术问题,而是提出了实际问题。
在 Java 环境中,我们可能会从 SQLite 中提取此字符串,然后将其解析为 java.time 对象。 java.time 类是处理日期时间编程的现代方式。这个框架的灵感来自Joda-Time(领导这项工作的同一个人,Stephen Colbourne),由JSR 310 定义,内置于 Java 8 及更高版本中,正式取代了麻烦的旧日期时间类,向后移植到 Java 6 和 7在ThreeTen-Backport 项目中并进一步适应Android 在ThreeTenABP 项目中。
在解析/生成表示日期时间值的字符串时,java.time 类默认使用标准 ISO 8601 格式。但是 java.time 只使用严格的定义。这意味着 T
在日期时间的中间。
Instant instant = Instant.parse( "2016-09-17T18:28:27Z" );
问题是解析被SQLite文档错误标记为ISO 8601的格式,中间有SPACE将无法被java.time解析。换个方向,java.time 生成的字符串默认会在中间有T
,而不是SPACE。
解决方案很简单:只需使用严格的 ISO 8601 定义,中间加上 T
。
如果您对SQLite documentation page on date-time functions 进行了足够深入的阅读,您会看到它提到:
...“T”是分隔日期和时间的文字字符,符合 ISO-8601 的要求
这种带有T
的格式受SQLite 及其日期时间函数的支持。此外,该文档指出,Z
的时区指示符也支持Zulu
UTC。
...可以选择后跟“[+-]HH:MM”或只是“Z”形式的时区指示符。日期和时间函数在内部使用 UTC 或“zulu”时间,因此“Z”后缀是空操作。
因此,我建议将在 SQLite 中存储时刻的最明智方法调整为 UTC,并使用严格的 ISO 8601 格式,包括末尾的 Z
:2016-09-17T18:28:27Z
至于小数秒,SQLite 文档说,虽然只有前三个小数位数对其 SQLite 函数有意义,但您实际上可以存储任意数量的数字。因此,java.time 中允许的纳秒内最多 9 位数字显然可以成功存储,尽管没有精确比较:2016-09-17T18:28:27.123456789Z
如果这种比较精度对您的应用来说是个问题,您可以使用可以处理纳秒的 java.time 类在应用的 Java 端进行比较工作,或者您可以考虑将日期时间值截断为毫秒解析度。 java.time 类提供了一种方便的截断方法。
其他选项
选项呢?
REAL 作为儒略日数。 INTEGER 作为 Unix 时间,自 1970-01-01 00:00:00 UTC 以来的秒数。Floating point technology 用于实数,例如 Java 中的float
/Float
和double
/Double
,故意牺牲准确性来换取执行速度。这意味着无关的数字经常出现在分数的右侧。虽然这在某些科学/工程/游戏领域可能是可以容忍的,但在金钱问题或日期时间值的某些用途中是不可接受的。所以,在我看来,这不是一个好主意。
从-epoch 计数,例如自 1970 年初以来 UTC 的秒数不是一个好主意。虽然 java.time 类在后台使用这种方法来实际跟踪时间,但我们有这些封面是有原因的。这样的整数对人类读者没有意义,无法辨别日期时间值,因此可能无法检测到错误。数据类型本身是模棱两可的,因为我们必须猜测预期的粒度,一些计算机系统使用整秒作为计数,其他计算机系统使用毫秒或微秒或纳秒或其他粒度。至于时代,这也是模棱两可的,因为至少有一个couple dozen epoch points 已在各种计算系统中使用。
其他数据库
请注意,SQLite 从来没有打算成为一个严肃的重型数据库。因此,SQLite 中的lite
。 inventor has explained 他打算让 SQLite 成为编写纯文本文件的升级替代方案。他从未打算让 SQLite 成为更严肃的数据库的竞争对手。
因此,如果您遇到 SQLite 限制的痛点,您的需求可能已经超出了 SQLite 的设计参数。如果您需要更严肃的数据库,请使用更严肃的数据库。
在 Android 环境中,我首先考虑的是H2 Database Engine。这种纯 Java、开源、免费的数据库产品正在积极开发并越来越受欢迎。见:
H2 documentation about use on Android H2 Database vs SQLite on Android - 堆栈内存溢出【讨论】:
以上是关于Android - 如何在 SQLite 中“正确”存储日期和时间?的主要内容,如果未能解决你的问题,请参考以下文章
在 Android SQLite 中处理日期的最佳方式 [关闭]
Android clear listview 链接到 SQLite