Meilleure façon de travailler avec des dates dans Android SQLite [fermé]

237

J'ai du mal à travailler avec les dates sur mon application Android qui utilise SQLite. J'ai quelques questions:

  1. Quel type dois-je utiliser pour stocker des dates dans SQLite (texte, entier, ...)?
  2. Étant donné la meilleure façon de stocker des dates, comment les stocker correctement à l'aide de ContentValues?
  3. Quelle est la meilleure façon de récupérer la date de la base de données SQLite?
  4. Comment faire une sélection SQL sur SQLite, en classant les résultats par date?
Filipe
la source
2
Utilisez simplement la classe Calendar et son temps membre (qui représente le nombre de millisecondes écoulées depuis le 1/1/1970). Il existe des fonctions membres pour muter la valeur de temps en chaînes lisibles par l'utilisateur.
slayton

Réponses:

43

Vous pouvez utiliser un champ de texte pour stocker des dates dans SQLite.

Le stockage des dates au format UTC, la valeur par défaut si vous utilisez, datetime('now') (yyyy-MM-dd HH:mm:ss)permettra alors le tri par colonne de date.

La récupération de dates sous forme de chaînes SQLitepeut ensuite être formatée / convertie selon les besoins en formats régionaux régionalisés à l'aide du calendrier ou de la android.text.format.DateUtils.formatDateTimeméthode.

Voici une méthode de formatage régionalisé que j'utilise;

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
la source
63
Comment géreriez-vous l'interrogation des plages de dates?
Joe
51
"Pratique recommandée"? Ça ne sonne pas bien.
cale
135
Depuis des années que j'utilise SQL, je n'ai jamais vu personne recommander de stocker des dates sous forme de chaînes. Si vous n'avez pas de type de colonne de date spécifique, utilisez un entier et stockez-le en temps Unix (secondes depuis l'époque). Son tri et utilisable dans des gammes et facilement convertis.
mikebabcock
20
Le stockage des dates sous forme de chaîne est très bien si vous souhaitez le stocker en tant qu '"information", quelque chose que vous récupérez et affichez. Mais si vous voulez stocker des dates en tant que "données", quelque chose avec quoi travailler, vous devriez envisager de les stocker en tant que temps entier depuis l'époque. Cela vous permettra d'interroger des plages de dates, c'est standard, vous n'avez donc pas à vous soucier des conversions, etc.
Krystian
8
La documentation sqlite répertorie le stockage sous forme de texte (ISO 8601) comme une solution viable pour le stockage des dates. En fait, il est répertorié en premier.
anderspitman
211

La meilleure façon est de stocker les dates sous forme de nombre, reçues à l'aide de la commande Calendrier.

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

Pourquoi faire ceci? Tout d'abord, il est facile d'obtenir des valeurs à partir d'une plage de dates. Convertissez simplement votre date en millisecondes, puis interrogez de manière appropriée. Le tri par date est également facile. Les appels à convertir entre différents formats sont également faciles, comme je l'ai inclus. En bout de ligne, avec cette méthode, vous pouvez faire tout ce que vous devez faire, sans problème. Il sera légèrement difficile de lire une valeur brute, mais cela compense largement ce léger inconvénient d'être facilement lisible et utilisable par la machine. Et en fait, il est relativement facile de construire un lecteur (et je sais qu'il y en a) qui convertira automatiquement la date et l'heure en tant que telle pour en faciliter la lecture.

Il convient de mentionner que les valeurs qui en découlent devraient être longues, pas int. Un entier dans sqlite peut signifier beaucoup de choses, allant de 1 à 8 octets, mais pour presque toutes les dates, 64 bits ou plus, c'est ce qui fonctionne.

EDIT: Comme cela a été souligné dans les commentaires, vous devez utiliser le cursor.getLong()pour obtenir correctement l'horodatage si vous faites cela.

PearsonArtPhoto
la source
17
Merci mec. Lol j'ai pensé à une faute de frappe mais je ne l'ai pas trouvé. Il doit être récupéré par cursor.getLong (), et non par cursor.getInt (). Lol ne peut pas arrêter de se moquer de moi. Merci encore.
Son Huy TRAN
37
  1. Comme présumé dans ce commentaire , j'utiliserais toujours des entiers pour stocker les dates.
  2. Pour le stockage, vous pouvez utiliser une méthode utilitaire

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

    ainsi:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
    
  3. Une autre méthode utilitaire prend en charge le chargement

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

    peut être utilisé comme ceci:

    entity.setDate(loadDate(cursor, INDEX));
  4. La commande par date est une simple clause SQL ORDER (car nous avons une colonne numérique). L'ordre suivant décroît (c'est la date la plus récente qui commence en premier):

    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
        }
    

Assurez-vous toujours de stocker l' heure UTC / GMT , en particulier lorsque vous travaillez avec java.util.Calendaret java.text.SimpleDateFormatqui utilise le fuseau horaire par défaut (c'est-à-dire votre appareil). java.util.Date.Date()est sûr à utiliser car il crée une valeur UTC.

schnatterer
la source
9

SQLite peut utiliser des types de données texte, réels ou entiers pour stocker des dates. De plus, chaque fois que vous effectuez une requête, les résultats sont affichés en utilisant le format %Y-%m-%d %H:%M:%S.

Maintenant, si vous insérez / mettez à jour des valeurs de date / heure à l'aide des fonctions de date / heure SQLite, vous pouvez également stocker des millisecondes. Si tel est le cas, les résultats sont affichés en utilisant le format %Y-%m-%d %H:%M:%f. Par exemple:

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

Maintenant, faire quelques requêtes pour vérifier si nous sommes réellement en mesure de comparer les temps:

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

