How can I display localized dates in Jetpack Compose without breaking ISO date filtering?


I am developing an Android expense-tracking application using Kotlin, Jetpack Compose, and Room.

Each expense has a date stored as a String in ISO format (yyyy-MM-dd). I use this format because my application filters expenses by month and generates monthly reports.

However, I want to display dates in the UI using the format dd/MM/yyyy.

For example, a date stored as:

2026-10-29

should appear in the UI as:

29/10/2026

My current code displays the date directly in a Compose Text element:

Text(
    text = gasto.fecha
)

My monthly filter uses the first seven characters of the stored date:

val gastosDelMes = gastos.filter {
    it.fecha.startsWith(YearMonth.now().toString())
}

I could change the stored date format to dd/MM/yyyy, but that would break the existing filtering logic because the date would no longer start with the year and month.

What is the recommended way to format dates for display in Jetpack Compose while keeping the original ISO date string for database storage and monthly filtering?

Should I create a separate date-formatting function for the UI, or is there a better approach for this situation?

-1
Oct 29 at 3:00 PM
User AvatarGERSON DAVID MONTUFAR ZECEÑA
#android#kotlin#datetime#android-jetpack-compose

Accepted Answer

If it is a date you should definitely use an appropriate date data type for it, not a String.

If you just want to store a wall clock date, then the best data type would be a kotlinx.datetime.LocalDate from the official kotlinx-datetime library. If you want to store an instant in time (that would be independent of the local timezone) then you should use a kotlin.time.Instant which is part of Kotlin's standard lib and doesn't even need the above mentioned external library. Whatever you choose, make sure to use the Kotlin types, not the Java types of the same name of the java.time package.

Doing date arithmetics then becomes much more robust and flexible than having to mess with substrings as you are currently doing it. Also, converting the date for the UI becomes more simple, especially since you may want your users to choose a familiar format that is more close to what is used in their locale, which - for many - is not dd/MM/yyyy. You can easily accomodate for this when using a proper date type, but you will have a hard time getting it right if you only use a String.

Both data types (LocalDate and Instant), however, are not natively supported by Room. That isn't a problem, though, because you can simply define a type converter for Room to automatically convert the type to something it does support and back again. This works fully transparent, so your data models never come in contact with the converted data.

For a LocalDate with Room 3 that would look like this<sup>1</sup>:

object Converters {
    @ColumnTypeConverter
    fun fromEpochDays(days: Long): LocalDate = LocalDate.fromEpochDays(days)

    @ColumnTypeConverter
    fun dateToEpochDays(date: LocalDate): Long = date.toEpochDays()
}

For Room to be able to discover these converters you have to annotate your database class accordingly:

@ColumnTypeConverters(Converters::class)

Your entity can then use a proper LocalDate:

data class GastoEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val fecha: LocalDate,
)

And your filtering logic would look like this:

val gastosDelMes = gastos.filter {
    it.fecha.yearMonth == today.yearMonth
}

today would be the LocalDate representing today, which is timezone-dependent (maybe val today = Clock.System.todayIn(TimeZone.currentSystemDefault())). Something your current approach with Strings also neglects to properly handle, which will fall on your feet if the user moves from one timezone to another.


<sup><sup>1</sup> If you are still using the deprecated Room version 2.x you need to employ the old syntax described here: https://developer.android.com/training/data-storage/room/v2#type-converters</sup>

User Avatartyg
Oct 29 at 11:07 PM
2