Cara terbaik untuk bekerja dengan tanggal di Android SQLite [ditutup]

237

Saya mengalami masalah dalam menangani tanggal pada aplikasi Android yang menggunakan SQLite. Saya punya beberapa pertanyaan:

  1. Jenis apa yang harus saya gunakan untuk menyimpan tanggal dalam SQLite (teks, integer, ...)?
  2. Diberi cara terbaik untuk menyimpan tanggal, bagaimana cara menyimpannya dengan benar menggunakan ContentValues?
  3. Apa cara terbaik untuk mengambil tanggal dari database SQLite?
  4. Bagaimana cara memilih sql pada SQLite, memesan hasilnya berdasarkan tanggal?
Filipe
sumber
2
Cukup gunakan kelas Kalender dan waktu anggotanya (yang mewakili jumlah milidetik yang telah berlalu sejak 1/1/1970). Ada fungsi anggota untuk mengubah nilai waktu menjadi string yang dapat dibaca pengguna.
slayton

Jawaban:

43

Anda dapat menggunakan bidang teks untuk menyimpan tanggal dalam SQLite.

Menyimpan tanggal dalam format UTC, defaultnya jika Anda gunakan datetime('now') (yyyy-MM-dd HH:mm:ss)kemudian akan mengizinkan pengurutan berdasarkan kolom tanggal.

Mengambil tanggal sebagai string dari SQLiteAnda kemudian dapat memformat / mengonversinya sesuai kebutuhan ke dalam format regionalisasi lokal menggunakan Kalender atau android.text.format.DateUtils.formatDateTimemetode.

Berikut adalah metode formatter regional yang saya gunakan;

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;
}
CodeChimp
sumber
63
Bagaimana Anda menangani rentang tanggal permintaan?
Joe
51
"Latihan yang disarankan"? Kedengarannya tidak benar.
shim
135
Pada tahun-tahun saya telah menggunakan SQL, saya belum pernah melihat orang yang sebelumnya merekomendasikan menyimpan tanggal sebagai string. Jika Anda tidak memiliki tipe kolom tanggal tertentu, gunakan bilangan bulat dan simpan dalam waktu Unix (detik sejak zaman). Diurutkan dan digunakan dalam rentang dan mudah dikonversi.
mikebabcock
20
Menyimpan tanggal sebagai string tidak masalah jika Anda ingin menyimpannya sebagai "informasi", sesuatu yang Anda ambil dan tunjukkan. Tetapi jika Anda ingin menyimpan tanggal sebagai "data", sesuatu untuk dikerjakan, Anda harus mempertimbangkan menyimpannya sebagai integer - waktu sejak zaman. Ini akan memungkinkan Anda untuk menanyakan rentang tanggal, itu standar sehingga Anda tidak perlu khawatir tentang konversi dll. Menyimpan tanggal sebagai string sangat terbatas dan saya benar-benar ingin tahu siapa yang merekomendasikan praktik ini sebagai aturan umum.
Krystian
8
The sqlite dokumentasi daftar menyimpan sebagai teks (ISO 8601) sebagai solusi yang layak untuk menyimpan tanggal. Sebenarnya, ini terdaftar lebih dulu.
anderspitman
211

Cara terbaik adalah menyimpan tanggal sebagai angka, diterima dengan menggunakan perintah Kalender.

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

Kenapa melakukan ini? Pertama-tama, mendapatkan nilai dari rentang tanggal itu mudah. Konversikan tanggal Anda menjadi milidetik, lalu kueri dengan tepat. Menyortir berdasarkan tanggal juga mudah. Panggilan untuk mengonversi berbagai format juga mudah, seperti yang saya sertakan. Intinya adalah, dengan metode ini, Anda dapat melakukan apa pun yang perlu Anda lakukan, tidak ada masalah. Akan sedikit sulit untuk membaca nilai mentah, tetapi lebih dari sekadar membuat sedikit kerugian dengan mudah dibaca dan digunakan mesin. Dan pada kenyataannya, relatif mudah untuk membangun pembaca (Dan saya tahu ada beberapa di luar sana) yang secara otomatis akan mengubah tanda waktu menjadi tanggal sehingga mudah dibaca.

Perlu disebutkan bahwa nilai-nilai yang keluar dari ini harus panjang, bukan int. Integer dalam sqlite dapat berarti banyak hal, mulai dari 1-8 byte, tetapi untuk hampir semua tanggal 64 bit, atau panjang, itulah yang berhasil.

