Найкращий спосіб роботи з датами в Android SQLite [закрито]


237

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

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

2
Просто використовуйте клас Calendar та час його учасника (який представляє кількість мілісекунд, що минули з 1.01.1970). Існують функції членів для вимкнення значення часу в читані користувачем рядки.
слайтон

Відповіді:


43

Ви можете використовувати текстове поле для зберігання дат 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;
}

63
Як би ви обробляли діапазони дат запитів?
Джо

51
"Рекомендована практика"? Не звучить правильно.
шим

135
За роки, якими я користувався SQL, я ніколи не бачив, щоб хтось раніше рекомендував зберігати дати як рядки. Якщо у вас немає конкретного типу стовпця дати, використовуйте ціле число та зберігайте у часі Unix (секунди з епохи). Її можна сортувати та використовувати в діапазонах і легко конвертувати.
mikebabcock

20
Збереження дат у вигляді рядка добре, якщо ви хочете зберігати його як "інформацію", щось, що ви отримуєте та показуєте. Але якщо ви хочете зберігати дати як "дані", з чим працювати, вам слід розглянути можливість їх зберігання як ціле - час з епохи. Це дозволить вам запитувати діапазони дат, це стандартно, тому вам не потрібно турбуватися про конверсії тощо. Зберігання дат як рядка дуже обмежує, і я б дуже хотів знати, хто рекомендував цю практику як загальне правило.
Крістіян

8
Документація sqlite перераховує збереження тексту (ISO 8601) як життєздатне рішення для зберігання дат. Власне, вона занесена першою.
anderspitman

211

Найкращий спосіб - збереження дат як числа, отриманих за допомогою команди Календар.

//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 біти або довгі - це те, що працює.

РЕДАКТУВАННЯ: Як було зазначено в коментарях, cursor.getLong()якщо ви це зробите , вам потрібно скористатись, щоб правильно отримати позначку часу.


17
Спасибі хлопець. Lol Я подумав про неправильне введення тексту, але не зміг його знайти. Він повинен бути отриманий курсором.getLong (), а не cursor.getInt (). Лол не може перестати сміятися над собою. Знову дякую.
Син Хуй ТРО

37
  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 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.


9

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)

Отже, питання полягає в тому, як ми можемо насправді вибирати, вставляти або оновлювати об’єкти Дата / Час / Часові позначки? Немає простої відповіді. Ви можете спробувати різні комбінації, але вони змусять вас вставляти функції 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 ...


1
З огляду на внутрішню систему зберігання SQLite, я не переконаний, що ваші приклади мають такий ефект, який ви маєте на увазі: схоже, двигун "дозволяє зберігати будь-які значення, набрані сховищем, у будь-якому стовпчику незалежно від заявленого типу SQL" ( books.google. де /… ). Мені здається, що у прикладі «Реал проти цілого» та «Тексту» відбувається таке: SQLite просто зберігає текст у вигляді тексту у всіх стовпцях дерев. Таким чином, природно, результати хороші, зберігання все-таки витрачено. Якщо було використано лише цілий ряд, то вам слід втратити мілісекунди. Просто кажу ...
Марко

Насправді ви можете підтвердити те, що я щойно сказав, виконавши SELECT datetime (col3, 'unixepoch') FROM test_table. Це покаже порожні рядки для ваших прикладів ... якщо тільки для тесту ви не вставите фактичний цілий ряд. Наприклад, якщо ви повинні додати рядок зі значенням col3 37, вищевикладений оператор SELECT покаже: 1970-01-01 00:00:37. Тому, якщо ви насправді не чудово зберігаєте всі дати, неефективно, як текстовий рядок, не робіть так, як ви пропонуєте.
Марко

Вже давно я опублікував цю відповідь ... можливо SQLite було оновлено. Єдине, про що я можу придумати, - це повторити виконання операторів SQL проти ваших пропозицій.
miguelt

3

Зазвичай (як і в mysql / postgres) я зберігаю дати в int (mysql / post) або в тексті (sqlite), щоб зберігати їх у форматі часових позначок.

Тоді я перетворять їх у об’єкти дати та виконувати дії на основі користувача TimeZone


3

Найкращий спосіб зберігання dateв SQlite DB - це зберігання струму DateTimeMilliseconds. Нижче наведено фрагмент коду, щоб зробити так_

  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 .

Наступним кроком є ​​те, що ви хочете отримати дані з БД, вам потрібно перетворити відповідний час у мілісекундах у відповідну дату. Нижче наведено фрагмент зразкового коду, щоб зробити те саме_

  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 не підтримує довгий тип даних. EDIT: Моя помилка, INTEGER має 8 байт, тому він повинен підтримувати цей тип даних.
Антоніо Власіч


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.