关于sql:在Android SQLite中使用日期的最佳方式

Best way to work with dates in Android SQLite

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

  • 我应该使用哪种类型来在sqlite中存储日期(文本、整数…)
  • 给定存储日期的最佳方法,如何使用ContentValues正确存储日期?
  • 从sqlite数据库中检索日期的最佳方法是什么?
  • 如何在sqlite上进行SQL选择,并按日期对结果排序?

  • The best way is to store the dates as a number, received by using the Calendar command.

    1
    2
    3
    4
    5
    6
    7
    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_DATETIME+" int)");

    //AND inserting the DATA includes this:
    VALUES.put(COLUMN_DATETIME, System.currentTimeMillis());

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

    值得一提的是,从中得到的值应该是长的,而不是int。sqlite中的integer可以表示许多东西,从1到8字节不等,但对于几乎所有的日期,64位或长的是有效的。

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


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

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

    SQLite中检索作为字符串的日期,然后可以根据需要使用日历或android.text.format.DateUtils.formatDateTime方法将其格式化/转换为本地区域化格式。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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;
    }


  • 正如在本文中假定的,我总是使用整数来存储日期。
  • 对于存储,可以使用实用方法

    1
    2
    3
    4
    5
    6
    public static Long persistDate(DATE DATE) {
        IF (DATE != NULL) {
            RETURN DATE.getTime();
        }
        RETURN NULL;
    }

    就像这样:

    1
    2
    3
    ContentValues VALUES = NEW ContentValues();
    VALUES.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, NULL, VALUES);

  • 另一种实用方法负责装载

    1
    2
    3
    4
    5
    6
    public static DATE loadDate(Cursor cursor, INT INDEX) {
        IF (cursor.isNull(INDEX)) {
            RETURN NULL;
        }
        RETURN NEW DATE(cursor.getLong(INDEX));
    }

    可以这样使用:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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值。


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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

    现在,进行一些查询以验证我们是否能够实际比较时间:

    1
    2
    3
    4
    5
    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包含在内,因此…

    1
    2
    3
    4
    5
    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功能怎么办?

    1
    2
    3
    4
    5
    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函数而没有毫秒呢?

    1
    2
    3
    4
    5
    6
    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呢?

    1
    2
    3
    4
    5
    6
    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中的sqliteJDBC驱动程序v3.7.2-也许更新的版本会改变下面解释的行为…如果您在Android中开发,则不需要JDBC驱动程序。所有SQL操作都可以使用SQLiteOpenHelper提交。

    JDBC有不同的方法从数据库中获取实际日期/时间值:java.sql.Datejava.sql.Timejava.sql.Timestamp

    java.sql.ResultSet中的相关方法(显然)分别是getDate(..)getTime(..)getTimestamp()

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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没有实际的日期/时间/时间戳数据类型,所有这3个方法都返回值,就好像对象是用0初始化的:

    1
    2
    3
    NEW java.sql.Date(0)
    NEW java.sql.Time(0)
    NEW java.sql.Timestamp(0)

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

    总之,尽管总是使用正确的数据类型,或者甚至表示UNIX时间的整数(毫秒以来),但我发现使用默认SQLite格式(EDCOX1 17)或Java EDCOX1(18)更容易,而用SQLite函数使所有SQL语句复杂化。前一种方法更容易维护。

    TODO:在Android(API15或更高版本)中使用getDate/getTime/getTimeStamp时,我将检查结果…也许内部驱动程序不同于sqlite jdbc…


    通常(和我在mysql/postgres中做的一样),我将日期存储在int(mysql/post)或text(sqlite)中,以时间戳格式存储它们。

    然后我将它们转换成日期对象,并根据用户时区执行操作


    在sqlite db中存储date的最佳方法是存储当前的DateTimeMilliseconds。下面是要执行此操作的代码段_

  • Get the DateTimeMilliseconds
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
    }

  • Insert the data in your DB
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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();

    }

    江户十一〔21〕号

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

  • Convert date milliseconds in to date string.
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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());
    }

  • Now, Finally fetch the data and see its working...
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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;

    }

    希望这对大家都有帮助!:)


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //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);
    }


    就像斯特米说的。

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

    3

    1
    2
    Cursor cursor = db.query(TABLE_NAME, NEW String[] {"_id","title","title_raw","timestamp
    <hr>[cc lang="
    SQL"]"SELECT "+_ID+" , "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM"+TBL_NOTIFICATION+" ORDER BY"+"strftime(%s,"+_DATE_TIME+") DESC";