Лучший способ работы с датами в Android SQLite [закрыто]

У меня возникли проблемы с работой с датами в моем приложении Android, которое использует SQLite. У меня есть пара вопросов:

  1. какой тип следует использовать для хранения дат в SQLite (text, integer,...)?
  2. учитывая лучший способ хранения дат, как правильно хранить его с помощью ContentValues?
  3. каков наилучший способ получить дату из базы данных SQLite?
  4. как сделать выбор sql на SQLite, упорядочивая результаты по дата?

9 ответов


вы можете использовать текстовое поле для хранения дат в 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;
}

лучший способ-сохранить даты как число, полученное с помощью команды 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() чтобы правильно получить метку времени, если вы это сделаете.


  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 пункт заказа (потому что у нас есть цифровой столбец.) Ниже будет порядок убывания (то есть самая новая дата идет первой):

    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.


SQLite может использовать текстовые, реальные или целочисленные типы данных для хранения дат. Более того, при выполнении запроса результаты отображаются в формате %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...)

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 are (очевидно)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 не имеет фактическая дата / время / отметка времени тип данных все эти 3 метода возвращают значения, как если бы объекты были инициализированы с 0:

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

Итак, вопрос: как мы можем выбрать, вставить или обновить объекты даты/времени/метки времени? нет простого ответа. Вы можете попробовать разные комбинации, но они заставят вас встроить функции SQLite во все операторы SQL. Гораздо проще определить класс утилиты для преобразования текста в объекты даты внутри вашей 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...


обычно(так же, как и в mysql/postgres) я храню даты в int(mysql/post) или text (sqlite), чтобы сохранить их в формате метки времени.

затем я преобразую их в объекты даты и выполняю действия на основе часового пояса пользователя


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 () в SQLiteDatabase

4 - см. ответ 3


Я предпочитаю этот. Это не лучший способ, но быстрое решение.

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

"SELECT  "+_ID+" ,  "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";

лучший способ хранения dateна SQLite DB для хранения текущего DateTimeMilliseconds. Ниже приведен фрагмент кода для выполнения so_

  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. вставьте данные в свой DB
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 .

следующий шаг, когда вы хотите получить данные из БД, вам нужно преобразовать соответствующее время даты миллисекунды до соответствующей даты. Ниже приведен пример фрагмента кода для выполнения same_

  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;

}

надеюсь, это поможет всем! :)