Il modo migliore per lavorare con le date in Android SQLite [chiuso]


237

Ho dei problemi a lavorare con le date sulla mia applicazione Android che utilizza SQLite. Ho un paio di domande:

  1. Che tipo dovrei usare per memorizzare le date in SQLite (testo, numero intero, ...)?
  2. Dato il modo migliore per memorizzare le date come posso memorizzarlo correttamente usando ContentValues?
  3. Qual è il modo migliore per recuperare la data dal database SQLite?
  4. Come effettuare una selezione SQL su SQLite, ordinando i risultati per data?

2
Usa semplicemente la classe Calendar e il suo tempo membro (che rappresenta il numero di millisecondi trascorsi dall'1 / 1/1970). Esistono funzioni membro per la mutazione del valore temporale in stringhe leggibili dall'utente.
slayton,

Risposte:


43

È possibile utilizzare un campo di testo per memorizzare le date all'interno SQLite.

Memorizzando le date in formato UTC, l'impostazione predefinita se si utilizza datetime('now') (yyyy-MM-dd HH:mm:ss)consentirà quindi l'ordinamento per colonna di data.

Il recupero delle date come stringhe da SQLitete può quindi formattarle / convertirle come richiesto nei formati locali localizzati usando il Calendario o il android.text.format.DateUtils.formatDateTimemetodo.

Ecco un metodo di formattazione regionalizzato che uso;

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
Come gestiresti gli intervalli di date per le query?
Joe,

51
"Pratica raccomandata"? Non suona bene.
shim

135
Negli anni in cui ho usato SQL non avevo mai visto nessuno in precedenza consigliare di memorizzare le date come stringhe. Se non si dispone di un tipo di colonna di data specifico, utilizzare un numero intero e archiviarlo in tempo Unix (secondi dall'epoca). È ordinabile e utilizzabile nelle gamme e facilmente convertibile.
mikebabcock,

20
Memorizzare le date come stringa va bene se vuoi memorizzarle come "informazioni", qualcosa che recuperi e mostri. Ma se vuoi memorizzare le date come "dati", qualcosa con cui lavorare, dovresti considerare di memorizzarle come numeri interi - tempo dall'epoca. Ciò ti consentirà di eseguire query sugli intervalli di date, è standard, quindi non devi preoccuparti delle conversioni, ecc. Ecc. La memorizzazione delle date come stringa è molto limitante e mi piacerebbe davvero sapere chi ha raccomandato questa pratica come regola generale.
Krystian,

8
La documentazione di sqlite elenca l'archiviazione come testo (ISO 8601) come soluzione praticabile per l'archiviazione delle date. In realtà, è elencato per primo.
Anderspitman,

211

Il modo migliore è memorizzare le date come un numero, ricevute utilizzando il comando Calendario.

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

Perché farlo Prima di tutto, ottenere valori da un intervallo di date è facile. Converti la tua data in millisecondi e poi esegui una query in modo appropriato. L'ordinamento per data è altrettanto facile. Anche le chiamate per la conversione tra vari formati sono altrettanto facili, come ho incluso. In conclusione, con questo metodo, puoi fare tutto ciò che devi fare, senza problemi. Sarà leggermente difficile leggere un valore grezzo, ma più che compensa quel leggero svantaggio di essere facilmente leggibile e utilizzabile dalla macchina. E infatti, è relativamente facile costruire un lettore (e so che ce ne sono alcuni là fuori) che convertirà automaticamente il time tag fino ad oggi in quanto tale per una facile lettura.

Vale la pena ricordare che i valori che ne risultano dovrebbero essere lunghi, non int. Il numero intero in sqlite può significare molte cose, qualsiasi cosa compreso tra 1 e 8 byte, ma per quasi tutte le date 64 bit, o un lungo, è ciò che funziona.

EDIT: Come è stato sottolineato nei commenti, è necessario utilizzare il cursor.getLong()per ottenere correttamente il timestamp se si esegue questa operazione.


17
Grazie ragazzo. Lol Ho pensato a un errore di battitura ma non sono riuscito a trovarlo. Deve essere recuperato da cursor.getLong (), non da cursor.getInt (). Lol non riesce a smettere di ridere di me stesso. Grazie ancora.
Son Huy TRAN,

37
  1. Come presunto in questo commento , userei sempre numeri interi per memorizzare le date.
  2. Per la memorizzazione, è possibile utilizzare un metodo di utilità

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

    così:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
    
  3. Un altro metodo di utilità si occupa del caricamento

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

    può essere usato in questo modo:

    entity.setDate(loadDate(cursor, INDEX));
  4. Ordinare per data è una semplice clausola SQL ORDER (perché abbiamo una colonna numerica). Quanto segue ordinerà in ordine decrescente (la data più recente inizia per prima):

    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
        }
    

Assicurati sempre di memorizzare l' ora UTC / GMT , in particolare quando lavori java.util.Calendare java.text.SimpleDateFormatutilizzi il fuso orario predefinito (cioè il tuo dispositivo). java.util.Date.Date()è sicuro da usare in quanto crea un valore UTC.


9

SQLite può utilizzare tipi di dati di testo, reali o interi per memorizzare le date. Inoltre, ogni volta che si esegue una query, i risultati vengono visualizzati utilizzando il formato %Y-%m-%d %H:%M:%S.

Ora, se inserisci / aggiorni i valori di data / ora utilizzando le funzioni di data / ora di SQLite, puoi effettivamente memorizzare anche i millisecondi. In tal caso, i risultati vengono visualizzati utilizzando il formato %Y-%m-%d %H:%M:%f. Per esempio:

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

Ora, facendo alcune domande per verificare se siamo effettivamente in grado di confrontare i tempi:

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

Puoi controllare lo stesso SELECTusando col2e col3e otterrai gli stessi risultati. Come puoi vedere, la seconda riga (126 millisecondi) non viene restituita.

Nota che BETWEENè inclusivo, quindi ...

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

... restituirà lo stesso set.

Prova a giocare con diversi intervalli di data / ora e tutto si comporterà come previsto.

Che dire senza strftimefunzione?

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

Che dire senza strftimefunzione e senza millisecondi?

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

Che dire 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

Funziona bene.

Infine, quando si ha a che fare con operazioni reali all'interno di un programma (senza usare l'eseguibile sqlite ...)

