在Android SQLite中使用日期的最佳方式

Fil*_*ipe 231 sql database sqlite android date

我在使用SQLite的Android应用程序上处理日期时遇到了一些麻烦.我有几个问题:

  1. 我应该使用什么类型在SQLite中存储日期(文本,整数,...)?
  2. 鉴于存储日期的最佳方法如何使用ContentValues正确存储它?
  3. 从SQLite数据库中检索日期的最佳方法是什么?
  4. 如何在SQLite上进行SQL选择,按日期排序结果?

Pea*_*oto 206

最好的方法是将日期存储为数字,使用"日历"命令接收.

//Building the table includes:
StringBuilder query=new StringBuilder();
query.append("CREATE TABLE "+TABLE_NAME+ " (");
query.append(COLUMN_ID+"int primary key autoincrement,");
query.append(COLUMN_DATETIME+" int)");

//And inserting the data includes this:
values.put(COLUMN_DATETIME, System.currentTimeMillis()); 
Run Code Online (Sandbox Code Playgroud)

为什么这样?首先,从日期范围获取值很容易.只需将您的日期转换为毫秒,然后进行适当的查询.按日期排序同样容易.正如我所包含的那样,转换各种格式的调用也同样容易.最重要的是,通过这种方法,您可以做任何您需要做的事情,没有问题.读取原始值有点困难,但它更容易弥补机器可读和可用的轻微缺点.事实上,构建一个阅读器(我知道有一些阅读器)相对容易,它会自动将时间标签转换为日期,以便于阅读.

值得一提的是,由此产生的值应该很长,而不是int.sqlite中的整数可以表示很多东西,从1-8字节开始,但对于几乎所有日期,64位或长整数都有效.

编辑:正如评论中指出的那样,cursor.getLong()如果你这样做,你必须使用它来正确获取时间戳.

  • 谢了,兄弟们.大声笑我想到了错误的打字,但我找不到它.它必须由cursor.getLong()检索,而不是由cursor.getInt()检索.哈哈不能停止嘲笑自己.再次感谢. (17认同)

Cod*_*imp 42

您可以使用文本字段来存储日期SQLite.

以UTC格式存储日期,如果使用默认值,datetime('now') (yyyy-MM-dd HH:mm:ss)则允许按日期列进行排序.

SQLite您那里检索日期作为字符串然后可以使用Calendar或android.text.format.DateUtils.formatDateTime方法将它们格式化/转换为本地区域化格式.

这是我使用的区域化格式化方法;