EDIT: Seperti yang telah ditunjukkan dalam komentar, Anda harus menggunakan cursor.getLong()untuk mendapatkan cap waktu dengan benar jika Anda melakukan ini.

PearsonArtPhoto
sumber
17
Terimakasih teman. Lol saya pikir salah ketik tapi saya tidak bisa menemukannya. Itu harus diambil oleh cursor.getLong (), bukan oleh cursor.getInt (). Lol tidak bisa berhenti menertawakan diriku sendiri. Terima kasih lagi.
Son Huy TRAN
37
  1. Seperti yang diduga dalam komentar ini , saya selalu menggunakan bilangan bulat untuk menyimpan tanggal.
  2. Untuk menyimpan, Anda bisa menggunakan metode utilitas

    public static Long persistDate(Date date) {
        if (date != null) {
            return date.getTime();
        }
        return null;
    }

    seperti itu:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
  3. Metode utilitas lain menangani pemuatan

    public static Date loadDate(Cursor cursor, int index) {
        if (cursor.isNull(index)) {
            return null;
        }
        return new Date(cursor.getLong(index));
    }

    dapat digunakan seperti ini:

    entity.setDate(loadDate(cursor, INDEX));
  4. Pemesanan berdasarkan tanggal adalah klausa SQL ORDER sederhana (karena kami memiliki kolom numerik). Berikut ini akan memesan menurun (yaitu tanggal terbaru lebih dulu):

    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
        }

Selalu pastikan untuk menyimpan waktu UTC / GMT , terutama ketika bekerja dengan java.util.Calendardan java.text.SimpleDateFormatyang menggunakan zona waktu default (yaitu perangkat Anda). java.util.Date.Date()aman digunakan karena menciptakan nilai UTC.

pemalas
sumber
9

SQLite dapat menggunakan tipe data teks, nyata, atau bilangan bulat untuk menyimpan tanggal. Terlebih lagi, setiap kali Anda melakukan kueri, hasilnya ditampilkan menggunakan format %Y-%m-%d %H:%M:%S.

Sekarang, jika Anda memasukkan / memperbarui nilai tanggal / waktu menggunakan fungsi tanggal / waktu SQLite, Anda sebenarnya dapat menyimpan milidetik juga. Jika itu masalahnya, hasilnya ditampilkan menggunakan format %Y-%m-%d %H:%M:%f. Sebagai contoh:

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

Sekarang, lakukan beberapa pertanyaan untuk memverifikasi apakah kami benar-benar dapat membandingkan waktu:

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

Anda dapat memeriksa SELECTmenggunakan yang sama col2dan col3dan Anda akan mendapatkan hasil yang sama. Seperti yang Anda lihat, baris kedua (126 milidetik) tidak dikembalikan.

Perhatikan bahwa BETWEENitu inklusif, oleh karena itu ...

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');

... akan mengembalikan set yang sama.

Cobalah bermain-main dengan rentang tanggal / waktu yang berbeda dan semuanya akan berperilaku seperti yang diharapkan.

Bagaimana tanpa strftimefungsi?

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

Bagaimana dengan tanpa strftimefungsi dan tanpa milidetik?

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

Bagaimana dengan 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

Bekerja dengan baik.

Akhirnya, ketika berhadapan dengan operasi aktual dalam suatu program (tanpa menggunakan sqlite yang dapat dieksekusi ...)

BTW: Saya menggunakan JDBC (tidak yakin tentang bahasa lain) ... driver sqlite-jdbc v3.7.2 dari xerial - mungkin revisi yang lebih baru mengubah perilaku yang dijelaskan di bawah ini ... Jika Anda mengembangkan di Android, Anda tidak membutuhkan driver jdbc. Semua operasi SQL dapat dikirimkan menggunakan SQLiteOpenHelper.

JDBC memiliki metode yang berbeda untuk mendapatkan nilai tanggal / waktu yang sebenarnya dari database: java.sql.Date, java.sql.Time, dan java.sql.Timestamp.

Metode terkait di java.sql.ResultSetyang (jelas) getDate(..), getTime(..)dan getTimestamp()masing-masing.

Sebagai contoh:

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.

Karena SQLite tidak memiliki tipe data DATE / TIME / TIMESTAMP yang sebenarnya, ketiga metode ini mengembalikan nilai seolah-olah objek diinisialisasi dengan 0:

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

