From 6c6f09ac5ac103411867f59e87bdd19fb28f30c7 Mon Sep 17 00:00:00 2001 From: Maddie Witman Date: Fri, 16 Feb 2024 06:09:00 -0500 Subject: [PATCH] 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 (cherry picked from commit 7ff95e21babda98dd1b479912278d6029cd15f0d) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt # app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt # app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt --- .../java/eu/kanade/domain/ui/UiPreferences.kt | 10 ++-- .../components/BrowseSourceEHentaiList.kt | 11 ++-- .../presentation/components/DateText.kt | 25 +++++---- .../presentation/history/HistoryScreen.kt | 4 +- .../HistoryScreenModelStateProvider.kt | 5 +- .../kanade/presentation/manga/MangaScreen.kt | 8 ++- .../screen/SettingsAppearanceScreen.kt | 4 +- .../more/settings/screen/about/AboutScreen.kt | 19 ++----- .../settings/screen/debug/WorkerInfoScreen.kt | 20 +++++-- .../presentation/reader/ChapterListDialog.kt | 15 +++-- .../presentation/track/TrackInfoDialogHome.kt | 9 +-- .../TrackInfoDialogHomePreviewProvider.kt | 7 ++- .../presentation/updates/UpdatesScreen.kt | 4 +- .../tachiyomi/source/online/all/EHentai.kt | 22 ++++++-- .../ui/history/HistoryScreenModel.kt | 12 ++-- .../ui/reader/chapter/ReaderChapterItem.kt | 4 +- .../ui/updates/UpdatesScreenModel.kt | 14 ++--- .../tachiyomi/util/lang/DateExtensions.kt | 55 ++++++------------- .../adapters/NHentaiDescriptionAdapter.kt | 10 +++- .../kotlin/exh/metadata/MetadataUtil.kt | 5 +- .../metadata/EHentaiSearchMetadata.kt | 22 ++++++-- .../metadata/NHentaiSearchMetadata.kt | 12 +++- .../metadata/TsuminoSearchMetadata.kt | 12 +++- 23 files changed, 177 insertions(+), 132 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt b/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt index 7ed0ae42c..3c732f649 100644 --- a/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt +++ b/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt @@ -8,8 +8,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.getEnum -import java.text.DateFormat -import java.text.SimpleDateFormat +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle import java.util.Locale class UiPreferences( @@ -59,9 +59,9 @@ class UiPreferences( // SY <-- companion object { - fun dateFormat(format: String): DateFormat = when (format) { - "" -> DateFormat.getDateInstance(DateFormat.SHORT) - else -> SimpleDateFormat(format, Locale.getDefault()) + fun dateFormat(format: String): DateTimeFormatter = when (format) { + "" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) + else -> DateTimeFormatter.ofPattern(format, Locale.getDefault()) } } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceEHentaiList.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceEHentaiList.kt index a5fb09630..0d91807bf 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceEHentaiList.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceEHentaiList.kt @@ -50,7 +50,8 @@ import tachiyomi.presentation.core.components.Badge import tachiyomi.presentation.core.components.BadgeGroup import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource -import java.util.Date +import java.time.Instant +import java.time.ZoneId @Composable fun BrowseSourceEHentaiList( @@ -128,9 +129,11 @@ fun BrowseSourceEHentaiListItem( } val datePosted by produceState("", metadata) { value = withIOContext { - runCatching { metadata.datePosted?.let { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) } } - .getOrNull() - .orEmpty() + runCatching { + metadata.datePosted?.let { + MetadataUtil.EX_DATE_FORMAT.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault())) + } + }.getOrNull().orEmpty() } } val genre by produceState?>(null, metadata) { diff --git a/app/src/main/java/eu/kanade/presentation/components/DateText.kt b/app/src/main/java/eu/kanade/presentation/components/DateText.kt index 9fa8c85d1..59708a049 100644 --- a/app/src/main/java/eu/kanade/presentation/components/DateText.kt +++ b/app/src/main/java/eu/kanade/presentation/components/DateText.kt @@ -4,25 +4,31 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext 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.presentation.core.i18n.stringResource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.Date +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId @Composable fun relativeDateText( dateEpochMillis: Long, ): String { return relativeDateText( - date = Date(dateEpochMillis).takeIf { dateEpochMillis > 0L }, + localDate = LocalDate.ofInstant( + Instant.ofEpochMilli(dateEpochMillis), + ZoneId.systemDefault(), + ) + .takeIf { dateEpochMillis > 0L }, ) } @Composable fun relativeDateText( - date: Date?, + localDate: LocalDate?, ): String { val context = LocalContext.current @@ -30,11 +36,10 @@ fun relativeDateText( val relativeTime = remember { preferences.relativeTime().get() } val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } - return date - ?.toRelativeString( - context = context, - relative = relativeTime, - dateFormat = dateFormat, - ) + return localDate?.toRelativeSting( + context = context, + relative = relativeTime, + dateFormat = dateFormat, + ) ?: stringResource(MR.strings.not_applicable) } diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt index ba57f500e..a973a2bb6 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt @@ -29,7 +29,7 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen -import java.util.Date +import java.time.LocalDate @Composable fun HistoryScreen( @@ -134,7 +134,7 @@ private fun HistoryScreenContent( } sealed interface HistoryUiModel { - data class Header(val date: Date) : HistoryUiModel + data class Header(val date: LocalDate) : HistoryUiModel data class Item(val item: HistoryWithRelations) : HistoryUiModel } diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreenModelStateProvider.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreenModelStateProvider.kt index c948ae1a7..835bef899 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreenModelStateProvider.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreenModelStateProvider.kt @@ -7,6 +7,7 @@ import kotlinx.collections.immutable.toImmutableList import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.manga.model.MangaCover import java.time.Instant +import java.time.LocalDate import java.time.temporal.ChronoUnit import java.util.Date import kotlin.random.Random @@ -73,10 +74,10 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider Instant = { it }) = - HistoryUiModel.Header(Date.from(instantBuilder(Instant.now()))) + HistoryUiModel.Header(LocalDate.from(instantBuilder(Instant.now()))) fun items() = sequence { var count = 1 diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index d77d17833..0f1cc335b 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -106,7 +106,8 @@ import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrollingUp import tachiyomi.source.local.isLocal import java.time.Instant -import java.util.Date +import java.time.ZoneId +import java.time.ZonedDateTime @Composable fun MangaScreen( @@ -986,9 +987,10 @@ private fun LazyListScope.sharedChapterItems( ?.let { // SY --> if (manga.isEhBasedManga()) { - MetadataUtil.EX_DATE_FORMAT.format(Date(it)) + MetadataUtil.EX_DATE_FORMAT + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())) } else { - relativeDateText(Date(item.chapter.dateUpload)) + relativeDateText(item.chapter.dateUpload) } // SY <-- }, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt index f61e9003b..36e03eef5 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt @@ -28,7 +28,7 @@ import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.time.Instant +import java.time.LocalDate object SettingsAppearanceScreen : SearchableSettings { @@ -107,7 +107,7 @@ object SettingsAppearanceScreen : SearchableSettings { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow - val now = remember { Instant.now().toEpochMilli() } + val now = remember { LocalDate.now() } val dateFormat by uiPreferences.dateFormat().collectAsState() val formattedNow = remember(dateFormat) { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt index c1875c892..4efb3538b 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt @@ -56,10 +56,10 @@ import tachiyomi.presentation.core.icons.Reddit import tachiyomi.presentation.core.icons.X import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.DateFormat -import java.text.SimpleDateFormat +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter import java.util.Locale -import java.util.TimeZone object AboutScreen : Screen() { @@ -293,16 +293,9 @@ object AboutScreen : Screen() { internal fun getFormattedBuildTime(): String { return try { - val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US) - inputDf.timeZone = TimeZone.getTimeZone("UTC") - val buildTime = inputDf.parse(BuildConfig.BUILD_TIME) - - val outputDf = DateFormat.getDateTimeInstance( - DateFormat.MEDIUM, - DateFormat.SHORT, - Locale.getDefault(), - ) - outputDf.timeZone = TimeZone.getDefault() + val df = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'", Locale.US) + .withZone(ZoneId.of("UTC")) + val buildTime = LocalDateTime.from(df.parse(BuildConfig.BUILD_TIME)) buildTime!!.toDateTimestampString(UiPreferences.dateFormat(Injekt.get().dateFormat().get())) } catch (e: Exception) { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt index ea8db81e8..dbe82830d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt @@ -42,7 +42,9 @@ import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.plus import uy.kohesive.injekt.Injekt 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() { @@ -149,11 +151,17 @@ class WorkerInfoScreen : Screen() { appendLine("State: ${workInfo.state}") if (workInfo.state == WorkInfo.State.ENQUEUED) { appendLine( - "Next scheduled run: ${Date(workInfo.nextScheduleTimeMillis).toDateTimestampString( - UiPreferences.dateFormat( - Injekt.get().dateFormat().get(), - ), - )}", + "Next scheduled run: ${ + LocalDateTime.ofInstant( + Instant.ofEpochMilli(workInfo.nextScheduleTimeMillis), + ZoneId.systemDefault(), + ) + .toDateTimestampString( + UiPreferences.dateFormat( + Injekt.get().dateFormat().get(), + ), + ) + }", ) appendLine("Attempt #${workInfo.runAttemptCount + 1}") } diff --git a/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt index 29d93ac15..b03a9ad4f 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ChapterListDialog.kt @@ -16,13 +16,16 @@ import eu.kanade.presentation.manga.components.MangaChapterListItem import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.reader.chapter.ReaderChapterItem import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel -import eu.kanade.tachiyomi.util.lang.toRelativeString +import eu.kanade.tachiyomi.util.lang.toRelativeSting import exh.metadata.MetadataUtil import exh.source.isEhBasedManga import kotlinx.collections.immutable.ImmutableList import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.library.service.LibraryPreferences -import java.util.Date +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.time.ZonedDateTime @Composable fun ChapterListDialog( @@ -56,9 +59,13 @@ fun ChapterListDialog( ?.let { // SY --> if (manga?.isEhBasedManga() == true) { - MetadataUtil.EX_DATE_FORMAT.format(Date(it)) + MetadataUtil.EX_DATE_FORMAT + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())) } else { - Date(it).toRelativeString(context, dateRelativeTime, chapterItem.dateFormat) + LocalDate.ofInstant( + Instant.ofEpochMilli(it), + ZoneId.systemDefault(), + ).toRelativeSting(context, dateRelativeTime, chapterItem.dateFormat) } // SY <-- }, diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt index 11f2b9a06..993532180 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt @@ -52,17 +52,18 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.ui.manga.track.TrackItem +import eu.kanade.tachiyomi.util.lang.toLocalDate import eu.kanade.tachiyomi.util.system.copyToClipboard import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource -import java.text.DateFormat +import java.time.format.DateTimeFormatter private const val UnsetStatusTextAlpha = 0.5F @Composable fun TrackInfoDialogHome( trackItems: List, - dateFormat: DateFormat, + dateFormat: DateTimeFormatter, onStatusClick: (TrackItem) -> Unit, onChapterClick: (TrackItem) -> Unit, onScoreClick: (TrackItem) -> Unit, @@ -104,11 +105,11 @@ fun TrackInfoDialogHome( .takeIf { supportsScoring && item.track.score != 0.0 }, onScoreClick = { onScoreClick(item) } .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 }, onStartDateClick = { onStartDateEdit(item) } // TODO .takeIf { supportsReadingDates }, - endDate = dateFormat.format(item.track.finishDate) + endDate = dateFormat.format(item.track.finishDate.toLocalDate()) .takeIf { supportsReadingDates && item.track.finishDate != 0L }, onEndDateClick = { onEndDateEdit(item) } .takeIf { supportsReadingDates }, diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt index 51b7ca3f8..aa58403ea 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt @@ -5,7 +5,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.test.DummyTracker import tachiyomi.domain.track.model.Track -import java.text.DateFormat +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle internal class TrackInfoDialogHomePreviewProvider : PreviewParameterProvider<@Composable () -> Unit> { @@ -46,7 +47,7 @@ internal class TrackInfoDialogHomePreviewProvider : trackItemWithoutTrack, trackItemWithTrack, ), - dateFormat = DateFormat.getDateInstance(), + dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM), onStatusClick = {}, onChapterClick = {}, onScoreClick = {}, @@ -61,7 +62,7 @@ internal class TrackInfoDialogHomePreviewProvider : private val noTrackers = @Composable { TrackInfoDialogHome( trackItems = listOf(), - dateFormat = DateFormat.getDateInstance(), + dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM), onStatusClick = {}, onChapterClick = {}, onScoreClick = {}, diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index 219d3158c..4df8622c8 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -36,7 +36,7 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen -import java.util.Date +import java.time.LocalDate import kotlin.time.Duration.Companion.seconds @Composable @@ -212,6 +212,6 @@ private fun UpdatesBottomBar( } sealed interface UpdatesUiModel { - data class Header(val date: Date) : UpdatesUiModel + data class Header(val date: LocalDate) : UpdatesUiModel data class Item(val item: UpdatesItem) : UpdatesUiModel } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt index 2bb0ab04d..989fce581 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt @@ -89,6 +89,8 @@ import uy.kohesive.injekt.injectLazy import java.io.ByteArrayOutputStream import java.io.IOException import java.net.URLEncoder +import java.time.ZoneOffset +import java.time.ZonedDateTime // TODO Consider gallery updating when doing tabbed browsing class EHentai( @@ -271,8 +273,9 @@ class EHentai( private fun getDateTag(element: Element?): Long? { val text = element?.text()?.nullIfBlank() return if (text != null) { - val date = MetadataUtil.EX_DATE_FORMAT.parse(text) - date?.time + println(text) + val date = ZonedDateTime.parse(text, MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC)) + date?.toInstant()?.toEpochMilli() } else { null } @@ -376,11 +379,12 @@ class EHentai( url = EHentaiSearchMetadata.normalizeUrl(location), name = "v1: " + doc.selectFirst("#gn")!!.text(), chapter_number = 1f, - date_upload = MetadataUtil.EX_DATE_FORMAT.parse( + date_upload = ZonedDateTime.parse( doc.select("#gdd .gdt1").find { el -> el.text().lowercase() == "posted:" }!!.nextElementSibling()!!.text(), - )!!.time, + MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) + )!!.toInstant().toEpochMilli(), scanlator = EHentaiSearchMetadata.galleryId(location), ) // Build and append the rest of the galleries @@ -395,7 +399,10 @@ class EHentai( url = EHentaiSearchMetadata.normalizeUrl(link), name = "v${index + 2}: $name", chapter_number = index + 2f, - date_upload = MetadataUtil.EX_DATE_FORMAT.parse(posted)!!.time, + date_upload = ZonedDateTime.parse( + posted, + MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) + ).toInstant().toEpochMilli(), scanlator = EHentaiSearchMetadata.galleryId(link), ) }.reversed() + self @@ -706,7 +713,10 @@ class EHentai( if (left != null && right != null) { ignore { when (left.removeSuffix(":").lowercase()) { - "posted" -> datePosted = MetadataUtil.EX_DATE_FORMAT.parse(right)!!.time + "posted" -> datePosted = ZonedDateTime.parse( + right, + MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) + ).toInstant().toEpochMilli() // Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/ // Example JP gallery: https://exhentai.org/g/1375385/03519d541b/ // Parent is older variation of the gallery diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt index 5a0ae41d0..f9dfb07c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt @@ -5,7 +5,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.core.util.insertSeparators import eu.kanade.presentation.history.HistoryUiModel -import eu.kanade.tachiyomi.util.lang.toDateKey +import eu.kanade.tachiyomi.util.lang.toLocalDate import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers @@ -30,7 +30,7 @@ import tachiyomi.domain.history.interactor.RemoveHistory import tachiyomi.domain.history.model.HistoryWithRelations import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.Date +import java.time.LocalDate class HistoryScreenModel( private val getHistory: GetHistory = Injekt.get(), @@ -62,10 +62,12 @@ class HistoryScreenModel( private fun List.toHistoryUiModels(): List { return map { HistoryUiModel.Item(it) } .insertSeparators { before, after -> - val beforeDate = before?.item?.readAt?.time?.toDateKey() ?: Date(0) - val afterDate = after?.item?.readAt?.time?.toDateKey() ?: Date(0) + val beforeDate = before?.item?.readAt?.time?.toLocalDate() ?: LocalDate.MIN + val afterDate = after?.item?.readAt?.time?.toLocalDate() ?: LocalDate.MIN 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. else -> null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterItem.kt index 59a0f1770..0c115084e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterItem.kt @@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.reader.chapter import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.manga.model.Manga -import java.text.DateFormat +import java.time.format.DateTimeFormatter data class ReaderChapterItem( val chapter: Chapter, val manga: Manga, val isCurrent: Boolean, - val dateFormat: DateFormat, + val dateFormat: DateTimeFormatter, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt index f1ae2eb0a..cc1f93458 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt @@ -17,7 +17,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences -import eu.kanade.tachiyomi.util.lang.toDateKey +import eu.kanade.tachiyomi.util.lang.toLocalDate import exh.source.EH_SOURCE_ID import exh.source.EXH_SOURCE_ID import kotlinx.collections.immutable.PersistentList @@ -48,8 +48,8 @@ import tachiyomi.domain.updates.interactor.GetUpdates import tachiyomi.domain.updates.model.UpdatesWithRelations import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.time.LocalDate import java.time.ZonedDateTime -import java.util.Date class UpdatesScreenModel( private val sourceManager: SourceManager = Injekt.get(), @@ -386,12 +386,12 @@ class UpdatesScreenModel( return items .map { UpdatesUiModel.Item(it) } .insertSeparators { before, after -> - val beforeDate = before?.item?.update?.dateFetch?.toDateKey() ?: Date(0) - val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0) + val beforeDate = before?.item?.update?.dateFetch?.toLocalDate() ?: LocalDate.MIN + val afterDate = after?.item?.update?.dateFetch?.toLocalDate() ?: LocalDate.MIN when { - beforeDate.time != afterDate.time && afterDate.time != 0L -> { - UpdatesUiModel.Header(afterDate) - } + beforeDate.isAfter(afterDate) + or afterDate.equals(LocalDate.MIN) + or beforeDate.equals(LocalDate.MIN) -> UpdatesUiModel.Header(afterDate) // Return null to avoid adding a separator between two items. else -> null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt index f6ca6bb5e..e1a1ea62f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt @@ -6,15 +6,17 @@ import tachiyomi.core.common.i18n.stringResource import tachiyomi.i18n.MR import java.text.DateFormat import java.time.Instant +import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle import java.time.temporal.ChronoUnit -import java.util.Calendar import java.util.Date -fun Date.toDateTimestampString(dateFormatter: DateFormat): String { - val date = dateFormatter.format(this) - val time = DateFormat.getTimeInstance(DateFormat.SHORT).format(this) +fun LocalDateTime.toDateTimestampString(dateTimeFormatter: DateTimeFormatter): String { + val date = dateTimeFormatter.format(this) + val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(this) return "$date $time" } @@ -32,51 +34,28 @@ fun Long.convertEpochMillisZone( .toEpochMilli() } -/** - * Get date as time key - * - * @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 Long.toLocalDate(): LocalDate { + return LocalDate.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault()) } -fun Date.toRelativeString( +fun LocalDate.toRelativeSting( context: Context, relative: Boolean = true, - dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT), + dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT), ): String { if (!relative) { return dateFormat.format(this) } - val now = Date() - val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt() + val now = LocalDate.now() + val difference = ChronoUnit.DAYS.between(this, now) return when { - difference < 0 -> dateFormat.format(this) - difference < MILLISECONDS_IN_DAY -> context.stringResource(MR.strings.relative_time_today) - difference < MILLISECONDS_IN_DAY.times(7) -> context.pluralStringResource( + difference < 0 -> difference.toString() + difference < 1 -> context.stringResource(MR.strings.relative_time_today) + difference < 7 -> context.pluralStringResource( MR.plurals.relative_time, - days, - days, + difference.toInt(), + difference.toInt(), ) 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 -} diff --git a/app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt index 519d8ed96..17d635663 100644 --- a/app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt +++ b/app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt @@ -18,7 +18,9 @@ import tachiyomi.core.common.i18n.pluralStringResource import tachiyomi.core.common.i18n.stringResource import tachiyomi.i18n.MR import tachiyomi.i18n.sy.SYMR -import java.util.Date +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime @Composable fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) { @@ -50,7 +52,11 @@ fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) { binding.favorites.bindDrawable(context, R.drawable.ic_book_24dp) } - binding.whenPosted.text = MetadataUtil.EX_DATE_FORMAT.format(Date((meta.uploadDate ?: 0) * 1000)) + binding.whenPosted.text = MetadataUtil.EX_DATE_FORMAT + .format( + ZonedDateTime + .ofInstant(Instant.ofEpochSecond(meta.uploadDate ?: 0), ZoneId.systemDefault()) + ) binding.pages.text = context.pluralStringResource( SYMR.plurals.num_pages, diff --git a/source-api/src/commonMain/kotlin/exh/metadata/MetadataUtil.kt b/source-api/src/commonMain/kotlin/exh/metadata/MetadataUtil.kt index d8576723c..76e8b4be3 100644 --- a/source-api/src/commonMain/kotlin/exh/metadata/MetadataUtil.kt +++ b/source-api/src/commonMain/kotlin/exh/metadata/MetadataUtil.kt @@ -1,7 +1,6 @@ package exh.metadata -import java.text.SimpleDateFormat -import java.util.Locale +import java.time.format.DateTimeFormatter import kotlin.math.ln import kotlin.math.pow @@ -55,5 +54,5 @@ object MetadataUtil { "wip", ) - val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) + val EX_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") } diff --git a/source-api/src/commonMain/kotlin/exh/metadata/metadata/EHentaiSearchMetadata.kt b/source-api/src/commonMain/kotlin/exh/metadata/metadata/EHentaiSearchMetadata.kt index 2abb1410e..38b772265 100644 --- a/source-api/src/commonMain/kotlin/exh/metadata/metadata/EHentaiSearchMetadata.kt +++ b/source-api/src/commonMain/kotlin/exh/metadata/metadata/EHentaiSearchMetadata.kt @@ -12,7 +12,9 @@ import tachiyomi.i18n.MR import tachiyomi.i18n.sy.SYMR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.Date +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime @Serializable class EHentaiSearchMetadata : RaisedSearchMetadata() { @@ -99,7 +101,13 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { getItem(title) { stringResource(MR.strings.title) }, getItem(altTitle) { stringResource(SYMR.strings.alt_title) }, getItem(genre) { stringResource(SYMR.strings.genre) }, - getItem(datePosted, { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) }) { + getItem( + datePosted, + { + MetadataUtil.EX_DATE_FORMAT + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())) + }, + ) { stringResource(SYMR.strings.date_posted) }, getItem(parent) { stringResource(SYMR.strings.parent) }, @@ -114,9 +122,13 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { getItem(ratingCount) { stringResource(SYMR.strings.total_ratings) }, getItem(averageRating) { stringResource(SYMR.strings.average_rating) }, getItem(aged) { stringResource(SYMR.strings.aged) }, - getItem(lastUpdateCheck, { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) }) { - stringResource(SYMR.strings.last_update_check) - }, + getItem( + lastUpdateCheck, + { + MetadataUtil.EX_DATE_FORMAT + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())) + }, + ) { stringResource(SYMR.strings.last_update_check) }, ) } } diff --git a/source-api/src/commonMain/kotlin/exh/metadata/metadata/NHentaiSearchMetadata.kt b/source-api/src/commonMain/kotlin/exh/metadata/metadata/NHentaiSearchMetadata.kt index 9921997b5..6e4949e9f 100644 --- a/source-api/src/commonMain/kotlin/exh/metadata/metadata/NHentaiSearchMetadata.kt +++ b/source-api/src/commonMain/kotlin/exh/metadata/metadata/NHentaiSearchMetadata.kt @@ -8,7 +8,9 @@ import kotlinx.serialization.Serializable import tachiyomi.core.common.i18n.stringResource import tachiyomi.i18n.MR import tachiyomi.i18n.sy.SYMR -import java.util.Date +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime @Serializable class NHentaiSearchMetadata : RaisedSearchMetadata() { @@ -92,7 +94,13 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { return with(context) { listOfNotNull( getItem(nhId) { stringResource(SYMR.strings.id) }, - getItem(uploadDate, { MetadataUtil.EX_DATE_FORMAT.format(Date(it * 1000)) }) { + getItem( + uploadDate, + { + MetadataUtil.EX_DATE_FORMAT + .format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(it), ZoneId.systemDefault())) + }, + ) { stringResource(SYMR.strings.date_posted) }, getItem(favoritesCount) { stringResource(SYMR.strings.total_favorites) }, diff --git a/source-api/src/commonMain/kotlin/exh/metadata/metadata/TsuminoSearchMetadata.kt b/source-api/src/commonMain/kotlin/exh/metadata/metadata/TsuminoSearchMetadata.kt index 14b35405c..f644381de 100644 --- a/source-api/src/commonMain/kotlin/exh/metadata/metadata/TsuminoSearchMetadata.kt +++ b/source-api/src/commonMain/kotlin/exh/metadata/metadata/TsuminoSearchMetadata.kt @@ -11,7 +11,9 @@ import tachiyomi.core.common.i18n.stringResource import tachiyomi.i18n.MR import tachiyomi.i18n.sy.SYMR import java.text.SimpleDateFormat -import java.util.Date +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime import java.util.Locale @Serializable @@ -73,7 +75,13 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() { getItem(tmId) { stringResource(SYMR.strings.id) }, getItem(title) { stringResource(MR.strings.title) }, getItem(uploader) { stringResource(SYMR.strings.uploader) }, - getItem(uploadDate, { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) }) { + getItem( + uploadDate, + { + MetadataUtil.EX_DATE_FORMAT + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())) + }, + ) { stringResource(SYMR.strings.date_posted) }, getItem(length) { stringResource(SYMR.strings.page_count) },