A proposito: sto usando JDBC (non sono sicuro di altre lingue) ... il driver sqlite-jdbc v3.7.2 da xerial - forse le revisioni più recenti cambiano il comportamento spiegato di seguito ... Se stai sviluppando in Android, non lo fai serve un driver jdbc. Tutte le operazioni SQL possono essere inviate utilizzando il SQLiteOpenHelper.

JDBC ha metodi diversi per ottenere i valori reali di data / ora da un database: java.sql.Date, java.sql.Time, e java.sql.Timestamp.

I metodi correlati a java.sql.ResultSetsono (ovviamente) getDate(..), getTime(..)e getTimestamp()rispettivamente.

Per esempio:

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.

Poiché SQLite non ha un tipo di dati DATE / TIME / TIMESTAMP effettivo, tutti questi 3 metodi restituiscono valori come se gli oggetti fossero inizializzati con 0:

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

Quindi, la domanda è: come possiamo effettivamente selezionare, inserire o aggiornare gli oggetti Data / Ora / Timestamp? Non c'è una risposta facile. Puoi provare diverse combinazioni, ma ti costringeranno a incorporare le funzioni SQLite in tutte le istruzioni SQL. È molto più semplice definire una classe di utilità per trasformare il testo in oggetti Date all'interno del programma Java. Ma ricorda sempre che SQLite trasforma qualsiasi valore di data in UTC + 0000.

In sintesi, nonostante la regola generale di utilizzare sempre il tipo di dati corretto, o anche numeri interi che indicano il tempo Unix (millisecondi dall'epoca), trovo molto più facile utilizzare il formato SQLite predefinito ( '%Y-%m-%d %H:%M:%f'o in Java 'yyyy-MM-dd HH:mm:ss.SSS') piuttosto che complicare tutte le tue istruzioni SQL con Funzioni SQLite. Il primo approccio è molto più semplice da mantenere.

TODO: Controllerò i risultati quando uso getDate / getTime / getTimestamp all'interno di Android (API15 o superiore) ... forse il driver interno è diverso da sqlite-jdbc ...


1
Dato il motore di archiviazione interno di SQLite, non sono convinto che i tuoi esempi abbiano l'effetto che implica: sembra che il motore "consenta di archiviare qualsiasi valore tipizzato in qualsiasi colonna indipendentemente dal tipo SQL dichiarato" ( books.google. de / ... ). Mi sembra che nel tuo esempio Real vs. Integer vs. Text, ciò che sta accadendo è questo: SQLite memorizza il testo come Testo in tutte le colonne dell'albero. Quindi, naturalmente, i risultati sono tutti buoni, la memoria è ancora sprecata. Se è stato utilizzato solo un numero intero, allora dovresti perdere millisecondi. Sto solo dicendo ...
marco,

In effetti, puoi confermare ciò che ho appena detto facendo un SELECT datetime (col3, 'unixepoch') FROM test_table. Questo mostrerà righe vuote per i tuoi esempi ... a meno che, per amor di prova, inserisci un intero effettivo. Ad esempio, se si dovesse aggiungere una riga con valore col3 37, l'istruzione SELECT sopra mostrerà: 1970-01-01 00:00:37. Quindi, a meno che tu non stia davvero bene con la memorizzazione di tutte le tue date piuttosto in modo inefficiente come stringa di testo, non fare come suggerisci.
marco,

È passato molto tempo da quando ho pubblicato questa risposta ... forse SQLite è stato aggiornato. L'unica cosa che mi viene in mente è di eseguire nuovamente le istruzioni SQL rispetto ai tuoi suggerimenti.
miguelt,

3

Di solito (come faccio in mysql / postgres) memorizzo le date in int (mysql / post) o in testo (sqlite) per memorizzarle nel formato timestamp.

Quindi li convertirò in oggetti Date ed eseguirò azioni in base all'utente TimeZone


3

Il modo migliore per archiviare datein SQlite DB è archiviare l'attuale DateTimeMilliseconds. Di seguito è riportato lo snippet di codice per farlo_

  1. Ottenere l 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. Inserisci i dati nel tuo 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 .

Il passo successivo è, quando si desidera recuperare i dati dal DB, è necessario convertire la rispettiva data e ora millisecondi nella data corrispondente. Di seguito è riportato lo snippet di codice di esempio per fare lo stesso_

  1. Converti i millisecondi della data in stringa della data.
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. Ora, infine, recupera i dati e vedi come funziona ...
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;

}

Spero che questo possa aiutare tutti! :)


SQLite non supporta il tipo di dati lungo. EDIT: errore mio, INTEGER è lungo 8 byte, quindi dovrebbe supportare questo tipo di dati.
Antonio Vlasic,


1

Preferisco questo. Questo non è il modo migliore, ma una soluzione rapida.

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

0
"SELECT  "+_ID+" ,  "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.