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

Posted

技术标签:

【中文标题】在 Android SQLite 中处理日期的最佳方式 [关闭]【英文标题】:Best way to work with dates in Android SQLite [closed] 【发布时间】:2011-11-13 20:31:19 【问题描述】:

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

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

【问题讨论】:

只需使用 Calendar 类及其成员时间(表示自 1970 年 1 月 1 日以来经过的毫秒数)。有成员函数可以将时间值转换为用户可读的字符串。 【参考方案1】:

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

//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()); 

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

值得一提的是,由此得出的值应该是long,而不是int。 Integer in sqlite 可以表示很多东西,从 1 到 8 个字节不等,但对于几乎所有日期来说,64 位或很长的日期都是有效的。

编辑:正如 cmets 中所指出的,如果您这样做,您必须使用 cursor.getLong() 正确获取时间戳

【讨论】:

谢谢你。大声笑我想到了一个错误的输入,但我找不到它。它必须由 cursor.getLong() 检索,而不是由 cursor.getInt() 检索。大声笑不能停止嘲笑自己。再次感谢。 您写“值得一提的是,由此得出的值应该是长的,而不是 int。”但同时您在查询中使用了 'int'。所以一方面你说使用长而不是整数,另一方面你使用整数而不是长整数。这相当令人困惑。 正如@VanessaF 所说,您需要在毫秒内使用 DateTime 的长时间工作;否则,您将面临 int 溢出导致错误日期的风险。 SQLite中有4种类型的值,整数、实数、字符串和blob。 Long 不是一种类型,但它是整数的子集。当你从中获取值时,如上一行所述,你必须使用“cursor.getLong()”,但是当你在表中声明它时,它必须声明为整数。【参考方案2】:

您可以使用文本字段在SQLite 中存储日期。

以 UTC 格式存储日期,如果您使用 datetime('now'),则默认设置 (yyyy-MM-dd HH:mm:ss) 将允许按日期列排序。

SQLite 以字符串形式检索日期,然后您可以使用日历或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;

【讨论】:

您将如何处理查询日期范围? “推荐做法”?听起来不对。 在我使用 SQL 的这些年里,我从未见过有人建议将日期存储为字符串。如果您没有特定的日期列类型,请使用整数并以 Unix 时间(自纪元以来的秒数)存储。它可以在范围内排序和使用,并且易于转换。 如果您想将日期存储为“信息”,即您检索并显示的内容,则可以将日期存储为字符串。但是,如果您想将日期存储为“数据”,可以使用的东西,您应该考虑将其存储为整数 - 自纪元以来的时间。这将允许您查询日期范围,它是标准的,因此您不必担心转换等。将日期存储为字符串是非常有限的,我真的很想知道谁推荐这种做法作为一般规则。 sqlite documentation 将存储为文本 (ISO 8601) 列为存储日期的可行解决方案。其实是排在第一位的。【参考方案3】:
    假设in this comment,我总是使用整数来存储日期。

    对于存储,您可以使用实用程序方法

    public static Long persistDate(Date date) 
        if (date != null) 
            return date.getTime();
        
        return null;
    
    

    像这样:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
    

    另一种实用方法负责加载

    public static Date loadDate(Cursor cursor, int index) 
        if (cursor.isNull(index)) 
            return null;
        
        return new Date(cursor.getLong(index));
    
    

    可以这样使用:

    entity.setDate(loadDate(cursor, INDEX));
    

    按日期排序是简单的 SQL ORDER clause(因为我们有一个数字列)。以下将按降序排列(即最新日期在前):

    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
        
    

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

【讨论】:

【参考方案4】:

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

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

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

您可以使用col2col3 检查相同的SELECT,您将得到相同的结果。 如您所见,没有返回第二行(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');

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

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

没有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

没有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

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

工作得很好。

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

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

JDBC 有不同的方法从数据库中获取实际的日期/时间值:java.sql.Datejava.sql.Timejava.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.

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

new java.sql.Date(0)
new java.sql.Time(0)
new java.sql.Timestamp(0)

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

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

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

【讨论】:

鉴于 SQLite 的内部存储引擎,我不相信您的示例具有您暗示的效果:看起来引擎“允许在任何列中存储任何存储类型的值,而不管声明的SQL 类型”(books.google.de/…)。在我看来,在您的 Real vs. Integer vs. Text 示例中,正在发生的事情是这样的:SQLite 只是将文本作为 Text 存储在所有树列中。所以,自然结果都是好的,存储还是浪费。如果只使用一个整数,那么你应该失去毫秒。只是说... 其实你可以通过 SELECT datetime(col3, 'unixepoch') FROM test_table 来确认我刚才所说的。这将为您的示例显示空行......除非为了测试,您插入一个实际的整数。例如,如果您要添加 col3 值为 37 的行,则上面的 SELECT 语句将显示:1970-01-01 00:00:37。因此,除非您实际上可以将所有日期存储为文本字符串而效率低下,否则请不要按照您的建议进行操作。 自从我发布这个答案已经很久了......也许SQLite已经更新了。我能想到的唯一一件事就是根据您的建议再次执行 SQL 语句。【参考方案5】:

通常(与我在 mysql/postgres 中所做的相同)我将日期存储在 int(mysql/post) 或 text(sqlite) 中以将它们存储为时间戳格式。

然后我会将它们转换为 Date 对象并根据用户 TimeZone 执行操作

【讨论】:

【参考方案6】:

SQlite DB中存储date的最佳方式是存储当前的DateTimeMilliseconds。下面是执行此操作的代码sn-p_

    获取DateTimeMilliseconds
public static long getTimeMillis(String dateString, String dateFormat) throws ParseException 
    /*Use date format as according to your need! Ex. - yyyy/MM/dd HH:mm:ss */
    String myDate = dateString;//"2017/12/20 18:10:45";
    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat/*"yyyy/MM/dd HH:mm:ss"*/);
    Date date = sdf.parse(myDate);
    long millis = date.getTime();

    return millis;

    在您的数据库中插入数据
