Android
SQLite
Date Handling
Mobile Development
Database Management

Best way to work with dates in Android SQLite

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

SQLite does not have a dedicated date type, so the best way to store dates on Android is to choose one representation and keep it consistent across inserts, queries, and UI formatting. For most app code, the strongest default is an INTEGER column containing UTC epoch milliseconds. It is compact, easy to compare, and maps cleanly to modern Java and Kotlin time APIs.

Why Epoch Milliseconds Are a Strong Default

SQLite can store time-like values as text, real numbers, or integers. On Android, INTEGER epoch time is often the most practical because it gives you:

  • simple numeric range queries
  • timezone-neutral storage in UTC
  • fast ordering and indexing
  • easy conversion to Instant, LocalDateTime, or ZonedDateTime

The most important design rule is this: store in UTC, format for the user later.

Define the Table With an Integer Column

A basic schema looks like this:

sql
1CREATE TABLE events (
2    id INTEGER PRIMARY KEY AUTOINCREMENT,
3    title TEXT NOT NULL,
4    starts_at_utc_ms INTEGER NOT NULL
5);

This makes the meaning explicit. The column is not "some date string." It is a UTC timestamp in milliseconds.

Insert Dates From Kotlin

With modern Android code, work with Instant or epoch milliseconds rather than building manual date strings.

kotlin
1import android.content.ContentValues
2import java.time.Instant
3
4val values = ContentValues().apply {
5    put("title", "Release")
6    put("starts_at_utc_ms", Instant.now().toEpochMilli())
7}
8
9db.insert("events", null, values)

That keeps the database layer stable and avoids locale formatting issues.

Read Dates Back Into Time Objects

kotlin
1import java.time.Instant
2import java.time.ZoneId
3
4val cursor = db.rawQuery("SELECT title, starts_at_utc_ms FROM events", null)
5while (cursor.moveToNext()) {
6    val title = cursor.getString(0)
7    val epochMs = cursor.getLong(1)
8    val instant = Instant.ofEpochMilli(epochMs)
9    val localTime = instant.atZone(ZoneId.systemDefault())
10    println("$title at $localTime")
11}
12cursor.close()

This is the right separation of concerns:

  • database stores neutral UTC time
  • app converts for the device timezone only when presenting to users

Query by Date Range Efficiently

Numeric timestamps make range queries straightforward.

kotlin
1val start = Instant.parse("2026-03-01T00:00:00Z").toEpochMilli()
2val end = Instant.parse("2026-04-01T00:00:00Z").toEpochMilli()
3
4val cursor = db.rawQuery(
5    "SELECT title FROM events WHERE starts_at_utc_ms >= ? AND starts_at_utc_ms < ? ORDER BY starts_at_utc_ms",
6    arrayOf(start.toString(), end.toString())
7)

This is cleaner and usually more index-friendly than parsing textual date formats at query time.

When ISO 8601 Text Is Still Reasonable

Text dates are not wrong. They can be a good choice when you need human-readable raw rows or easy interoperability with external data.

A good text format is ISO 8601 in UTC, for example:

text
2026-03-07T15:30:00Z

If you choose text, be strict and keep the format uniform. Do not mix local time strings, human-readable display text, and machine-sortable timestamps in the same column.

For most Android apps, integer UTC timestamps are still easier to keep correct.

Avoid Legacy Date Handling Patterns

Older Android code often used java.util.Date, Calendar, and ad hoc SimpleDateFormat logic everywhere. Those APIs still exist, but they make timezone bugs and parsing mistakes more likely.

If your minSdk or toolchain allows it, prefer java.time APIs or their Android-compatible equivalents through desugaring. The code is clearer and the UTC story is easier to reason about.

Formatting for Display Is a Separate Step

Do not store user-facing formatted text such as "Mar 7, 2026 at 10:30 AM" in SQLite for actual date logic. That text is for UI only.

Store UTC timestamps, then format them near the UI layer:

kotlin
1import java.time.Instant
2import java.time.ZoneId
3import java.time.format.DateTimeFormatter
4
5val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
6val text = Instant.ofEpochMilli(epochMs)
7    .atZone(ZoneId.systemDefault())
8    .format(formatter)

That keeps persistence logic and presentation logic separate.

Common Pitfalls

  • Storing local-time strings and later trying to compare them like real timestamps.
  • Mixing several date formats in one table.
  • Doing timezone conversion at insert time instead of storing UTC consistently.
  • Using formatted display strings as the primary persisted date value.
  • Forgetting that SQLite does not enforce a dedicated date column type for you.

Summary

  • The best general-purpose Android SQLite date format is usually UTC epoch milliseconds in an INTEGER column.
  • Store neutrally, then format for the user's timezone at the UI layer.
  • Numeric timestamps make ordering and range queries simple.
  • ISO 8601 text is viable, but only if you keep it strict and consistent.
  • Date correctness depends more on a stable storage contract than on SQLite itself.

Course illustration
Course illustration

All Rights Reserved.