Jadi, pertanyaannya adalah: bagaimana kita dapat benar-benar memilih, menyisipkan, atau memperbarui objek Tanggal / Waktu / Stempel Waktu? Tidak ada jawaban yang mudah. Anda dapat mencoba kombinasi yang berbeda, tetapi mereka akan memaksa Anda untuk menanamkan fungsi SQLite di semua pernyataan SQL. Jauh lebih mudah untuk mendefinisikan kelas utilitas untuk mengubah teks menjadi objek Tanggal di dalam program Java Anda. Tetapi selalu ingat bahwa SQLite mengubah nilai tanggal apa pun menjadi UTC + 0000.

Singkatnya, meskipun aturan umum untuk selalu menggunakan tipe data yang benar, atau, bahkan bilangan bulat yang menunjukkan waktu Unix (milidetik sejak zaman), saya menemukan jauh lebih mudah menggunakan format SQLite default ( '%Y-%m-%d %H:%M:%f'atau di Jawa 'yyyy-MM-dd HH:mm:ss.SSS') daripada mempersulit semua pernyataan SQL Anda dengan Fungsi SQLite. Pendekatan sebelumnya jauh lebih mudah untuk dipertahankan.

TODO: Saya akan memeriksa hasilnya ketika menggunakan getDate / getTime / getTimestamp di dalam Android (API15 atau lebih baik) ... mungkin driver internal berbeda dari sqlite-jdbc ...

miguelt
sumber
1
Mengingat mesin penyimpanan internal SQLite, saya tidak yakin bahwa contoh Anda memiliki efek yang Anda maksudkan: Sepertinya mesin "memungkinkan untuk menyimpan nilai-nilai yang diketik dalam penyimpanan apa pun di kolom apa pun terlepas dari tipe SQL yang dinyatakan" ( books.google. de / ... ). Kedengarannya bagi saya bahwa dalam contoh Real vs Integer vs Text, apa yang terjadi adalah ini: SQLite hanya menyimpan teks sebagai Teks di semua kolom pohon. Jadi, tentu saja hasilnya bagus, penyimpanan masih terbuang sia-sia. Jika hanya Integer yang digunakan, maka Anda harus kehilangan milidetik. Hanya mengatakan ...
marco
Bahkan, Anda dapat mengkonfirmasi apa yang baru saja saya katakan dengan melakukan SELECT datetime (col3, 'unixepoch') DARI test_table. Ini akan menampilkan baris kosong untuk contoh Anda ... kecuali, demi pengujian, Anda memasukkan Integer yang sebenarnya. Misalnya, jika Anda menambahkan baris dengan nilai col3 37, pernyataan SELECT di atas akan menunjukkan: 1970-01-01 00:00:37. Jadi, kecuali Anda benar-benar baik-baik saja dengan menyimpan semua tanggal Anda agak tidak efisien sebagai string teks, jangan lakukan seperti yang Anda sarankan.
marco
Sudah lama sejak saya memposting jawaban ini ... mungkin SQLite telah diperbarui. Satu-satunya hal yang dapat saya pikirkan adalah menjalankan pernyataan SQL lagi vs saran Anda.
miguelt
3

Biasanya (sama seperti yang saya lakukan di mysql / postgres) Saya menyimpan tanggal di int (mysql / post) atau teks (sqlite) untuk menyimpannya dalam format timestamp.

Lalu saya akan mengonversinya menjadi objek Tanggal dan melakukan tindakan berdasarkan pengguna TimeZone

StErMi
sumber
3

Cara terbaik untuk menyimpan datedi SQlite DB adalah menyimpan saat ini DateTimeMilliseconds. Di bawah ini adalah cuplikan kode untuk melakukannya_

  1. Ambil 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. Masukkan data dalam DB Anda
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 .

Langkah selanjutnya adalah, ketika Anda ingin mengambil data dari DB Anda perlu mengkonversi milidetik waktu tanggal masing-masing ke tanggal yang sesuai. Di bawah ini adalah cuplikan kode sampel untuk melakukan hal yang sama_

  1. Konversi milidetik tanggal menjadi string tanggal.
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. Sekarang, Terakhir ambil datanya dan lihat ...
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;

}

Semoga ini bisa membantu semua! :)

Rupesh Yadav
sumber
SQLite tidak mendukung tipe data yang panjang. EDIT: Kesalahan saya, INTEGER adalah 8 byte, jadi harus mendukung tipe data ini.
Antonio Vlasic
1

Saya lebih memilih ini. Ini bukan cara terbaik, tetapi solusi cepat.

//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);
}
Lidox
sumber
0
"SELECT  "+_ID+" ,  "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";
Suresh Mewara
sumber