在Android SQLite中使用日期的最佳方法[关闭]


237

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

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

2
只需使用Calendar类及其成员时间(代表自1970年1月1日以来经过的毫秒数)。有一些成员函数可以将时间值转换为用户可读的字符串。
slayton 2011年

Answers:


43

您可以使用文本字段将日期存储在中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;
}

63
您将如何处理日期范围查询?
2012年

51
“推荐做法”?听起来不对。
shim 2012年

135
在我使用SQL的那些年里,我从未见过有人建议将日期存储为字符串。如果您没有特定的日期列类型,请使用整数并存储在Unix时间(自纪元以来的秒数)中。其可排序且在范围内可用,并且易于转换。
mikebabcock 2012年

20
如果要将日期存储为“信息”(可以检索并显示),则将日期存储为字符串是可以的。但是,如果您想将日期存储为“数据”,那么您应该考虑将其存储为整数-自纪元以来的时间。这将允许您查询日期范围,这是标准的,因此您不必担心转换等问题。将日期存储为字符串是非常有限的,我真的很想知道是谁推荐了这种做法作为一般规则。
Krystian

8
sqlite的文件列表保存为文本(ISO 8601)作为存储日期的可行的解决方案。实际上,它被列在第一位。
anderspitman 2014年

211

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

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

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

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

编辑:正如评论中指出的那样,cursor.getLong()如果您执行此操作,则必须使用来正确获取时间戳。


17
谢了,兄弟们。大声笑,我想到了打字错误,但我找不到。必须通过cursor.getLong()而不是cursor.getInt()来检索它。哈哈不能停止嘲笑自己。再次感谢。
Son Huy TRAN 2013年

37
  1. 本评论所假定,我将始终使用整数来存储日期。
  2. 对于存储,您可以使用实用程序方法

    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);
  3. 另一种实用程序方法负责加载

    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));
  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
        }

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


9

SQLite可以使用文本,实数或整数数据类型来存储日期。而且,无论何时执行查询,结果都将使用format来显示%Y-%m-%d %H:%M:%S

现在,如果您使用SQLite日期/时间函数插入/更新日期/时间值,则实际上也可以存储毫秒。如果是这样,结果将使用format显示%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

您可以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');

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

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

没有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驱动程序。可以使用提交所有SQL操作SQLiteOpenHelper

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

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

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

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

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

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


1
考虑到SQLite的内部存储引擎,我不相信您的示例具有您所暗示的效果:看起来该引擎“允许在任何列中存储任何存储类型的值,而与声明的SQL类型无关 ”(books.google。 de /…)。在我的实数,整数和文本示例中,发生的事情是这样的:SQLite只是将文本作为文本存储在所有树列中。因此,结果自然是好的,但存储仍然浪费。如果仅使用整数,则应减少毫秒数。只是说...
marco 2015年

实际上,您可以通过从test_table执行SELECT datetime(col3,'unixepoch')来确认我刚才所说的内容。这将为您的示例显示空行...除非出于测试目的,否则您插入实际的Integer。例如,如果要添加col3值为37的行,则上面的SELECT语句将显示:1970-01-01 00:00:37。因此,除非您实际上可以很好地将所有日期存储为低效率的文本字符串,否则请不要按照您的建议进行操作。
marco 2015年

自从我发布此答案以来已经有很长时间了……也许SQLite已更新。我唯一能想到的就是再次执行SQL语句和您的建议。
miguelt 2015年

3

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

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


3

存储dateSQlite DB中的最佳方法是存储当前文件DateTimeMilliseconds。以下是这样做的代码段

  1. 得到 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;
}
  1. 将数据插入数据库
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 .

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

  1. 将日期毫秒转换为日期字符串。
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());
}
  1. 现在,最后获取数据并查看其工作...
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不支持long数据类型。编辑:我的错误,INTEGER是8字节长,所以它应该支持此数据类型。
Antonio Vlasic '18年


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_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);
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.