Melhor maneira de trabalhar com datas no Android SQLite [fechado]

237

Estou com problemas para trabalhar com datas no meu aplicativo Android que usa SQLite. Eu tenho algumas perguntas:

  1. Que tipo devo usar para armazenar datas no SQLite (texto, número inteiro, ...)?
  2. Dada a melhor maneira de armazenar datas, como faço para armazená-lo corretamente usando o ContentValues?
  3. Qual é a melhor maneira de recuperar a data do banco de dados SQLite?
  4. Como fazer um sql selecionar no SQLite, ordenando os resultados por data?
Filipe
fonte
2
Basta usar a classe Calendar e seu horário de membro (que representa o número de milissegundos que passaram desde 1/1/1970). Existem funções de membro para alterar o valor do tempo em seqüências legíveis pelo usuário.
slayton

Respostas:

43

Você pode usar um campo de texto para armazenar datas SQLite.

Armazenando datas no formato UTC, o padrão se você usar datetime('now') (yyyy-MM-dd HH:mm:ss)permitirá a classificação pela coluna de datas.

Recuperando datas como seqüências de caracteres, SQLitevocê pode formatá-las / convertê-las conforme necessário em formatos regionais regionalizados usando o Calendário ou o android.text.format.DateUtils.formatDateTimemétodo.

Aqui está um método formatador regionalizado que eu 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;
}
CodeChimp
fonte
63
Como você lidaria com a consulta de períodos?
Joe
51
"Prática recomendada"? Não parece certo.
shim
135
Nos anos em que uso o SQL, nunca vi alguém recomendar anteriormente o armazenamento de datas como strings. Se você não possui um tipo específico de coluna de data, use um número inteiro e armazene no horário Unix (segundos desde a época). É classificável e utilizável em intervalos e facilmente convertido.
mikebabcock
20
Armazenar datas como string é bom se você deseja armazená-las como "informações", algo que você recupera e mostra. Mas se você deseja armazenar datas como "dados", algo para trabalhar, considere armazená-las como um número inteiro desde a época. Isso permitirá que você consulte períodos, é padrão, assim você não precisa se preocupar com conversões, etc. O armazenamento de datas como uma string é muito limitante e eu gostaria muito de saber quem recomendou essa prática como regra geral.
Krystian #
8
A documentação do sqlite lista o armazenamento como texto (ISO 8601) como uma solução viável para o armazenamento de datas. Na verdade, ele é listado primeiro.
precisa saber é o seguinte
211

A melhor maneira é armazenar as datas como um número, recebido usando o comando Calendário.

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

Por que fazer isso? Primeiro de tudo, é fácil obter valores de um período. Basta converter sua data em milissegundos e, em seguida, consultar adequadamente. Classificar por data é igualmente fácil. As chamadas para converter entre vários formatos também são fáceis, como eu incluí. Resumindo, com este método, você pode fazer o que precisar, sem problemas. Será um pouco difícil ler um valor bruto, mas mais do que compõe essa pequena desvantagem de ser facilmente legível e utilizável pela máquina. E, de fato, é relativamente fácil criar um leitor (e eu sei que existem alguns por aí) que converterão automaticamente a marca de tempo para data como tal, para facilitar a leitura.

Vale ressaltar que os valores resultantes disso devem ser longos, não int. Inteiro no sqlite pode significar muitas coisas, de 1 a 8 bytes, mas para quase todas as datas 64 bits, ou um longo, é o que funciona.

EDIT: Como foi apontado nos comentários, você deve usar o cursor.getLong()para obter o registro de data e hora corretamente se fizer isso.

PearsonArtPhoto
fonte
17
Obrigado cara. Lol Pensei em digitar errado, mas não consegui encontrá-lo. Ele deve ser recuperado por cursor.getLong (), não por cursor.getInt (). Lol não consegue parar de rir de mim mesma. Obrigado novamente.
Son Huy TRAN
37
  1. Como presumido neste comentário , eu sempre usaria números inteiros para armazenar datas.
  2. Para armazenar, você pode usar um método utilitário

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

    igual a:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
  3. Outro método utilitário cuida do carregamento

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

    pode ser usado assim:

    entity.setDate(loadDate(cursor, INDEX));
  4. A ordenação por data é uma simples cláusula SQL ORDER (porque temos uma coluna numérica). A ordem a seguir será decrescente (a data mais recente será a primeira):

    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
        }

Sempre certifique-se de armazenar a hora UTC / GMT , especialmente quando estiver trabalhando java.util.Calendare java.text.SimpleDateFormatque use o fuso horário padrão (por exemplo, o seu dispositivo). java.util.Date.Date()é seguro de usar, pois cria um valor UTC.

schnatterer
fonte
9

O SQLite pode usar tipos de dados de texto, reais ou inteiros para armazenar datas. Ainda mais, sempre que você realiza uma consulta, os resultados são mostrados usando o formato %Y-%m-%d %H:%M:%S.

Agora, se você inserir / atualizar valores de data / hora usando as funções de data / hora do SQLite, também poderá armazenar milissegundos. Se for esse o caso, os resultados são mostrados usando o formato %Y-%m-%d %H:%M:%f. Por exemplo:

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

Agora, faça algumas consultas para verificar se conseguimos comparar os tempos:

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