public static String formatDateTime(Context context, String timeToFormat) {

    String finalDateTime = "";          

    SimpleDateFormat iso8601Format = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss");

    Date date = null;
    if (timeToFormat != null) {
        try {
            date = iso8601Format.parse(timeToFormat);
        } catch (ParseException e) {
            date = null;
        }

        if (date != null) {
            long when = date.getTime();
            int flags = 0;
            flags |= android.text.format.DateUtils.FORMAT_SHOW_TIME;
            flags |= android.text.format.DateUtils.FORMAT_SHOW_DATE;
            flags |= android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
            flags |= android.text.format.DateUtils.FORMAT_SHOW_YEAR;

            finalDateTime = android.text.format.DateUtils.formatDateTime(context,
            when + TimeZone.getDefault().getOffset(when), flags);               
        }
    }
    return finalDateTime;
}
Run Code Online (Sandbox Code Playgroud)

  • 在我使用SQL的那些年里,我从未见过任何人以前建议将日期存储为字符串.如果您没有特定的日期列类型,请使用整数并在Unix时间(自纪元以来的秒数)存储.它的可分类和可用范围很容易转换. (132认同)
  • 你会如何处理查询日期范围? (63认同)
  • "推荐做法"?听起来不对劲. (51认同)
  • 如果您想将日期存储为"信息",则可以将日期存储为字符串,这是您检索和显示的内容.但是如果你想将日期存储为"数据",可以使用它,你应该考虑将它存储为整数 - 自纪元以来的时间.这将允许您查询日期范围,它是标准的,因此您不必担心转换等.将日期存储为字符串是非常有限的,我真的想知道谁推荐这种做法作为一般规则. (20认同)
  • [sqlite文档](http://www.sqlite.org/datatype3.html)列出了存储为文本(ISO 8601)作为存储日期的可行解决方案.实际上,它首先列出. (7认同)
  • 可行并不意味着推荐. (5认同)
  • @anderspitman阅读了mikebabcock的评论.他已经说过为什么以int/long存储比string更好.这个答案远离现实.我想知道为什么它收到了这么多的赞成票. (4认同)
  • 存储为毫秒的一个问题是,例如,您丢失了timzezone.所以你可以单独保存.了解时间并不是唯一重要的事情.知道用户感知的日期/时间通常很重要,如果您要将信息显示给他们. (3认同)
  • @Fraggle推荐格式(ISO8601)也没有时区.所以你必须假设它是UTC.(但如果不是?) (2认同)

sch*_*rer 34

  1. 正如本评论中所假设的那样,我总是使用整数来存储日期.
  2. 对于存储,您可以使用实用程序方法

    public static Long persistDate(Date date) {
        if (date != null) {
            return date.getTime();
        }
        return null;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    像这样:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
    
    Run Code Online (Sandbox Code Playgroud)
  3. 另一种实用方法负责装载

    public static Date loadDate(Cursor cursor, int index) {
        if (cursor.isNull(index)) {
            return null;
        }
        return new Date(cursor.getLong(index));
    }
    
    Run Code Online (Sandbox Code Playgroud)

    可以像这样使用:

    entity.setDate(loadDate(cursor, INDEX));
    
    Run Code Online (Sandbox Code Playgroud)
  4. 按日期排序是简单的SQL ORDER子句(因为我们有一个数字列).以下将按顺序降序(即最新的日期先行):

    public static final String QUERY = "SELECT table._id, table.dateCol FROM table ORDER BY table.dateCol DESC";
    
    //...
    
        Cursor cursor = rawQuery(QUERY, null);
        cursor.moveToFirst();
    
        while (!cursor.isAfterLast()) {
            // Process results
        }
    
    Run Code Online (Sandbox Code Playgroud)

始终确保存储UTC/GMT时间,尤其是在使用java.util.Calendarjava.text.SimpleDateFormat使用默认(即您设备的)时区时. java.util.Date.Date()使用是安全的,因为它创建一个UTC值.


mig*_*elt 9

SQLite可以使用文本,实数或整数数据类型来存储日期.更重要的是,无论何时执行查询,结果都会以格式显示%Y-%m-%d %H:%M:%S.

现在,如果使用SQLite日期/时间函数插入/更新日期/时间值,您实际上也可以存储毫秒数.如果是这种情况,结果将使用格式显示%Y-%m-%d %H:%M:%f.例如:

sqlite> create table test_table(col1 text, col2 real, col3 integer);
sqlite> insert into test_table values (
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123')
        );
sqlite> insert into test_table values (
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.126'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.126'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.126')
        );
sqlite> select * from test_table;
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126
Run Code Online (Sandbox Code Playgroud)

现在,做一些查询来验证我们是否真的能够比较时间:

sqlite> select * from test_table /* using col1 */
           where col1 between 
               strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.121') and
               strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.125');
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
Run Code Online (Sandbox Code Playgroud)

您可以SELECT使用col2和检查相同col3,您将得到相同的结果.如您所见,未返回第二行(126毫秒).

请注意,这BETWEEN是包容性的,因此......

sqlite> select * from test_table 
            where col1 between 
                 /* Note that we are using 123 milliseconds down _here_ */
                strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123') and
                strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.125');
Run Code Online (Sandbox Code Playgroud)

...将返回相同的集合.

尝试使用不同的日期/时间范围,一切都将按预期运行.

没有strftime功能怎么样?

sqlite> select * from test_table /* using col1 */
           where col1 between 
               '2014-03-01 13:01:01.121' and
               '2014-03-01 13:01:01.125';
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
Run Code Online (Sandbox Code Playgroud)

没有strftime功能而没有毫秒呢?

sqlite> select * from test_table /* using col1 */
           where col1 between 
               '2014-03-01 13:01:01' and
               '2014-03-01 13:01:02';
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126
Run Code Online (Sandbox Code Playgroud)

怎么样ORDER BY

sqlite> select * from test_table order by 1 desc;
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
sqlite> select * from test_table order by 1 asc;
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126
Run Code Online (Sandbox Code Playgroud)

工作得很好.

最后,在处理程序中的实际操作时(不使用sqlite可执行文件......)

顺便说一句:我正在使用JDBC(不确定其他语言)...来自xerial的sqlite-jdbc驱动程序v3.7.2 - 也许更新版本会改变下面解释的行为...如果你在Android中开发,你就不会需要一个jdbc驱动程序.可以使用SQLiteOpenHelper.提交所有SQL操作.

JDBC有不同的方法来从数据库中获取实际的日期/时间值:java.sql.Date,java.sql.Time,和java.sql.Timestamp.

在相关的方法java.sql.ResultSet是(明显)getDate(..),getTime(..)getTimestamp()分别.

例如:

Statement stmt = ... // Get statement from connection
ResultSet rs = stmt.executeQuery("SELECT * FROM TEST_TABLE");
while (rs.next()) {
    System.out.println("COL1 : "+rs.getDate("COL1"));
    System.out.println("COL1 : "+rs.getTime("COL1"));
    System.out.println("COL1 : "+rs.getTimestamp("COL1"));
    System.out.println("COL2 : "+rs.getDate("COL2"));
    System.out.println("COL2 : "+rs.getTime("COL2"));
    System.out.println("COL2 : "+rs.getTimestamp("COL2"));
    System.out.println("COL3 : "+rs.getDate("COL3"));
    System.out.println("COL3 : "+rs.getTime("COL3"));
    System.out.println("COL3 : "+rs.getTimestamp("COL3"));
}
// close rs and stmt.
Run Code Online (Sandbox Code Playgroud)

由于SQLite没有实际的DATE/TIME/TIMESTAMP数据类型,因此所有这3个方法都返回值,就像对象初始化为0一样:

new java.sql.Date(0)
new java.sql.Time(0)
new java.sql.Timestamp(0)
Run Code Online (Sandbox Code Playgroud)

所以,问题是:我们如何实际选择,插入或更新日期/时间/时间戳对象?没有简单的答案.您可以尝试不同的组合,但它们会强制您在所有SQL语句中嵌入SQLite函数.定义一个实用程序类来将文本转换为Java程序中的Date对象要容易得多.但请记住,SQLite会将任何日期值转换为UTC + 0000.

总之,尽管一般规则总是使用正确的数据类型,或者甚至整数表示Unix时间(自纪元以来的毫秒),我发现使用默认的SQLite格式('%Y-%m-%d %H:%M:%f'或在Java中'yyyy-MM-dd HH:mm:ss.SSS')更容易,而是使所有SQL语句复杂化SQLite函数.前一种方法更容易维护.

TODO:我会在Android(API15或更高版本)中使用getDate/getTime/getTimestamp检查结果...也许内部驱动程序与sqlite-jdbc不同...