Updated on 2026-08-14
This commit is contained in:
parent
9275e7d178
commit
00974e5d3d
3 changed files with 166 additions and 7 deletions
|
|
@ -103,9 +103,9 @@ object DateTimeFormatters {
|
|||
*/
|
||||
val localFullDate: DateTimeFormatter by lazy {
|
||||
val locale = Locale.getDefault()
|
||||
val datePattern = DateFormat.getBestDateTimePattern(locale, "d MMMM")
|
||||
val datePattern = icuPatternToJodaPattern(DateFormat.getBestDateTimePattern(locale, "d MMMM"))
|
||||
val timeSkeleton = if (is12HourFormat) "h:mm a" else "HH:mm"
|
||||
val timePattern = DateFormat.getBestDateTimePattern(locale, timeSkeleton)
|
||||
val timePattern = icuPatternToJodaPattern(DateFormat.getBestDateTimePattern(locale, timeSkeleton))
|
||||
val fullPattern = "$datePattern, $timePattern"
|
||||
DateTimeFormatterBuilder()
|
||||
.appendPattern(fullPattern)
|
||||
|
|
@ -128,13 +128,26 @@ object DateTimeFormatters {
|
|||
*/
|
||||
fun getBestFormatterBySkeleton(skeleton: String): DateTimeFormatter {
|
||||
val skeletonWithLocale = skeleton.replaceHourLetters()
|
||||
val icuPattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeletonWithLocale)
|
||||
val jodaPattern = icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
return DateTimeFormatterBuilder()
|
||||
.appendPattern(DateFormat.getBestDateTimePattern(Locale.getDefault(), skeletonWithLocale))
|
||||
.appendPattern(jodaPattern)
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts ICU date/time pattern (from [DateFormat.getBestDateTimePattern]) to Joda-Time compatible pattern.
|
||||
*/
|
||||
internal fun icuPatternToJodaPattern(icuPattern: String): String {
|
||||
return icuPattern
|
||||
.replace("LLLL", "MMMM")
|
||||
.replace("LLL", "MMM")
|
||||
.replace("LL", "MM")
|
||||
.replace("L", "M")
|
||||
}
|
||||
|
||||
private fun String.replaceHourLetters(): String {
|
||||
return if (is12HourFormat) {
|
||||
this.replace('H', 'h').replace('k', 'K')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
/**
|
||||
* Unit tests for [DateTimeFormatters], in particular for conversion of ICU date/time patterns
|
||||
* to Joda-Time compatible patterns.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DateTimeFormattersTest {
|
||||
|
||||
@Test
|
||||
fun `converts LLLL to MMMM - full standalone month pattern that crashes on Chinese locale`() {
|
||||
// Arrange
|
||||
val icuPattern = "d LLLL"
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo("d MMMM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `converts LLL to MMM - short standalone month`() {
|
||||
// Arrange
|
||||
val icuPattern = "dd LLL yyyy"
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo("dd MMM yyyy")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `converts LL to MM - numeric standalone month`() {
|
||||
// Arrange
|
||||
val icuPattern = "yyyy-MM-LL"
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo("yyyy-MM-MM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `converts single L to M`() {
|
||||
// Arrange
|
||||
val icuPattern = "d/L/yyyy"
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo("d/M/yyyy")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `leaves pattern without L unchanged`() {
|
||||
// Arrange
|
||||
val icuPattern = "dd.MM.yyyy HH:mm"
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo("dd.MM.yyyy HH:mm")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `leaves pattern with M unchanged`() {
|
||||
// Arrange
|
||||
val icuPattern = "d MMMM yyyy"
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo("d MMMM yyyy")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handles mixed ICU pattern as returned for Chinese locale - d MMMM`() {
|
||||
// Arrange
|
||||
val icuPatternWithStandaloneMonth = "d LLLL"
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPatternWithStandaloneMonth)
|
||||
|
||||
// Assert — Joda-Time can parse and format this without IllegalArgumentException
|
||||
Truth.assertThat(actual).isEqualTo("d MMMM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handles empty string`() {
|
||||
// Arrange
|
||||
val icuPattern = ""
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handles pattern with only literal characters`() {
|
||||
// Arrange
|
||||
val icuPattern = " 'at' "
|
||||
|
||||
// Act
|
||||
val actual = DateTimeFormatters.icuPatternToJodaPattern(icuPattern)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(" 'at' ")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getBestFormatterBySkeleton with d MMMM skeleton produces formatter that does not throw on format`() {
|
||||
// Arrange
|
||||
val formatter = DateTimeFormatters.getBestFormatterBySkeleton("d MMMM")
|
||||
val date = org.joda.time.DateTime(2025, 2, 13, 12, 0, 0, 0)
|
||||
|
||||
// Act & Assert
|
||||
val formatted = formatter.print(date)
|
||||
Truth.assertThat(formatted).isNotEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getBestFormatterBySkeleton with dd MMMM skeleton produces formatter that does not throw on format`() {
|
||||
// Arrange
|
||||
val formatter = DateTimeFormatters.getBestFormatterBySkeleton("dd MMMM")
|
||||
val date = org.joda.time.DateTime(2025, 2, 13, 12, 0, 0, 0)
|
||||
|
||||
// Act & Assert
|
||||
val formatted = formatter.print(date)
|
||||
Truth.assertThat(formatted).isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,13 @@ import com.tangem.utils.StringsSigns
|
|||
import org.joda.time.DateTime
|
||||
|
||||
internal fun mapFormattedDate(createdAt: String): TextReference {
|
||||
val formattedDate = getFormattedDate(
|
||||
createdAt = createdAt,
|
||||
now = DateTime.now(),
|
||||
)
|
||||
val formattedDate = runCatching {
|
||||
getFormattedDate(
|
||||
createdAt = createdAt,
|
||||
now = DateTime.now(),
|
||||
)
|
||||
}.getOrElse { FormattedDate.FullDate("") }
|
||||
|
||||
return when (formattedDate) {
|
||||
is FormattedDate.FullDate -> TextReference.Str(value = formattedDate.date)
|
||||
is FormattedDate.HoursAgo -> TextReference.PluralRes(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue