在 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.Calendar
和 java.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
您可以使用col2
和col3
检查相同的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.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.
由于 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 中处理日期的最佳方式 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Sqlite ( android ) 中的 datetime 字段获取日期
如何在 Android 应用程序中插入日期时间设置为“现在”的 SQLite 记录?
Android日期SQLite SimpleDateFormat日历[重复]