forked from dsutanto/bChot-android
First Commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,21 @@
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.dateformatter.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCommonDependencies(libs)
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.api
|
||||
|
||||
interface DateFormatter {
|
||||
fun format(
|
||||
timestamp: Long?,
|
||||
mode: DateFormatterMode = DateFormatterMode.Full,
|
||||
useRelative: Boolean = false,
|
||||
): String
|
||||
}
|
||||
|
||||
enum class DateFormatterMode {
|
||||
/**
|
||||
* Full date and time.
|
||||
* Example:
|
||||
* "April 6, 1980 at 6:35 PM"
|
||||
* Format can be shorter when useRelative is true.
|
||||
* Example:
|
||||
* "6:35 PM"
|
||||
*/
|
||||
Full,
|
||||
|
||||
/**
|
||||
* Only month and year.
|
||||
* Example:
|
||||
* "April 1980"
|
||||
* "This month" can be returned when useRelative is true.
|
||||
* Example:
|
||||
* "This month"
|
||||
*/
|
||||
Month,
|
||||
|
||||
/**
|
||||
* Only day.
|
||||
* Example:
|
||||
* "Sunday 6 April"
|
||||
* "Today", "Yesterday" and day of week can be returned when useRelative is true.
|
||||
*/
|
||||
Day,
|
||||
|
||||
/**
|
||||
* Time if same day, else date.
|
||||
*/
|
||||
TimeOrDate,
|
||||
|
||||
/**
|
||||
* Only time whatever the day.
|
||||
*/
|
||||
TimeOnly,
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.api
|
||||
|
||||
import java.util.Locale
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Convert milliseconds to human readable duration.
|
||||
* Hours in 1 digit or more.
|
||||
* Minutes in 2 digits when hours are available.
|
||||
* Seconds always on 2 digits.
|
||||
* Example:
|
||||
* - when the duration is longer than 1 hour:
|
||||
* - "10:23:34"
|
||||
* - "1:23:34"
|
||||
* - "1:03:04"
|
||||
* - when the duration is shorter:
|
||||
* - "4:56"
|
||||
* - "14:06"
|
||||
* - Less than one minute:
|
||||
* - "0:00"
|
||||
* - "0:01"
|
||||
* - "0:59"
|
||||
*/
|
||||
fun Long.toHumanReadableDuration(): String {
|
||||
val inSeconds = this / 1_000
|
||||
val hours = inSeconds / 3_600
|
||||
val minutes = inSeconds % 3_600 / 60
|
||||
val seconds = inSeconds % 60
|
||||
return if (hours > 0) {
|
||||
String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds)
|
||||
} else {
|
||||
String.format(Locale.US, "%d:%02d", minutes, seconds)
|
||||
}
|
||||
}
|
||||
|
||||
fun Duration.toHumanReadableDuration() = inWholeMilliseconds.toHumanReadableDuration()
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.api
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class DurationFormatterTest {
|
||||
@Test
|
||||
fun `format seconds only`() {
|
||||
assertThat(buildDuration().toHumanReadableDuration()).isEqualTo("0:00")
|
||||
assertThat(buildDuration(seconds = 1).toHumanReadableDuration()).isEqualTo("0:01")
|
||||
assertThat(buildDuration(seconds = 59).toHumanReadableDuration()).isEqualTo("0:59")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `format minutes and seconds`() {
|
||||
assertThat(buildDuration(minutes = 1).toHumanReadableDuration()).isEqualTo("1:00")
|
||||
assertThat(buildDuration(minutes = 1, seconds = 30).toHumanReadableDuration()).isEqualTo("1:30")
|
||||
assertThat(buildDuration(minutes = 59, seconds = 59).toHumanReadableDuration()).isEqualTo("59:59")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `format hours, minutes and seconds`() {
|
||||
assertThat(buildDuration(hours = 1).toHumanReadableDuration()).isEqualTo("1:00:00")
|
||||
assertThat(buildDuration(hours = 1, minutes = 1, seconds = 1).toHumanReadableDuration()).isEqualTo("1:01:01")
|
||||
assertThat(buildDuration(hours = 24, minutes = 59, seconds = 59).toHumanReadableDuration()).isEqualTo("24:59:59")
|
||||
assertThat(buildDuration(hours = 25, minutes = 0, seconds = 0).toHumanReadableDuration()).isEqualTo("25:00:00")
|
||||
}
|
||||
|
||||
private fun buildDuration(
|
||||
hours: Int = 0,
|
||||
minutes: Int = 0,
|
||||
seconds: Int = 0
|
||||
): Long {
|
||||
return (hours * 60 * 60 + minutes * 60 + seconds) * 1000L
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,49 @@
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.dateformatter.impl"
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
optIn = listOf(
|
||||
"kotlin.time.ExperimentalTime"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.services.toolbox.api)
|
||||
|
||||
api(projects.libraries.dateformatter.api)
|
||||
api(libs.datetime)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.libraries.dateformatter.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.extensions.safeCapitalize
|
||||
|
||||
interface DateFormatterDay {
|
||||
fun format(
|
||||
timestamp: Long,
|
||||
useRelative: Boolean,
|
||||
): String
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultDateFormatterDay(
|
||||
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||
private val dateFormatters: DateFormatters,
|
||||
) : DateFormatterDay {
|
||||
override fun format(
|
||||
timestamp: Long,
|
||||
useRelative: Boolean,
|
||||
): String {
|
||||
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||
val today = localDateTimeProvider.providesNow()
|
||||
return if (useRelative) {
|
||||
val dayDiff = today.date.toEpochDays() - dateToFormat.date.toEpochDays()
|
||||
when (dayDiff) {
|
||||
0L -> dateFormatters.getRelativeDay(timestamp, "Today")
|
||||
1L -> dateFormatters.getRelativeDay(timestamp, "Yesterday")
|
||||
else -> if (dayDiff < 7) {
|
||||
dateFormatters.formatDateWithDay(dateToFormat)
|
||||
} else {
|
||||
if (today.year == dateToFormat.year) {
|
||||
dateFormatters.formatDateWithFullFormatNoYear(dateToFormat)
|
||||
} else {
|
||||
dateFormatters.formatDateWithFullFormat(dateToFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (today.year == dateToFormat.year) {
|
||||
dateFormatters.formatDateWithFullFormatNoYear(dateToFormat)
|
||||
} else {
|
||||
dateFormatters.formatDateWithFullFormat(dateToFormat)
|
||||
}
|
||||
}
|
||||
.safeCapitalize()
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
|
||||
@Inject
|
||||
class DateFormatterFull(
|
||||
private val stringProvider: StringProvider,
|
||||
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||
private val dateFormatters: DateFormatters,
|
||||
private val dateFormatterDay: DateFormatterDay,
|
||||
) {
|
||||
fun format(
|
||||
timestamp: Long,
|
||||
useRelative: Boolean,
|
||||
): String {
|
||||
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||
val time = dateFormatters.formatTime(dateToFormat)
|
||||
return if (useRelative) {
|
||||
val now = localDateTimeProvider.providesNow()
|
||||
if (now.date == dateToFormat.date) {
|
||||
time
|
||||
} else {
|
||||
val dateStr = dateFormatterDay.format(timestamp, true)
|
||||
stringProvider.getString(R.string.common_date_date_at_time, dateStr, time)
|
||||
}
|
||||
} else {
|
||||
val dateStr = dateFormatters.formatDateWithFullFormat(dateToFormat)
|
||||
stringProvider.getString(R.string.common_date_date_at_time, dateStr, time)
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.core.extensions.safeCapitalize
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
|
||||
@Inject
|
||||
class DateFormatterMonth(
|
||||
private val stringProvider: StringProvider,
|
||||
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||
private val dateFormatters: DateFormatters,
|
||||
) {
|
||||
fun format(
|
||||
timestamp: Long,
|
||||
useRelative: Boolean,
|
||||
): String {
|
||||
val today = localDateTimeProvider.providesNow()
|
||||
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||
return if (useRelative && dateToFormat.month == today.month && dateToFormat.year == today.year) {
|
||||
stringProvider.getString(R.string.common_date_this_month)
|
||||
} else {
|
||||
dateFormatters.formatDateWithMonthAndYear(dateToFormat)
|
||||
}
|
||||
.safeCapitalize()
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
|
||||
@Inject
|
||||
class DateFormatterTime(
|
||||
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||
private val dateFormatters: DateFormatters,
|
||||
) {
|
||||
fun format(
|
||||
timestamp: Long,
|
||||
useRelative: Boolean,
|
||||
): String {
|
||||
val currentDate = localDateTimeProvider.providesNow()
|
||||
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||
val isSameDay = currentDate.date == dateToFormat.date
|
||||
return when {
|
||||
isSameDay -> {
|
||||
dateFormatters.formatTime(dateToFormat)
|
||||
}
|
||||
else -> {
|
||||
dateFormatters.formatDate(
|
||||
dateToFormat = dateToFormat,
|
||||
currentDate = currentDate,
|
||||
useRelative = useRelative,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
|
||||
@Inject
|
||||
class DateFormatterTimeOnly(
|
||||
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||
private val dateFormatters: DateFormatters,
|
||||
) {
|
||||
fun format(
|
||||
timestamp: Long,
|
||||
): String {
|
||||
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||
return dateFormatters.formatTime(dateToFormat)
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toJavaLocalDate
|
||||
import kotlinx.datetime.toJavaLocalDateTime
|
||||
import timber.log.Timber
|
||||
import java.time.Period
|
||||
import java.util.Locale
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.time.Clock
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@Inject
|
||||
class DateFormatters(
|
||||
localeChangeObserver: LocaleChangeObserver,
|
||||
private val clock: Clock,
|
||||
private val timeZoneProvider: TimezoneProvider,
|
||||
locale: Locale,
|
||||
) : LocaleChangeListener {
|
||||
init {
|
||||
localeChangeObserver.addListener(this)
|
||||
}
|
||||
|
||||
private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(locale)
|
||||
|
||||
override fun onLocaleChange() {
|
||||
Timber.w("Locale changed, updating formatters")
|
||||
dateTimeFormatters = DateTimeFormatters(Locale.getDefault())
|
||||
}
|
||||
|
||||
internal fun formatTime(localDateTime: LocalDateTime): String {
|
||||
return dateTimeFormatters.onlyTimeFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
internal fun formatDateWithMonthAndYear(localDateTime: LocalDateTime): String {
|
||||
return dateTimeFormatters.dateWithMonthAndYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
internal fun formatDateWithMonth(localDateTime: LocalDateTime): String {
|
||||
return dateTimeFormatters.dateWithMonthFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
internal fun formatDateWithDay(localDateTime: LocalDateTime): String {
|
||||
return dateTimeFormatters.dateWithDayFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
internal fun formatDateWithYear(localDateTime: LocalDateTime): String {
|
||||
return dateTimeFormatters.dateWithYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
internal fun formatDateWithFullFormat(localDateTime: LocalDateTime): String {
|
||||
return dateTimeFormatters.dateWithFullFormatFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
internal fun formatDateWithFullFormatNoYear(localDateTime: LocalDateTime): String {
|
||||
return dateTimeFormatters.dateWithFullFormatNoYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
internal fun formatDate(
|
||||
dateToFormat: LocalDateTime,
|
||||
currentDate: LocalDateTime,
|
||||
useRelative: Boolean
|
||||
): String {
|
||||
val period = Period.between(dateToFormat.date.toJavaLocalDate(), currentDate.date.toJavaLocalDate())
|
||||
return if (period.years.absoluteValue >= 1) {
|
||||
formatDateWithYear(dateToFormat)
|
||||
} else if (useRelative && period.days.absoluteValue < 2 && period.months.absoluteValue < 1) {
|
||||
getRelativeDay(dateToFormat.toInstant(timeZoneProvider.provide()).toEpochMilliseconds())
|
||||
} else {
|
||||
formatDateWithMonth(dateToFormat)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getRelativeDay(ts: Long, default: String = ""): String {
|
||||
return DateUtils.getRelativeTimeSpanString(
|
||||
ts,
|
||||
clock.now().toEpochMilliseconds(),
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_SHOW_WEEKDAY
|
||||
)?.toString() ?: default
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Locale
|
||||
|
||||
class DateTimeFormatters(
|
||||
private val locale: Locale,
|
||||
) {
|
||||
val onlyTimeFormatter: DateTimeFormatter by lazy {
|
||||
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale)
|
||||
}
|
||||
|
||||
val dateWithMonthAndYearFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = bestDateTimePattern("MMMM YYYY")
|
||||
DateTimeFormatter.ofPattern(pattern, locale)
|
||||
}
|
||||
|
||||
val dateWithMonthFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = bestDateTimePattern("d MMM")
|
||||
DateTimeFormatter.ofPattern(pattern, locale)
|
||||
}
|
||||
|
||||
val dateWithDayFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = bestDateTimePattern("EEEE")
|
||||
DateTimeFormatter.ofPattern(pattern, locale)
|
||||
}
|
||||
|
||||
val dateWithYearFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = bestDateTimePattern("dd.MM.yyyy")
|
||||
DateTimeFormatter.ofPattern(pattern, locale)
|
||||
}
|
||||
|
||||
val dateWithFullFormatFormatter: DateTimeFormatter by lazy {
|
||||
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(locale)
|
||||
}
|
||||
|
||||
val dateWithFullFormatNoYearFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = DateFormat.getBestDateTimePattern(locale, "EEEE d MMMM") ?: "EEEE d MMMM"
|
||||
DateTimeFormatter.ofPattern(pattern, locale)
|
||||
}
|
||||
|
||||
private fun bestDateTimePattern(pattern: String): String {
|
||||
return DateFormat.getBestDateTimePattern(locale, pattern) ?: pattern
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultDateFormatter(
|
||||
private val dateFormatterFull: DateFormatterFull,
|
||||
private val dateFormatterMonth: DateFormatterMonth,
|
||||
private val dateFormatterDay: DateFormatterDay,
|
||||
private val dateFormatterTime: DateFormatterTime,
|
||||
private val dateFormatterTimeOnly: DateFormatterTimeOnly,
|
||||
) : DateFormatter {
|
||||
override fun format(
|
||||
timestamp: Long?,
|
||||
mode: DateFormatterMode,
|
||||
useRelative: Boolean,
|
||||
): String {
|
||||
timestamp ?: return ""
|
||||
return when (mode) {
|
||||
DateFormatterMode.Full -> {
|
||||
dateFormatterFull.format(timestamp, useRelative)
|
||||
}
|
||||
DateFormatterMode.Month -> {
|
||||
dateFormatterMonth.format(timestamp, useRelative)
|
||||
}
|
||||
DateFormatterMode.Day -> {
|
||||
dateFormatterDay.format(timestamp, useRelative)
|
||||
}
|
||||
DateFormatterMode.TimeOrDate -> {
|
||||
dateFormatterTime.format(timestamp, useRelative)
|
||||
}
|
||||
DateFormatterMode.TimeOnly -> {
|
||||
dateFormatterTimeOnly.format(timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Inject
|
||||
class LocalDateTimeProvider(
|
||||
private val clock: Clock,
|
||||
private val timezoneProvider: TimezoneProvider,
|
||||
) {
|
||||
fun providesNow(): LocalDateTime {
|
||||
val now: Instant = clock.now()
|
||||
return now.toLocalDateTime(timezoneProvider.provide())
|
||||
}
|
||||
|
||||
fun providesFromTimestamp(timestamp: Long): LocalDateTime {
|
||||
val tsInstant = Instant.fromEpochMilliseconds(timestamp)
|
||||
return tsInstant.toLocalDateTime(timezoneProvider.provide())
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
|
||||
fun interface LocaleChangeObserver {
|
||||
fun addListener(listener: LocaleChangeListener)
|
||||
}
|
||||
|
||||
interface LocaleChangeListener {
|
||||
fun onLocaleChange()
|
||||
}
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultLocaleChangeObserver(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : LocaleChangeObserver {
|
||||
init {
|
||||
registerReceiver(object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
listeners.forEach(LocaleChangeListener::onLocaleChange)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private val listeners = mutableSetOf<LocaleChangeListener>()
|
||||
|
||||
override fun addListener(listener: LocaleChangeListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
private fun registerReceiver(receiver: BroadcastReceiver) {
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(Intent.ACTION_LOCALE_CHANGED)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
filter.addAction(Intent.ACTION_APPLICATION_LOCALE_CHANGED)
|
||||
}
|
||||
context.registerReceiver(receiver, filter)
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import kotlinx.datetime.TimeZone
|
||||
|
||||
fun interface TimezoneProvider {
|
||||
fun provide(): TimeZone
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl.di
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Provides
|
||||
import io.element.android.libraries.dateformatter.impl.TimezoneProvider
|
||||
import kotlinx.datetime.TimeZone
|
||||
import java.util.Locale
|
||||
import kotlin.time.Clock
|
||||
|
||||
@BindingContainer
|
||||
@ContributesTo(AppScope::class)
|
||||
object DateFormatterModule {
|
||||
@Provides
|
||||
fun providesClock(): Clock = Clock.System
|
||||
|
||||
@Provides
|
||||
fun providesLocale(): Locale = Locale.getDefault()
|
||||
|
||||
@Provides
|
||||
fun providesTimezone(): TimezoneProvider = TimezoneProvider { TimeZone.currentSystemDefault() }
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl.previews
|
||||
|
||||
data class DateForPreview(
|
||||
val semantic: String,
|
||||
val date: String,
|
||||
)
|
||||
|
||||
val dateForPreviewToday = DateForPreview(
|
||||
semantic = "Today",
|
||||
date = "1980-04-06T18:35:24.00Z",
|
||||
)
|
||||
|
||||
val dateForPreviews = listOf(
|
||||
DateForPreview(
|
||||
semantic = "Now",
|
||||
date = dateForPreviewToday.date,
|
||||
),
|
||||
DateForPreview(
|
||||
semantic = "One second ago",
|
||||
date = "1980-04-06T18:35:23.00Z",
|
||||
),
|
||||
DateForPreview(
|
||||
semantic = "One minute ago",
|
||||
date = "1980-04-06T18:34:24.00Z",
|
||||
),
|
||||
DateForPreview(
|
||||
semantic = "One hour ago",
|
||||
date = "1980-04-06T17:35:24.00Z",
|
||||
),
|
||||
DateForPreview(
|
||||
semantic = "One day ago",
|
||||
date = "1980-04-05T18:35:24.00Z",
|
||||
),
|
||||
DateForPreview(
|
||||
semantic = "Two days ago",
|
||||
date = "1980-04-04T18:35:24.00Z",
|
||||
),
|
||||
DateForPreview(
|
||||
semantic = "One month ago",
|
||||
date = "1980-03-06T18:35:24.00Z",
|
||||
),
|
||||
DateForPreview(
|
||||
semantic = "One year ago",
|
||||
date = "1979-04-06T18:35:24.00Z",
|
||||
),
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl.previews
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
|
||||
class DateFormatterModeProvider : PreviewParameterProvider<DateFormatterMode> {
|
||||
override val values: Sequence<DateFormatterMode>
|
||||
get() = DateFormatterMode.entries.asSequence()
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl.previews
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.allBooleans
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun DateFormatterModeViewPreview(
|
||||
@PreviewParameter(DateFormatterModeProvider::class) dateFormatterMode: DateFormatterMode,
|
||||
) = ElementPreview {
|
||||
DateFormatterModeView(dateFormatterMode)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DateFormatterModeView(
|
||||
mode: DateFormatterMode,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val composeLocale = Locale.current
|
||||
val dateFormatter = remember {
|
||||
createFormatter(
|
||||
context = context,
|
||||
currentDate = dateForPreviewToday.date,
|
||||
locale = java.util.Locale.Builder()
|
||||
.setLanguageTag(composeLocale.toLanguageTag())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = "Mode $mode / $composeLocale",
|
||||
style = ElementTheme.typography.fontHeadingSmMedium
|
||||
)
|
||||
val today = Instant.parse(dateForPreviewToday.date).toEpochMilliseconds()
|
||||
Text(
|
||||
text = "Today is: ${dateFormatter.format(today, DateFormatterMode.Full, useRelative = false)}",
|
||||
style = ElementTheme.typography.fontHeadingSmMedium,
|
||||
)
|
||||
dateForPreviews.forEach { dateForPreview ->
|
||||
DateForPreviewItem(
|
||||
dateForPreview = dateForPreview,
|
||||
dateFormatter = dateFormatter,
|
||||
mode = mode,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DateForPreviewItem(
|
||||
dateForPreview: DateForPreview,
|
||||
dateFormatter: DefaultDateFormatter,
|
||||
mode: DateFormatterMode,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(2.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 8.dp),
|
||||
text = dateForPreview.semantic,
|
||||
style = ElementTheme.typography.fontBodyMdMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
val ts = Instant.parse(dateForPreview.date).toEpochMilliseconds()
|
||||
Row {
|
||||
Column {
|
||||
listOf("Absolute:", "Relative:").forEach { label ->
|
||||
Text(
|
||||
text = label,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
allBooleans.forEach { useRelative ->
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = dateFormatter.format(ts, mode, useRelative),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl.previews
|
||||
|
||||
import android.content.Context
|
||||
import io.element.android.libraries.dateformatter.impl.DateFormatterFull
|
||||
import io.element.android.libraries.dateformatter.impl.DateFormatterMonth
|
||||
import io.element.android.libraries.dateformatter.impl.DateFormatterTime
|
||||
import io.element.android.libraries.dateformatter.impl.DateFormatterTimeOnly
|
||||
import io.element.android.libraries.dateformatter.impl.DateFormatters
|
||||
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter
|
||||
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatterDay
|
||||
import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider
|
||||
import kotlinx.datetime.TimeZone
|
||||
import java.util.Locale
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* Create DefaultDateFormatter and set current time to the provided date.
|
||||
*/
|
||||
fun createFormatter(
|
||||
context: Context,
|
||||
currentDate: String,
|
||||
locale: Locale,
|
||||
): DefaultDateFormatter {
|
||||
val clock = PreviewClock().apply { givenInstant(Instant.parse(currentDate)) }
|
||||
val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC }
|
||||
val dateFormatters = DateFormatters(
|
||||
localeChangeObserver = {},
|
||||
clock = clock,
|
||||
timeZoneProvider = { TimeZone.UTC },
|
||||
locale = locale,
|
||||
)
|
||||
val stringProvider = PreviewStringProvider(context.resources)
|
||||
val dateFormatterDay = DefaultDateFormatterDay(
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
)
|
||||
return DefaultDateFormatter(
|
||||
dateFormatterFull = DateFormatterFull(
|
||||
stringProvider = stringProvider,
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
dateFormatterDay = dateFormatterDay,
|
||||
),
|
||||
dateFormatterMonth = DateFormatterMonth(
|
||||
stringProvider = stringProvider,
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
),
|
||||
dateFormatterDay = dateFormatterDay,
|
||||
dateFormatterTime = DateFormatterTime(
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
),
|
||||
dateFormatterTimeOnly = DateFormatterTimeOnly(
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
),
|
||||
)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl.previews
|
||||
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
class PreviewClock : Clock {
|
||||
private var instant: Instant = Instant.fromEpochMilliseconds(0)
|
||||
|
||||
fun givenInstant(instant: Instant) {
|
||||
this.instant = instant
|
||||
}
|
||||
|
||||
override fun now(): Instant = instant
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl.previews
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
|
||||
class PreviewStringProvider(
|
||||
private val resources: Resources
|
||||
) : StringProvider {
|
||||
override fun getString(@StringRes resId: Int): String {
|
||||
return resources.getString(resId)
|
||||
}
|
||||
|
||||
override fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||
return resources.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String {
|
||||
return resources.getQuantityString(resId, quantity, *formatArgs)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_this_month">"Този месец"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s v %2$s"</string>
|
||||
<string name="common_date_this_month">"Tento měsíc"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s am %2$s"</string>
|
||||
<string name="common_date_this_month">"Y mis hwn"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s kl. %2$s"</string>
|
||||
<string name="common_date_this_month">"Denne måned"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s um %2$s"</string>
|
||||
<string name="common_date_this_month">"Diesen Monat"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s στις %2$s"</string>
|
||||
<string name="common_date_this_month">"Αυτό το μήνα"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s a las %2$s"</string>
|
||||
<string name="common_date_this_month">"Este mes"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s, %2$s"</string>
|
||||
<string name="common_date_this_month">"Sel kuul"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_this_month">"Hilabete hau"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s در %2$s"</string>
|
||||
<string name="common_date_this_month">"این ماه"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s klo %2$s"</string>
|
||||
<string name="common_date_this_month">"Tässä kuussa"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s à %2$s"</string>
|
||||
<string name="common_date_this_month">"Ce mois-ci"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s, %2$s"</string>
|
||||
<string name="common_date_this_month">"Ebben a hónapban"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s pada %2$s"</string>
|
||||
<string name="common_date_this_month">"Bulan ini"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s alle %2$s"</string>
|
||||
<string name="common_date_this_month">"Questo mese"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%2$s 에 %1$s"</string>
|
||||
<string name="common_date_this_month">"이번 달"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s kl. %2$s"</string>
|
||||
<string name="common_date_this_month">"Denne måneden"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s o %2$s"</string>
|
||||
<string name="common_date_this_month">"W tym miesiącu"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s às %2$s"</string>
|
||||
<string name="common_date_this_month">"Este mês"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s às %2$s"</string>
|
||||
<string name="common_date_this_month">"Este mês"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s la %2$s"</string>
|
||||
<string name="common_date_this_month">"Luna aceasta"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s в %2$s"</string>
|
||||
<string name="common_date_this_month">"В этом месяце"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s o %2$s"</string>
|
||||
<string name="common_date_this_month">"Tento mesiac"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$svid %2$s"</string>
|
||||
<string name="common_date_this_month">"Denna månad"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s %2$s"</string>
|
||||
<string name="common_date_this_month">"Bu ay"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s о %2$s"</string>
|
||||
<string name="common_date_this_month">"Цього місяця"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$sda %2$s"</string>
|
||||
<string name="common_date_this_month">"Bu oy"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s 在 %2$s"</string>
|
||||
<string name="common_date_this_month">"本月"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s在 %2$s"</string>
|
||||
<string name="common_date_this_month">"本月"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="common_date_date_at_time">"%1$s at %2$s"</string>
|
||||
<string name="common_date_this_month">"This month"</string>
|
||||
</resources>
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import android.os.Build
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
import kotlin.time.Instant
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@Config(qualifiers = "fr", sdk = [Build.VERSION_CODES.TIRAMISU])
|
||||
class DefaultDateFormatterFrTest {
|
||||
@Test
|
||||
fun `test null`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val ts: Long? = null
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts)).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test epoch`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val ts = 0L
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("1 janvier 1970 à 00:00")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Janvier 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("1 janvier 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("01/01/1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("00:00")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test epoch relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val ts = 0L
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("1 janvier 1970 à 00:00")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Janvier 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("1 janvier 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("01/01/1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("00:00")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test now`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test now relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one second before`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:23.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one second before relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:23.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one minute before`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:34:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:34")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:34")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:34")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one minute before relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:34:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:34")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:34")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:34")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one hour before`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T17:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 17:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("17:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("17:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one hour before relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T17:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("17:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("17:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("17:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one day before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-05T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("5 avril 1980 à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Samedi 5 avril")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5 avr.")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one day before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-05T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Hier à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Hier")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Hier")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test two days before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-04T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("4 avril 1980 à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Vendredi 4 avril")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("4 avr.")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test two days before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-04T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Vendredi à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Vendredi")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("4 avr.")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one month before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-03-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 mars 1980 à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Mars 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Jeudi 6 mars")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6 mars")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one month before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-03-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Jeudi 6 mars à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Mars 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Jeudi 6 mars")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6 mars")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one year before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1979-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1979 à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("6 avril 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("06/04/1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one year before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1979-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6 avril 1979 à 18:35")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Avril 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("6 avril 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("06/04/1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||
}
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import android.os.Build
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
import kotlin.time.Instant
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@Config(qualifiers = "en", sdk = [Build.VERSION_CODES.TIRAMISU])
|
||||
class DefaultDateFormatterTest {
|
||||
@Test
|
||||
fun `test null`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val ts: Long? = null
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts)).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test epoch`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val ts = 0L
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("January 1, 1970 at 12:00 AM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("January 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("January 1, 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("01/01/1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("12:00 AM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test epoch relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val ts = 0L
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("January 1, 1970 at 12:00 AM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("January 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("January 1, 1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("01/01/1970")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("12:00 AM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test now`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday, April 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test now relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one second before`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:23.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday, April 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one second before relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:35:23.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one minute before`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:34:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:34 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday, April 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:34 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:34 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one minute before relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T18:34:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:34 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:34 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:34 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one hour before`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T17:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 5:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday, April 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("5:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one hour before relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-06T17:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("5:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("5:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("5:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one day before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-05T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 5, 1980 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Saturday, April 5")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("Apr 5")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one day before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-05T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Yesterday at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Yesterday")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Yesterday")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test two days before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-04T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 4, 1980 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Friday, April 4")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("Apr 4")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test two days before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-04-04T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Friday at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Friday")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Apr 4")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one month before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-03-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("March 6, 1980 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("March 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Thursday, March 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("Mar 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one month before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1980-03-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Thursday, March 6 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("March 1980")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Thursday, March 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Mar 6")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one year before same time`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1979-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1979 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("April 6, 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("04/06/1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test one year before same time relative`() {
|
||||
val now = "1980-04-06T18:35:24.00Z"
|
||||
val dat = "1979-04-06T18:35:24.00Z"
|
||||
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||
val formatter = createFormatter(now)
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("April 6, 1979 at 6:35 PM")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("April 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("April 6, 1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("04/06/1979")
|
||||
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import io.element.android.tests.testutils.InstrumentationStringProvider
|
||||
import kotlinx.datetime.TimeZone
|
||||
import java.util.Locale
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* Create DefaultDateFormatter and set current time to the provided date.
|
||||
*/
|
||||
fun createFormatter(currentDate: String): DefaultDateFormatter {
|
||||
val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) }
|
||||
val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC }
|
||||
val dateFormatters = DateFormatters(
|
||||
localeChangeObserver = {},
|
||||
clock = clock,
|
||||
timeZoneProvider = { TimeZone.UTC },
|
||||
locale = Locale.getDefault(),
|
||||
)
|
||||
val stringProvider = InstrumentationStringProvider()
|
||||
val dateFormatterDay = DefaultDateFormatterDay(
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
)
|
||||
return DefaultDateFormatter(
|
||||
dateFormatterFull = DateFormatterFull(
|
||||
stringProvider = stringProvider,
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
dateFormatterDay = dateFormatterDay,
|
||||
),
|
||||
dateFormatterMonth = DateFormatterMonth(
|
||||
stringProvider = stringProvider,
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
),
|
||||
dateFormatterDay = dateFormatterDay,
|
||||
dateFormatterTime = DateFormatterTime(
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
),
|
||||
dateFormatterTimeOnly = DateFormatterTimeOnly(
|
||||
localDateTimeProvider = localDateTimeProvider,
|
||||
dateFormatters = dateFormatters,
|
||||
),
|
||||
)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.impl
|
||||
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
class FakeClock : Clock {
|
||||
private var instant: Instant = Instant.fromEpochMilliseconds(0)
|
||||
|
||||
fun givenInstant(instant: Instant) {
|
||||
this.instant = instant
|
||||
}
|
||||
|
||||
override fun now(): Instant = instant
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.dateformatter.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.dateformatter.api)
|
||||
api(libs.datetime)
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.dateformatter.test
|
||||
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
|
||||
class FakeDateFormatter(
|
||||
private val formatLambda: (Long?, DateFormatterMode, Boolean) -> String = { timestamp, mode, useRelative ->
|
||||
"$timestamp $mode $useRelative"
|
||||
},
|
||||
) : DateFormatter {
|
||||
override fun format(
|
||||
timestamp: Long?,
|
||||
mode: DateFormatterMode,
|
||||
useRelative: Boolean,
|
||||
): String {
|
||||
return formatLambda(timestamp, mode, useRelative)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user