1
0
Fork 0
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:
Konstantin Ivanov 2024-12-04 16:06:16 +01:00 committed by GitHub
parent 2b40f21910
commit ca8721b725
Signed by: github
GPG key ID: B5690EEEBB952194
284 changed files with 6589 additions and 1211 deletions

View file

@ -21,6 +21,8 @@ dependencies {
testImplementation libs.androidXTestCore
testImplementation libs.robolectric
testImplementation libs.mockitoKotlin
testImplementation libs.coroutineTesting
testImplementation libs.turbine
compileOnly libs.javaxInject
}

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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)
}