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

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

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

【问题讨论】:

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

标签: android sql database sqlite date


【解决方案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】:
  1. 假设in this comment,我总是使用整数来存储日期。
  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 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_

      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 .

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

      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 不支持长数据类型。编辑:我的错误,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";
          

          【讨论】:

            猜你喜欢
            • 2018-04-14
            • 1970-01-01
            • 1970-01-01
            • 2018-03-16
            • 2015-01-07
            • 1970-01-01
            • 1970-01-01
            • 2012-08-10
            • 1970-01-01
            相关资源
            最近更新 更多