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?

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

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.


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.


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


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


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! :)


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

0
"SELECT  "+_ID+" ,  "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.