Vous pouvez vérifier la même en SELECTutilisant col2et col3vous obtiendrez les mêmes résultats. Comme vous pouvez le voir, la deuxième ligne (126 millisecondes) n'est pas renvoyée.

Notez que BETWEENc'est inclusif, donc ...

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

... renverra le même ensemble.

Essayez de jouer avec différentes plages de date / heure et tout se comportera comme prévu.

Et sans strftimefonction?

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

Qu'en est-il sans strftimefonction et sans millisecondes?

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

Et alors 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

Fonctionne très bien.

Enfin, lorsqu'il s'agit d'opérations réelles au sein d'un programme (sans utiliser l'exécutable sqlite ...)

BTW: J'utilise JDBC (je ne suis pas sûr des autres langues) ... le pilote sqlite-jdbc v3.7.2 de xerial - peut-être que des révisions plus récentes modifient le comportement expliqué ci-dessous ... Si vous développez sous Android, vous ne le faites pas besoin d'un pilote jdbc. Toutes les opérations SQL peuvent être soumises à l'aide du SQLiteOpenHelper.

JDBC a différentes méthodes pour obtenir des valeurs de date / heure réelle à partir d' une base de données: java.sql.Date, java.sql.Timeet java.sql.Timestamp.

Les méthodes connexes java.sql.ResultSetsont (évidemment) getDate(..), getTime(..)et getTimestamp()respectivement.

Par exemple:

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.

Étant donné que SQLite n'a pas de type de données DATE / TIME / TIMESTAMP réel, toutes ces 3 méthodes retournent des valeurs comme si les objets étaient initialisés avec 0:

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

Donc, la question est: comment pouvons-nous réellement sélectionner, insérer ou mettre à jour des objets Date / Heure / Horodatage? Il n'y a pas de réponse facile. Vous pouvez essayer différentes combinaisons, mais elles vous forceront à intégrer des fonctions SQLite dans toutes les instructions SQL. Il est beaucoup plus facile de définir une classe utilitaire pour transformer du texte en objets Date dans votre programme Java. Mais rappelez-vous toujours que SQLite transforme toute valeur de date en UTC + 0000.

En résumé, malgré la règle générale de toujours utiliser le type de données correct, ou même des entiers indiquant le temps Unix (millisecondes depuis l'époque), je trouve beaucoup plus facile d'utiliser le format SQLite par défaut ( '%Y-%m-%d %H:%M:%f'ou en Java 'yyyy-MM-dd HH:mm:ss.SSS') plutôt que de compliquer toutes vos instructions SQL avec Fonctions SQLite. L'ancienne approche est beaucoup plus facile à maintenir.

TODO: Je vérifierai les résultats lors de l'utilisation de getDate / getTime / getTimestamp dans Android (API15 ou mieux) ... peut-être que le pilote interne est différent de sqlite-jdbc ...

miguelt
la source
1
Étant donné le moteur de stockage interne de SQLite, je ne suis pas convaincu que vos exemples aient l'effet que vous impliquez: il semble que le moteur "permette de stocker toutes les valeurs de type stockage dans n'importe quelle colonne, quel que soit le type SQL déclaré" ( books.google. de /… ). Il me semble que dans votre exemple Real vs Integer vs. Text, ce qui se passe est le suivant: SQLite stocke simplement le texte sous forme de texte dans toutes les colonnes de l'arborescence. Donc, naturellement, les résultats sont tous bons, le stockage encore perdu. Si seulement un entier a été utilisé, vous devriez perdre des millisecondes. Je dis juste ...
marco
En fait, vous pouvez confirmer ce que je viens de dire en faisant un SELECT datetime (col3, 'unixepoch') FROM test_table. Cela affichera des lignes vides pour vos exemples ... sauf si, pour le test, vous insérez un entier réel. Par exemple, si vous deviez ajouter une ligne avec la valeur col3 37, l'instruction SELECT ci-dessus affichera: 1970-01-01 00:00:37. Donc, à moins que vous ne soyez vraiment d'accord pour stocker toutes vos dates de manière assez inefficace sous forme de chaîne de texte, ne faites pas ce que vous suggérez.
marco
Cela fait longtemps que je n'ai pas posté cette réponse ... peut-être que SQLite a été mis à jour. La seule chose à laquelle je peux penser est d'exécuter à nouveau les instructions SQL par rapport à vos suggestions.
miguelt
3

Habituellement (comme je le fais dans mysql / postgres), je stocke les dates dans int (mysql / post) ou texte (sqlite) pour les stocker au format d'horodatage.

Ensuite, je vais les convertir en objets Date et effectuer des actions basées sur l'utilisateur TimeZone

StErMi
la source
3

La meilleure façon de stocker datedans SQlite DB est de stocker le courant DateTimeMilliseconds. Voici l'extrait de code pour le faire_

  1. Obtenir le 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. Insérez les données dans votre base de données
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 .

L'étape suivante est, lorsque vous souhaitez récupérer des données de la base de données, vous devez convertir les millisecondes de date et d'heure respectives en date correspondante. Voici un exemple d'extrait de code pour faire de même_

  1. Convertissez les millisecondes de date en chaîne de date.
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. Maintenant, récupérez enfin les données et voyez son fonctionnement ...
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;

}

J'espère que cela aidera tout le monde! :)

Rupesh Yadav
la source
SQLite ne prend pas en charge le type de données long. EDIT: Mon erreur, INTEGER est de 8 octets de long, donc il devrait prendre en charge ce type de données.
Antonio Vlasic
1

Je préfère ça. Ce n'est pas le meilleur moyen, mais une solution rapide.

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