Você pode verificar o mesmo SELECTusando col2e col3e você vai obter os mesmos resultados. Como você pode ver, a segunda linha (126 milissegundos) não é retornada.

Observe que BETWEENé inclusivo, portanto ...

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

... retornará o mesmo conjunto.

Tente brincar com diferentes intervalos de data / hora e tudo se comportará conforme o esperado.

E sem strftimefunção?

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

Que tal sem strftimefunção e sem milissegundos?

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

Que tal 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

Funciona muito bem.

Finalmente, ao lidar com operações reais dentro de um programa (sem usar o executável sqlite ...)

BTW: Estou usando JDBC (não tenho certeza sobre outros idiomas) ... o driver sqlite-jdbc v3.7.2 da xerial - talvez as revisões mais recentes alterem o comportamento explicado abaixo ... Se você está desenvolvendo no Android, não precisa precisa de um driver jdbc. Todas as operações SQL podem ser enviadas usando o SQLiteOpenHelper.

JDBC tem diferentes métodos para obter valores de data / hora real a partir de um banco de dados: java.sql.Date, java.sql.Time, e java.sql.Timestamp.

Os métodos relacionados em java.sql.ResultSetsão (obviamente) getDate(..), getTime(..)e, getTimestamp()respectivamente.

Por exemplo:

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.

Como o SQLite não possui um tipo de dados DATE / TIME / TIMESTAMP real, todos esses três métodos retornam valores como se os objetos fossem inicializados com 0:

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

Portanto, a pergunta é: como podemos realmente selecionar, inserir ou atualizar objetos Data / Hora / Data e Hora? Não há resposta fácil. Você pode tentar combinações diferentes, mas elas o forçarão a incorporar funções SQLite em todas as instruções SQL. É muito mais fácil definir uma classe de utilitário para transformar texto em objetos Date dentro do seu programa Java. Mas lembre-se sempre de que o SQLite transforma qualquer valor de data em UTC + 0000.

Em resumo, apesar da regra geral de sempre usar o tipo de dados correto ou mesmo números inteiros que denotam tempo Unix (milissegundos desde a época), acho muito mais fácil usar o formato SQLite padrão ( '%Y-%m-%d %H:%M:%f'ou em Java 'yyyy-MM-dd HH:mm:ss.SSS') para complicar todas as suas instruções SQL com Funções SQLite. A abordagem anterior é muito mais fácil de manter.

TODO: Vou verificar os resultados ao usar getDate / getTime / getTimestamp dentro do Android (API15 ou melhor) ... talvez o driver interno seja diferente do sqlite-jdbc ...

miguelt
fonte
1
Dado o mecanismo de armazenamento interno do SQLite, não estou convencido de que seus exemplos tenham o efeito que você implica: parece que o mecanismo "permite armazenar quaisquer valores digitados em armazenamento em qualquer coluna, independentemente do tipo SQL declarado" ( books.google. de / ... ). Parece-me que, no seu exemplo Real x Inteiro x Texto, o que está acontecendo é o seguinte: SQLite apenas armazena o texto como Texto em todas as colunas da árvore. Então, naturalmente, os resultados são bons, o armazenamento ainda é desperdiçado. Se apenas um número inteiro foi usado, você deve perder milissegundos. Só estou dizendo ... #
2800 marco Marco
De fato, você pode confirmar o que acabei de dizer fazendo um datetime SELECT (col3, 'unixepoch') FROM test_table. Isso mostrará linhas vazias para seus exemplos ... a menos que, para fins de teste, você insira um inteiro inteiro. Por exemplo, se você adicionar uma linha com o valor col3 37, a instrução SELECT acima mostrará: 1970-01-01 00:00:37. Portanto, a menos que você esteja realmente bem em armazenar todas as suas datas de maneira ineficiente como uma sequência de texto, não faça o que sugere.
Marco
Faz muito tempo desde que eu postei esta resposta ... talvez o SQLite tenha sido atualizado. A única coisa em que consigo pensar é executar as instruções SQL novamente versus suas sugestões.
miguelt
3

Normalmente (o mesmo que no mysql / postgres) armazeno datas em int (mysql / post) ou texto (sqlite) para armazená-las no formato de carimbo de data / hora.

Então eu os converterei em objetos Date e executarei ações com base no usuário TimeZone

StErMi
fonte
3

A melhor maneira de armazenar dateno SQlite DB é armazenar o atual DateTimeMilliseconds. Abaixo está o snippet de código para fazer isso_

  1. Pegue o 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. Insira os dados no seu banco de dados
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 .

A próxima etapa é que, quando você deseja recuperar dados do banco de dados, precisa converter os respectivos milissegundos de data e hora na data correspondente. Abaixo está o exemplo de trecho de código para fazer o mesmo_

  1. Converta milissegundos de data em string de 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. Agora, finalmente, busque os dados e veja seu funcionamento ...
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;

}

Espero que isso ajude a todos! :)

Rupesh Yadav
fonte
O SQLite não suporta o tipo de dados longo. EDIT: Meu erro, INTEGER é de 8 bytes, por isso deve suportar esse tipo de dados.
Antonio Vlasic
1

Eu prefiro isso. Esta não é a melhor maneira, mas uma solução rápida.

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