Лучший способ работы с датами в Android SQLite
У меня возникли проблемы с работой с датами в приложении для Android, использующем SQLite.
У меня есть пара вопросов:
- Какой тип я должен использовать для хранения дат в SQLite (текст, целое число,...)?
- Учитывая лучший способ хранения дат, как его правильно сохранить с помощью ContentValues?
- Какой лучший способ получить дату из базы данных SQLite?
- Как сделать SQL-запрос на SQLite, заказывая результаты по дате?
Ответы
Ответ 1
Вы можете использовать текстовое поле для хранения дат в 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;
}
Ответ 2
Лучший способ - сохранить даты как число, полученное с помощью команды Calendar.
//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 может означать много вещей, что-то от 1 до 8 байт, но почти для всех дат работает 64 бит или длинный.
EDIT: Как было указано в комментариях, вы должны использовать cursor.getLong()
, чтобы правильно получить метку времени, если вы это сделаете.
Ответ 3
- Как предполагается в этом комментарии, я всегда буду использовать целые числа для хранения дат.
-
Для хранения можно использовать служебный метод
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 (потому что у нас есть числовой столбец). Ниже будет указано, что происходит спуск (это самая новая дата):
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
Вы можете проверить тот же 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...)
BTW: Я использую JDBC (не уверен в других языках)... драйвер sqlite-jdbc v3.7.2 от xerial - возможно, новые версии измените поведение, описанное ниже...
Если вы разрабатываете Android, вам не нужен драйвер jdbc. Все операции 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? Нет простого ответа.
Вы можете попробовать разные комбинации, но они заставят вас встроить функции SQLite во все SQL-запросы. Гораздо проще определить класс утилиты для преобразования текста в объекты Date внутри вашей Java-программы. Но всегда помните, что SQLite преобразует любое значение даты в UTC + 0000.
В целом, несмотря на общее правило всегда использовать правильный тип данных или даже целые числа, обозначающие время Unix (миллисекунды с эпохи), я нахожу намного проще с использованием стандартного SQLite-формата ('%Y-%m-%d %H:%M:%f'
или в Java 'yyyy-MM-dd HH:mm:ss.SSS'
), чтобы усложнить все ваши SQL-запросы функциями SQLite. Первый подход намного проще поддерживать.
TODO: Я проверю результаты при использовании getDate/getTime/getTimestamp внутри Android (API15 или лучше)... возможно, внутренний драйвер отличается от sqlite-jdbc...
Ответ 5
Обычно (так же, как в mysql/postgres) я храню даты в int (mysql/post) или text (sqlite), чтобы сохранить их в формате timestamp.
Затем я преобразую их в объекты Date и выполняю действия на основе пользователя TimeZone
Ответ 6
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);
см. здесь:
Запрос() в SQLiteDatabase
4 - см. ответ 3
Ответ 7
Я предпочитаю это. Это не лучший способ, но быстрое решение.
//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);
}
Ответ 8
"SELECT "+_ID+" , "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";
Ответ 9
Лучший способ сохранить date
в SQlite DB - сохранить текущие DateTimeMilliseconds
. Ниже приведен фрагмент кода, чтобы сделать это.
- Получить
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.
Следующим шагом, когда вы хотите получить данные из БД, необходимо преобразовать соответствующие миллисекунды времени в соответствующую дату. Ниже приведен пример фрагмента кода, чтобы сделать то же самое
- Преобразование даты в миллисекундах в строку даты.
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;
}
Надеюсь, это поможет всем! :)