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字符串格式 调整为UTC T 在中间(没有空格字符) 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 格式,包括末尾的 Z2016-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/Floatdouble/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中正确关闭光标

在 Android SQLite 中处理日期的最佳方式 [关闭]

Android clear listview 链接到 SQLite

ListView 未根据 sqlite 中的名称显示可绘制对象中的正确图像

android如何查看手机中的db文件,查看sqlite数据库的表结构