mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-2793 Date as an Object | Epic (#1782)
Co-authored-by: Evgenii Kozlov <enklave.mare.balticum@protonmail.com> Co-authored-by: Evgenii Kozlov <ubuphobos@gmail.com>
This commit is contained in:
parent
2b40f21910
commit
ca8721b725
284 changed files with 6589 additions and 1211 deletions
|
@ -21,6 +21,8 @@ dependencies {
|
|||
testImplementation libs.androidXTestCore
|
||||
testImplementation libs.robolectric
|
||||
testImplementation libs.mockitoKotlin
|
||||
testImplementation libs.coroutineTesting
|
||||
testImplementation libs.turbine
|
||||
|
||||
compileOnly libs.javaxInject
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.anytypeio.anytype.device.providers
|
||||
|
||||
import com.anytypeio.anytype.core_models.DEFAULT_TIME_STYLE
|
||||
import com.anytypeio.anytype.core_models.FALLBACK_DATE_PATTERN
|
||||
import com.anytypeio.anytype.domain.misc.LocaleProvider
|
||||
import java.text.DateFormat
|
||||
|
@ -33,7 +34,7 @@ class AppDefaultDateFormatProviderImpl @Inject constructor(
|
|||
override fun provide(): String {
|
||||
return try {
|
||||
val locale = localeProvider.locale()
|
||||
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale)
|
||||
val dateFormat = DateFormat.getDateInstance(DEFAULT_TIME_STYLE, locale)
|
||||
if (dateFormat is SimpleDateFormat) {
|
||||
dateFormat.toPattern()
|
||||
} else {
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package com.anytypeio.anytype.device.providers
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import com.anytypeio.anytype.core_models.FALLBACK_DATE_PATTERN
|
||||
import com.anytypeio.anytype.core_models.RelativeDate
|
||||
import com.anytypeio.anytype.core_models.TimeInMillis
|
||||
import com.anytypeio.anytype.core_models.TimeInSeconds
|
||||
import com.anytypeio.anytype.domain.misc.DateProvider
|
||||
import com.anytypeio.anytype.domain.misc.DateType
|
||||
import com.anytypeio.anytype.domain.misc.LocaleProvider
|
||||
import com.anytypeio.anytype.domain.vault.ObserveVaultSettings
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
|
@ -15,14 +18,30 @@ import java.time.ZoneId
|
|||
import java.time.ZoneOffset
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class DateProviderImpl @Inject constructor(
|
||||
private val defaultZoneId: ZoneId,
|
||||
private val localeProvider: LocaleProvider
|
||||
private val localeProvider: LocaleProvider,
|
||||
private val vaultSettings: ObserveVaultSettings,
|
||||
scope: CoroutineScope
|
||||
) : DateProvider {
|
||||
|
||||
private val defaultDateFormat = MutableStateFlow(FALLBACK_DATE_PATTERN)
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
vaultSettings.flow().collect { settings ->
|
||||
defaultDateFormat.value = settings.dateFormat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun calculateDateType(date: TimeInSeconds): DateType {
|
||||
val dateInstant = Instant.ofEpochSecond(date)
|
||||
val givenDate = dateInstant.atZone(defaultZoneId).toLocalDate()
|
||||
|
@ -88,12 +107,13 @@ class DateProviderImpl @Inject constructor(
|
|||
return weekAgoWithZeroTime.atStartOfDay(defaultZoneId).toEpochSecond()
|
||||
}
|
||||
|
||||
override fun getRelativeTimeSpanString(date: TimeInSeconds): CharSequence = DateUtils.getRelativeTimeSpanString(
|
||||
date,
|
||||
System.currentTimeMillis(),
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
override fun getRelativeTimeSpanString(date: TimeInSeconds): CharSequence =
|
||||
DateUtils.getRelativeTimeSpanString(
|
||||
date,
|
||||
System.currentTimeMillis(),
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
|
||||
override fun adjustToStartOfDayInUserTimeZone(timestamp: TimeInSeconds): TimeInMillis {
|
||||
val instant = Instant.ofEpochSecond(timestamp)
|
||||
|
@ -102,19 +122,21 @@ class DateProviderImpl @Inject constructor(
|
|||
return (startOfDay.toEpochSecond(ZoneOffset.UTC) * 1000)
|
||||
}
|
||||
|
||||
override fun adjustFromStartOfDayInUserTimeZoneToUTC(timestamp: TimeInMillis, zoneId: ZoneId): TimeInSeconds {
|
||||
// Convert the timestamp to an Instan
|
||||
val instant = Instant.ofEpochSecond(timestamp)
|
||||
override fun adjustFromStartOfDayInUserTimeZoneToUTC(
|
||||
timeInMillis: TimeInMillis
|
||||
): TimeInSeconds {
|
||||
// Convert the timestamp to an Instant
|
||||
val instant = Instant.ofEpochMilli(timeInMillis)
|
||||
|
||||
// Convert the Instant to a ZonedDateTime in UTC
|
||||
val utcDateTime = instant.atZone(ZoneOffset.UTC)
|
||||
|
||||
// Convert the UTC ZonedDateTime to the local time zone
|
||||
val localDateTime = utcDateTime.withZoneSameInstant(zoneId)
|
||||
val localDateTime = utcDateTime.withZoneSameInstant(defaultZoneId)
|
||||
|
||||
// Get the local date and the start of the day in the local time zone
|
||||
val localDate = localDateTime.toLocalDate()
|
||||
val startOfDay = localDate.atStartOfDay(zoneId)
|
||||
val startOfDay = localDate.atStartOfDay(defaultZoneId)
|
||||
|
||||
// Check if the UTC timestamp is at the boundary of the day in the local time zone
|
||||
return when {
|
||||
|
@ -122,10 +144,12 @@ class DateProviderImpl @Inject constructor(
|
|||
// If the UTC timestamp is after the start of the day in the local time zone, return the start of the next day
|
||||
startOfDay.plusDays(1).toEpochSecond()
|
||||
}
|
||||
|
||||
utcDateTime.toLocalDate().isBefore(startOfDay.toLocalDate()) -> {
|
||||
// If the UTC timestamp is before the start of the day in the local time zone, return the start of the previous day
|
||||
startOfDay.minusDays(1).toEpochSecond()
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Otherwise, return the start of the day
|
||||
startOfDay.toEpochSecond()
|
||||
|
@ -139,20 +163,21 @@ class DateProviderImpl @Inject constructor(
|
|||
val formatter = SimpleDateFormat(pattern, locale)
|
||||
return formatter.format(Date(timestamp))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e,"Error formatting timestamp to date string")
|
||||
Timber.e(e, "Error formatting timestamp to date string")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun formatTimestampToDateAndTime(
|
||||
timestamp: TimeInMillis,
|
||||
dateStyle: Int,
|
||||
timeStyle: Int
|
||||
): Pair<String, String> {
|
||||
Timber.d("Formatting timestamp [$timestamp] to date and time")
|
||||
return try {
|
||||
val locale = localeProvider.locale()
|
||||
val datePattern = (DateFormat.getDateInstance(dateStyle, locale) as SimpleDateFormat).toPattern()
|
||||
val timePattern = (DateFormat.getTimeInstance(timeStyle, locale) as SimpleDateFormat).toPattern()
|
||||
val datePattern = defaultDateFormat.value
|
||||
val timePattern =
|
||||
(DateFormat.getTimeInstance(timeStyle, locale) as SimpleDateFormat).toPattern()
|
||||
val dateFormatter = SimpleDateFormat(datePattern, locale)
|
||||
val timeFormatter = SimpleDateFormat(timePattern, locale)
|
||||
val date = Date(timestamp)
|
||||
|
@ -174,6 +199,51 @@ class DateProviderImpl @Inject constructor(
|
|||
|
||||
return truncatedDateTime1 == truncatedDateTime2
|
||||
}
|
||||
|
||||
override fun calculateRelativeDates(dateInSeconds: TimeInSeconds?): RelativeDate? {
|
||||
|
||||
if (dateInSeconds == null || dateInSeconds == 0L) return null
|
||||
val zoneId = defaultZoneId
|
||||
val dateInstant = Instant.ofEpochSecond(dateInSeconds)
|
||||
val givenDate = dateInstant.atZone(zoneId).toLocalDate()
|
||||
val today = LocalDate.now(zoneId)
|
||||
|
||||
val daysDifference = ChronoUnit.DAYS.between(today, givenDate)
|
||||
|
||||
return when (daysDifference) {
|
||||
0L -> RelativeDate.Today
|
||||
1L -> RelativeDate.Tomorrow
|
||||
-1L -> RelativeDate.Yesterday
|
||||
else -> {
|
||||
val timestampMillis = TimeUnit.SECONDS.toMillis(dateInSeconds)
|
||||
|
||||
val (dateString, timeString) = formatTimestampToDateAndTime(timestampMillis)
|
||||
RelativeDate.Other(
|
||||
formattedDate = dateString,
|
||||
formattedTime = timeString
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLocalDateOfTime(epochMilli: Long): LocalDate {
|
||||
val instant = Instant.ofEpochMilli(epochMilli)
|
||||
return instant.atZone(defaultZoneId).toLocalDate()
|
||||
}
|
||||
|
||||
override fun isTimestampWithinYearRange(timeStampInMillis: Long, yearRange: IntRange): Boolean {
|
||||
// Convert the timestamp in milliseconds to an Instant object
|
||||
val instant = Instant.ofEpochMilli(timeStampInMillis)
|
||||
|
||||
// Convert the Instant to a LocalDate object in default time zone
|
||||
val date = instant.atZone(defaultZoneId).toLocalDate()
|
||||
|
||||
// Extract the year from the LocalDate object
|
||||
val year = date.year
|
||||
|
||||
// Check if the year is within the desired range
|
||||
return year in yearRange.first()..yearRange.last()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,8 +3,11 @@ package com.anytypeio.anytype
|
|||
import com.anytypeio.anytype.device.providers.DateProviderImpl
|
||||
import com.anytypeio.anytype.domain.misc.DateProvider
|
||||
import com.anytypeio.anytype.domain.misc.LocaleProvider
|
||||
import com.anytypeio.anytype.domain.vault.ObserveVaultSettings
|
||||
import java.time.ZoneId
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
@ -14,20 +17,25 @@ import org.mockito.MockitoAnnotations
|
|||
|
||||
class DateProviderImplTest {
|
||||
|
||||
private val dispatcher = StandardTestDispatcher(name = "Default test dispatcher")
|
||||
|
||||
@Mock
|
||||
lateinit var localeProvider: LocaleProvider
|
||||
|
||||
@Mock
|
||||
lateinit var observeVaultSettings: ObserveVaultSettings
|
||||
|
||||
lateinit var dateProviderImpl: DateProvider
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
dateProviderImpl = DateProviderImpl(ZoneId.systemDefault(), localeProvider)
|
||||
Mockito.`when`(localeProvider.locale()).thenReturn(Locale.getDefault())
|
||||
Mockito.`when`(localeProvider.language()).thenReturn("en")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun adjustToStartOfDayInUserTimeZoneWithPastStart() {
|
||||
fun adjustToStartOfDayInUserTimeZoneWithPastStart() = runTest(dispatcher) {
|
||||
|
||||
val timeStamp = 1720828800L // Saturday, 13 July 2024 00:00:00
|
||||
|
||||
|
@ -61,15 +69,23 @@ class DateProviderImplTest {
|
|||
Triple(timeStamp, ZoneId.of("GMT-12"), 1720872000L)
|
||||
)
|
||||
tests.forEach { (utcTimestamp, zoneId, expected) ->
|
||||
dateProviderImpl = DateProviderImpl(
|
||||
defaultZoneId = zoneId,
|
||||
localeProvider = localeProvider,
|
||||
vaultSettings = observeVaultSettings,
|
||||
scope = backgroundScope
|
||||
)
|
||||
val startOfDayInLocalZone =
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId)
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(
|
||||
timeInMillis = utcTimestamp * 1000
|
||||
)
|
||||
|
||||
assertEquals(expected, startOfDayInLocalZone)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun adjustToStartOfDayInUserTimeZoneWithPastMidday() {
|
||||
fun adjustToStartOfDayInUserTimeZoneWithPastMidday() = runTest(dispatcher) {
|
||||
|
||||
val timeStamp = 1720888800L // Saturday, 13 July 2024 16:40:00
|
||||
val tests = listOf(
|
||||
|
@ -101,16 +117,23 @@ class DateProviderImplTest {
|
|||
Triple(timeStamp, ZoneId.of("GMT+12"), 1720785600L),
|
||||
Triple(timeStamp, ZoneId.of("GMT-12"), 1720872000L)
|
||||
)
|
||||
// TODO fix tests
|
||||
tests.forEach { (utcTimestamp, zoneId, expected) ->
|
||||
dateProviderImpl = DateProviderImpl(
|
||||
defaultZoneId = zoneId,
|
||||
localeProvider = localeProvider,
|
||||
vaultSettings = observeVaultSettings,
|
||||
scope = backgroundScope
|
||||
)
|
||||
val startOfDayInLocalZone =
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId)
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp * 1000)
|
||||
|
||||
assertEquals(expected, startOfDayInLocalZone)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun adjustToStartOfDayInUserTimeZoneWithPastEnd() {
|
||||
fun adjustToStartOfDayInUserTimeZoneWithPastEnd() = runTest(dispatcher) {
|
||||
|
||||
val timeStamp = 1720915199L // Saturday, 13 July 2024 23:59:59
|
||||
val tests = listOf(
|
||||
|
@ -143,15 +166,21 @@ class DateProviderImplTest {
|
|||
Triple(timeStamp, ZoneId.of("GMT-12"), 1720872000L)
|
||||
)
|
||||
tests.forEach { (utcTimestamp, zoneId, expected) ->
|
||||
dateProviderImpl = DateProviderImpl(
|
||||
defaultZoneId = zoneId,
|
||||
localeProvider = localeProvider,
|
||||
vaultSettings = observeVaultSettings,
|
||||
scope = backgroundScope
|
||||
)
|
||||
val startOfDayInLocalZone =
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId)
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp * 1000)
|
||||
|
||||
assertEquals(expected, startOfDayInLocalZone)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun adjustToStartOfDayInUserTimeZoneWithFuture() {
|
||||
fun adjustToStartOfDayInUserTimeZoneWithFuture() = runTest(dispatcher) {
|
||||
|
||||
val timeStamp = 3720915199 // Saturday, 29 November 2087 03:33:19
|
||||
val tests = listOf(
|
||||
|
@ -181,9 +210,16 @@ class DateProviderImplTest {
|
|||
Triple(timeStamp, ZoneId.of("GMT-11"), 3720942000L),
|
||||
Triple(timeStamp, ZoneId.of("GMT-12"), 3720945600L)
|
||||
)
|
||||
// TODO fix tests
|
||||
tests.forEach { (utcTimestamp, zoneId, expected) ->
|
||||
dateProviderImpl = DateProviderImpl(
|
||||
defaultZoneId = zoneId,
|
||||
localeProvider = localeProvider,
|
||||
vaultSettings = observeVaultSettings,
|
||||
scope = backgroundScope
|
||||
)
|
||||
val startOfDayInLocalZone =
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId)
|
||||
dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp * 1000)
|
||||
|
||||
assertEquals(expected, startOfDayInLocalZone)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue