Format the "updated" timestamp as a locale-aware date/time

Replaces the raw "2026-09-10 10:03:54" with each platform's own
locale-formatted, no-seconds rendering (e.g. "Sep 10, 2026, 10:03 AM"
in en-US) — mirroring how amounts are already locale-formatted
elsewhere (NumberFormat.kt). Android uses the pre-java.time
Calendar/DateFormat APIs since minSdk 24 predates java.time (API 26)
and the project has no core library desugaring configured.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
This commit is contained in:
2026-09-10 11:57:19 +03:00
co-authored by Claude Sonnet 5
parent 43b66b6665
commit 3ce3bab7c6
6 changed files with 87 additions and 4 deletions
@@ -0,0 +1,17 @@
package xyz.tyiu.satsprice.domain
import kotlinx.datetime.LocalDateTime
import java.text.DateFormat
import java.util.Calendar
import java.util.Locale
// java.time requires API 26+ (this app's minSdk is 24) without core library desugaring, so this
// uses the older Calendar/DateFormat APIs instead, same as pre-java.time Android code always did.
actual fun localizedDateTime(dateTime: LocalDateTime): String {
val calendar = Calendar.getInstance().apply {
clear()
set(dateTime.year, dateTime.monthNumber - 1, dateTime.dayOfMonth, dateTime.hour, dateTime.minute)
}
val formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
return formatter.format(calendar.time)
}
@@ -0,0 +1,27 @@
package xyz.tyiu.satsprice.domain
import kotlinx.datetime.LocalDateTime
import platform.Foundation.NSCalendar
import platform.Foundation.NSDateComponents
import platform.Foundation.NSDateFormatter
import platform.Foundation.NSDateFormatterMediumStyle
import platform.Foundation.NSDateFormatterShortStyle
import platform.Foundation.NSLocale
import platform.Foundation.currentLocale
actual fun localizedDateTime(dateTime: LocalDateTime): String {
val components = NSDateComponents().apply {
year = dateTime.year.toLong()
month = dateTime.monthNumber.toLong()
day = dateTime.dayOfMonth.toLong()
hour = dateTime.hour.toLong()
minute = dateTime.minute.toLong()
}
val date = NSCalendar.currentCalendar.dateFromComponents(components) ?: return dateTime.toString()
val formatter = NSDateFormatter().apply {
dateStyle = NSDateFormatterMediumStyle
timeStyle = NSDateFormatterShortStyle
locale = NSLocale.currentLocale
}
return formatter.stringFromDate(date)
}
@@ -0,0 +1,11 @@
package xyz.tyiu.satsprice.domain
import kotlinx.datetime.LocalDateTime
/**
* A locale-aware, human-readable rendering of [dateTime] (e.g. "Sep 10, 2026, 10:03 AM" in
* en-US), using each platform's own date/time formatter — mirroring how [localizedGroupedInteger]
* and [localizedDecimalSeparator] format numbers. Deliberately drops seconds: a "last updated"
* label doesn't need second-level precision.
*/
expect fun localizedDateTime(dateTime: LocalDateTime): String
@@ -5,6 +5,7 @@ import kotlinx.datetime.toLocalDateTime
import xyz.tyiu.satsprice.CurrencyInfo import xyz.tyiu.satsprice.CurrencyInfo
import xyz.tyiu.satsprice.domain.CurrencyConverter import xyz.tyiu.satsprice.domain.CurrencyConverter
import xyz.tyiu.satsprice.domain.formatAmount import xyz.tyiu.satsprice.domain.formatAmount
import xyz.tyiu.satsprice.domain.localizedDateTime
import xyz.tyiu.satsprice.domain.toBigDecimalOrNull import xyz.tyiu.satsprice.domain.toBigDecimalOrNull
import kotlin.time.Instant import kotlin.time.Instant
@@ -52,7 +53,5 @@ fun ConverterUiState.unselectedCurrencies(): List<CurrencyInfo> =
/** Whether the active price source quotes a rate for [code] — every currency is listed regardless. */ /** Whether the active price source quotes a rate for [code] — every currency is listed regardless. */
fun ConverterUiState.isPriced(code: String): Boolean = code in pricedCurrencyCodes fun ConverterUiState.isPriced(code: String): Boolean = code in pricedCurrencyCodes
private fun Instant.toDateTimeString(): String { private fun Instant.toDateTimeString(): String =
val local = toLocalDateTime(TimeZone.currentSystemDefault()) localizedDateTime(toLocalDateTime(TimeZone.currentSystemDefault()))
return local.toString().substringBefore('.').replace('T', ' ')
}
@@ -0,0 +1,13 @@
package xyz.tyiu.satsprice.domain
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.toJavaLocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
actual fun localizedDateTime(dateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)
.withLocale(Locale.getDefault())
return dateTime.toJavaLocalDateTime().format(formatter)
}
@@ -0,0 +1,16 @@
package xyz.tyiu.satsprice.domain
import kotlinx.datetime.LocalDateTime
// Individual Int fields (rather than the LocalDateTime itself) cross the js() boundary, matching
// the rest of this file's web interop — see SystemCurrencies.web.kt for why.
private fun jsFormatDateTime(year: Int, month: Int, day: Int, hour: Int, minute: Int): String = js(
"""(function() {
var date = new Date(year, month - 1, day, hour, minute);
var fmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' });
return fmt.format(date);
})()""",
)
actual fun localizedDateTime(dateTime: LocalDateTime): String =
jsFormatDateTime(dateTime.year, dateTime.monthNumber, dateTime.dayOfMonth, dateTime.hour, dateTime.minute)