public void insert(Context mContext, long dateTimeMillis, String msg) 
    //Your DB Helper
    MyDatabaseHelper dbHelper = new MyDatabaseHelper(mContext);
    database = dbHelper.getWritableDatabase();

    ContentValues contentValue = new ContentValues();
    contentValue.put(MyDatabaseHelper.DATE_MILLIS, dateTimeMillis);
    contentValue.put(MyDatabaseHelper.MESSAGE, msg);

    //insert data in DB
    database.insert("your_table_name", null, contentValue);

   //Close the DB connection.
   dbHelper.close(); 


Now, your data (date is in currentTimeMilliseconds) is get inserted in DB .

下一步是,当您想从数据库中检索数据时,您需要将相应的日期时间毫秒转换为相应的日期。下面是执行相同操作的示例代码 sn-p_

    将日期毫秒转换为日期字符串。
public static String getDate(long milliSeconds, String dateFormat)

    // Create a DateFormatter object for displaying date in specified format.
    SimpleDateFormat formatter = new SimpleDateFormat(dateFormat/*"yyyy/MM/dd HH:mm:ss"*/);

    // Create a calendar object that will convert the date and time value in milliseconds to date.
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(milliSeconds);
    return formatter.format(calendar.getTime());

    现在,终于获取数据并查看它的工作...
public ArrayList<String> fetchData() 

    ArrayList<String> listOfAllDates = new ArrayList<String>();
    String cDate = null;

    MyDatabaseHelper dbHelper = new MyDatabaseHelper("your_app_context");
    database = dbHelper.getWritableDatabase();

    String[] columns = new String[] MyDatabaseHelper.DATE_MILLIS, MyDatabaseHelper.MESSAGE;
    Cursor cursor = database.query("your_table_name", columns, null, null, null, null, null);

    if (cursor != null) 

        if (cursor.moveToFirst())
            do
                //iterate the cursor to get data.
                cDate = getDate(cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.DATE_MILLIS)), "yyyy/MM/dd HH:mm:ss");

                listOfAllDates.add(cDate);

            while(cursor.moveToNext());
        
        cursor.close();

    //Close the DB connection.
    dbHelper.close(); 

    return listOfAllDates;


希望这对所有人都有帮助! :)

【讨论】:

SQLite 不支持长数据类型。编辑:我的错误,INTEGER 是 8 字节长,所以它应该支持这种数据类型。【参考方案7】:

1 - 就像 StErMi 所说的那样。

2 - 请阅读:http://www.vogella.de/articles/AndroidSQLite/article.html

3 -

Cursor cursor = db.query(TABLE_NAME, new String[] "_id", "title", "title_raw", "timestamp", 
                "//** YOUR REQUEST**//", null, null, "timestamp", null);

看这里:

Query() in SQLiteDatabase

4 - 见答案 3

【讨论】:

【参考方案8】:

我更喜欢这个。这不是最好的方法,而是一个快速的解决方案。

//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_CREATION_DATE+" DATE)");

//Inserting the data includes this:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
values.put(COLUMN_CREATION_DATE,dateFormat.format(reactionGame.getCreationDate())); 

// Fetching the data includes this:
try 
   java.util.Date creationDate = dateFormat.parse(cursor.getString(0);
   YourObject.setCreationDate(creationDate));
 catch (Exception e) 
   YourObject.setCreationDate(null);

【讨论】:

【参考方案9】:
"SELECT  "+_ID+" ,  "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";

【讨论】:

以上是关于在 Android SQLite 中处理日期的最佳方式 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Android 数据存储 - 文件与 SQLite

如何从 Sqlite ( android ) 中的 datetime 字段获取日期

如何在 Android 应用程序中插入日期时间设置为“现在”的 SQLite 记录?

Android日期SQLite SimpleDateFormat日历[重复]

Android - 将 SQLite 与 MySQL 同步的最佳方式 [重复]

带有日期字符串的 Android SQLite rawquery 在 WHERE 子句中不起作用