Refactor use of Java.util.date to Java.time.*, to fix localized date issues. (#402)

* Add support for localdate based relative times

* Update History Screen to use new localdate based relative times

* Update Updates Screen to use new localdate based relative times

* Cleaned up date util classes

* Updated build time display

* Code cleanup

* Fixed crash in settings

* Updated Preferences item

* Worker Info works

* Fixed Tracker date display

* Code changes to pass detekt
This commit is contained in:
Maddie Witman 2024-02-16 06:09:00 -05:00 committed by GitHub
parent 96c236e5c3
commit 7ff95e21ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 89 additions and 100 deletions

View file

@ -7,8 +7,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum import tachiyomi.core.common.preference.getEnum
import java.text.DateFormat import java.time.format.DateTimeFormatter
import java.text.SimpleDateFormat import java.time.format.FormatStyle
import java.util.Locale import java.util.Locale
class UiPreferences( class UiPreferences(
@ -31,9 +31,9 @@ class UiPreferences(
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC) fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
companion object { companion object {
fun dateFormat(format: String): DateFormat = when (format) { fun dateFormat(format: String): DateTimeFormatter = when (format) {
"" -> DateFormat.getDateInstance(DateFormat.SHORT) "" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
else -> SimpleDateFormat(format, Locale.getDefault()) else -> DateTimeFormatter.ofPattern(format, Locale.getDefault())
} }
} }
} }

View file

@ -4,25 +4,31 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toRelativeSting
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
@Composable @Composable
fun relativeDateText( fun relativeDateText(
dateEpochMillis: Long, dateEpochMillis: Long,
): String { ): String {
return relativeDateText( return relativeDateText(
date = Date(dateEpochMillis).takeIf { dateEpochMillis > 0L }, localDate = LocalDate.ofInstant(
Instant.ofEpochMilli(dateEpochMillis),
ZoneId.systemDefault(),
)
.takeIf { dateEpochMillis > 0L },
) )
} }
@Composable @Composable
fun relativeDateText( fun relativeDateText(
date: Date?, localDate: LocalDate?,
): String { ): String {
val context = LocalContext.current val context = LocalContext.current
@ -30,11 +36,10 @@ fun relativeDateText(
val relativeTime = remember { preferences.relativeTime().get() } val relativeTime = remember { preferences.relativeTime().get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
return date return localDate?.toRelativeSting(
?.toRelativeString( context = context,
context = context, relative = relativeTime,
relative = relativeTime, dateFormat = dateFormat,
dateFormat = dateFormat, )
)
?: stringResource(MR.strings.not_applicable) ?: stringResource(MR.strings.not_applicable)
} }

View file

@ -28,7 +28,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import java.util.Date import java.time.LocalDate
@Composable @Composable
fun HistoryScreen( fun HistoryScreen(
@ -133,7 +133,7 @@ private fun HistoryScreenContent(
} }
sealed interface HistoryUiModel { sealed interface HistoryUiModel {
data class Header(val date: Date) : HistoryUiModel data class Header(val date: LocalDate) : HistoryUiModel
data class Item(val item: HistoryWithRelations) : HistoryUiModel data class Item(val item: HistoryWithRelations) : HistoryUiModel
} }

View file

@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
import java.time.Instant import java.time.Instant
import java.time.LocalDate
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.Date import java.util.Date
import kotlin.random.Random import kotlin.random.Random
@ -71,10 +72,10 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
private object HistoryUiModelExamples { private object HistoryUiModelExamples {
val headerToday = header() val headerToday = header()
val headerTomorrow = val headerTomorrow =
HistoryUiModel.Header(Date.from(Instant.now().plus(1, ChronoUnit.DAYS))) HistoryUiModel.Header(LocalDate.now().plusDays(1))
fun header(instantBuilder: (Instant) -> Instant = { it }) = fun header(instantBuilder: (Instant) -> Instant = { it }) =
HistoryUiModel.Header(Date.from(instantBuilder(Instant.now()))) HistoryUiModel.Header(LocalDate.from(instantBuilder(Instant.now())))
fun items() = sequence { fun items() = sequence {
var count = 1 var count = 1

View file

@ -26,7 +26,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.time.Instant import java.time.LocalDate
object SettingsAppearanceScreen : SearchableSettings { object SettingsAppearanceScreen : SearchableSettings {
@ -101,7 +101,7 @@ object SettingsAppearanceScreen : SearchableSettings {
val context = LocalContext.current val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val now = remember { Instant.now().toEpochMilli() } val now = remember { LocalDate.now() }
val dateFormat by uiPreferences.dateFormat().collectAsState() val dateFormat by uiPreferences.dateFormat().collectAsState()
val formattedNow = remember(dateFormat) { val formattedNow = remember(dateFormat) {

View file

@ -55,10 +55,10 @@ import tachiyomi.presentation.core.icons.Reddit
import tachiyomi.presentation.core.icons.X import tachiyomi.presentation.core.icons.X
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.DateFormat import java.time.LocalDateTime
import java.text.SimpleDateFormat import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale import java.util.Locale
import java.util.TimeZone
object AboutScreen : Screen() { object AboutScreen : Screen() {
@ -269,16 +269,9 @@ object AboutScreen : Screen() {
internal fun getFormattedBuildTime(): String { internal fun getFormattedBuildTime(): String {
return try { return try {
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US) val df = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'", Locale.US)
inputDf.timeZone = TimeZone.getTimeZone("UTC") .withZone(ZoneId.of("UTC"))
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME) val buildTime = LocalDateTime.from(df.parse(BuildConfig.BUILD_TIME))
val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.SHORT,
Locale.getDefault(),
)
outputDf.timeZone = TimeZone.getDefault()
buildTime!!.toDateTimestampString(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get())) buildTime!!.toDateTimestampString(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()))
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -42,7 +42,9 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.plus import tachiyomi.presentation.core.util.plus
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
class WorkerInfoScreen : Screen() { class WorkerInfoScreen : Screen() {
@ -149,11 +151,17 @@ class WorkerInfoScreen : Screen() {
appendLine("State: ${workInfo.state}") appendLine("State: ${workInfo.state}")
if (workInfo.state == WorkInfo.State.ENQUEUED) { if (workInfo.state == WorkInfo.State.ENQUEUED) {
appendLine( appendLine(
"Next scheduled run: ${Date(workInfo.nextScheduleTimeMillis).toDateTimestampString( "Next scheduled run: ${
UiPreferences.dateFormat( LocalDateTime.ofInstant(
Injekt.get<UiPreferences>().dateFormat().get(), Instant.ofEpochMilli(workInfo.nextScheduleTimeMillis),
), ZoneId.systemDefault(),
)}", )
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(),
),
)
}",
) )
appendLine("Attempt #${workInfo.runAttemptCount + 1}") appendLine("Attempt #${workInfo.runAttemptCount + 1}")
} }

View file

@ -52,17 +52,18 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.lang.toLocalDate
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.text.DateFormat import java.time.format.DateTimeFormatter
private const val UnsetStatusTextAlpha = 0.5F private const val UnsetStatusTextAlpha = 0.5F
@Composable @Composable
fun TrackInfoDialogHome( fun TrackInfoDialogHome(
trackItems: List<TrackItem>, trackItems: List<TrackItem>,
dateFormat: DateFormat, dateFormat: DateTimeFormatter,
onStatusClick: (TrackItem) -> Unit, onStatusClick: (TrackItem) -> Unit,
onChapterClick: (TrackItem) -> Unit, onChapterClick: (TrackItem) -> Unit,
onScoreClick: (TrackItem) -> Unit, onScoreClick: (TrackItem) -> Unit,
@ -104,11 +105,11 @@ fun TrackInfoDialogHome(
.takeIf { supportsScoring && item.track.score != 0.0 }, .takeIf { supportsScoring && item.track.score != 0.0 },
onScoreClick = { onScoreClick(item) } onScoreClick = { onScoreClick(item) }
.takeIf { supportsScoring }, .takeIf { supportsScoring },
startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) } startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate.toLocalDate()) }
.takeIf { supportsReadingDates && item.track.startDate != 0L }, .takeIf { supportsReadingDates && item.track.startDate != 0L },
onStartDateClick = { onStartDateEdit(item) } // TODO onStartDateClick = { onStartDateEdit(item) } // TODO
.takeIf { supportsReadingDates }, .takeIf { supportsReadingDates },
endDate = dateFormat.format(item.track.finishDate) endDate = dateFormat.format(item.track.finishDate.toLocalDate())
.takeIf { supportsReadingDates && item.track.finishDate != 0L }, .takeIf { supportsReadingDates && item.track.finishDate != 0L },
onEndDateClick = { onEndDateEdit(item) } onEndDateClick = { onEndDateEdit(item) }
.takeIf { supportsReadingDates }, .takeIf { supportsReadingDates },

View file

@ -5,7 +5,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.test.DummyTracker import eu.kanade.test.DummyTracker
import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.model.Track
import java.text.DateFormat import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
internal class TrackInfoDialogHomePreviewProvider : internal class TrackInfoDialogHomePreviewProvider :
PreviewParameterProvider<@Composable () -> Unit> { PreviewParameterProvider<@Composable () -> Unit> {
@ -46,7 +47,7 @@ internal class TrackInfoDialogHomePreviewProvider :
trackItemWithoutTrack, trackItemWithoutTrack,
trackItemWithTrack, trackItemWithTrack,
), ),
dateFormat = DateFormat.getDateInstance(), dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
onStatusClick = {}, onStatusClick = {},
onChapterClick = {}, onChapterClick = {},
onScoreClick = {}, onScoreClick = {},
@ -61,7 +62,7 @@ internal class TrackInfoDialogHomePreviewProvider :
private val noTrackers = @Composable { private val noTrackers = @Composable {
TrackInfoDialogHome( TrackInfoDialogHome(
trackItems = listOf(), trackItems = listOf(),
dateFormat = DateFormat.getDateInstance(), dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
onStatusClick = {}, onStatusClick = {},
onChapterClick = {}, onChapterClick = {},
onScoreClick = {}, onScoreClick = {},

View file

@ -36,7 +36,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import java.util.Date import java.time.LocalDate
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@Composable @Composable
@ -206,6 +206,6 @@ private fun UpdatesBottomBar(
} }
sealed interface UpdatesUiModel { sealed interface UpdatesUiModel {
data class Header(val date: Date) : UpdatesUiModel data class Header(val date: LocalDate) : UpdatesUiModel
data class Item(val item: UpdatesItem) : UpdatesUiModel data class Item(val item: UpdatesItem) : UpdatesUiModel
} }

View file

@ -5,7 +5,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.util.insertSeparators import eu.kanade.core.util.insertSeparators
import eu.kanade.presentation.history.HistoryUiModel import eu.kanade.presentation.history.HistoryUiModel
import eu.kanade.tachiyomi.util.lang.toDateKey import eu.kanade.tachiyomi.util.lang.toLocalDate
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -28,7 +28,7 @@ import tachiyomi.domain.history.interactor.RemoveHistory
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.LocalDate
class HistoryScreenModel( class HistoryScreenModel(
private val getHistory: GetHistory = Injekt.get(), private val getHistory: GetHistory = Injekt.get(),
@ -60,10 +60,12 @@ class HistoryScreenModel(
private fun List<HistoryWithRelations>.toHistoryUiModels(): List<HistoryUiModel> { private fun List<HistoryWithRelations>.toHistoryUiModels(): List<HistoryUiModel> {
return map { HistoryUiModel.Item(it) } return map { HistoryUiModel.Item(it) }
.insertSeparators { before, after -> .insertSeparators { before, after ->
val beforeDate = before?.item?.readAt?.time?.toDateKey() ?: Date(0) val beforeDate = before?.item?.readAt?.time?.toLocalDate() ?: LocalDate.MIN
val afterDate = after?.item?.readAt?.time?.toDateKey() ?: Date(0) val afterDate = after?.item?.readAt?.time?.toLocalDate() ?: LocalDate.MIN
when { when {
beforeDate.time != afterDate.time && afterDate.time != 0L -> HistoryUiModel.Header(afterDate) beforeDate.isAfter(afterDate)
or afterDate.equals(LocalDate.MIN)
or beforeDate.equals(LocalDate.MIN) -> HistoryUiModel.Header(afterDate)
// Return null to avoid adding a separator between two items. // Return null to avoid adding a separator between two items.
else -> null else -> null
} }

View file

@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.util.lang.toDateKey import eu.kanade.tachiyomi.util.lang.toLocalDate
import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -45,8 +45,8 @@ import tachiyomi.domain.updates.interactor.GetUpdates
import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.model.UpdatesWithRelations
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.time.LocalDate
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.Date
class UpdatesScreenModel( class UpdatesScreenModel(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
@ -374,12 +374,12 @@ class UpdatesScreenModel(
return items return items
.map { UpdatesUiModel.Item(it) } .map { UpdatesUiModel.Item(it) }
.insertSeparators { before, after -> .insertSeparators { before, after ->
val beforeDate = before?.item?.update?.dateFetch?.toDateKey() ?: Date(0) val beforeDate = before?.item?.update?.dateFetch?.toLocalDate() ?: LocalDate.MIN
val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0) val afterDate = after?.item?.update?.dateFetch?.toLocalDate() ?: LocalDate.MIN
when { when {
beforeDate.time != afterDate.time && afterDate.time != 0L -> { beforeDate.isAfter(afterDate)
UpdatesUiModel.Header(afterDate) or afterDate.equals(LocalDate.MIN)
} or beforeDate.equals(LocalDate.MIN) -> UpdatesUiModel.Header(afterDate)
// Return null to avoid adding a separator between two items. // Return null to avoid adding a separator between two items.
else -> null else -> null
} }

View file

@ -6,15 +6,17 @@ import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import java.text.DateFormat import java.text.DateFormat
import java.time.Instant import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.Calendar
import java.util.Date import java.util.Date
fun Date.toDateTimestampString(dateFormatter: DateFormat): String { fun LocalDateTime.toDateTimestampString(dateTimeFormatter: DateTimeFormatter): String {
val date = dateFormatter.format(this) val date = dateTimeFormatter.format(this)
val time = DateFormat.getTimeInstance(DateFormat.SHORT).format(this) val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(this)
return "$date $time" return "$date $time"
} }
@ -32,52 +34,28 @@ fun Long.convertEpochMillisZone(
.toEpochMilli() .toEpochMilli()
} }
/** fun Long.toLocalDate(): LocalDate {
* Get date as time key return LocalDate.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
*
* @param date desired date
* @return date as time key
*/
fun Long.toDateKey(): Date {
val instant = Instant.ofEpochMilli(this)
return Date.from(instant.truncatedTo(ChronoUnit.DAYS))
} }
fun Date.toRelativeString( fun LocalDate.toRelativeSting(
context: Context, context: Context,
relative: Boolean = true, relative: Boolean = true,
dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT), dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT),
): String { ): String {
if (!relative) { if (!relative) {
return dateFormat.format(this) return dateFormat.format(this)
} }
val now = Date() val now = LocalDate.now()
val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - val difference = ChronoUnit.DAYS.between(this, now)
this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY)
val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt()
return when { return when {
difference < 0 -> dateFormat.format(this) difference < 0 -> difference.toString()
difference < MILLISECONDS_IN_DAY -> context.stringResource(MR.strings.relative_time_today) difference < 1 -> context.stringResource(MR.strings.relative_time_today)
difference < MILLISECONDS_IN_DAY.times(7) -> context.pluralStringResource( difference < 7 -> context.pluralStringResource(
MR.plurals.relative_time, MR.plurals.relative_time,
days, difference.toInt(),
days, difference.toInt(),
) )
else -> dateFormat.format(this) else -> dateFormat.format(this)
} }
} }
private const val MILLISECONDS_IN_DAY = 86_400_000L
private val Date.timeWithOffset: Long
get() {
return Calendar.getInstance().run {
time = this@timeWithOffset
val dstOffset = get(Calendar.DST_OFFSET)
this@timeWithOffset.time + timeZone.rawOffset + dstOffset
}
}
private fun Long.floorNearest(to: Long): Long {
return this.floorDiv(to) * to
}